fix: support `withFileTypes` in `fs.{readdir|readdirSync}` (#41627)

fix: support withFileTypes in fs.{readdir|readdirSync}
This commit is contained in:
Shelley Vohr 2024-03-21 15:22:40 +01:00 коммит произвёл GitHub
Родитель dd3fd78e63
Коммит 040acaaf30
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
2 изменённых файлов: 115 добавлений и 33 удалений

Просмотреть файл

@ -89,18 +89,19 @@ const gid = process.getgid?.() ?? 0;
const fakeTime = new Date(); const fakeTime = new Date();
function getDirents (p: string, { 0: names, 1: types }: any[][]): Dirent[] { function getDirents (p: string, { 0: names, 1: types }: any[][]): Dirent[] {
const info = splitPath(p); for (let i = 0; i < names.length; i++) {
const len = names.length; let type = types[i];
for (let i = 0; i < len; i++) { const info = splitPath(path.join(p, names[i]));
if (info.isAsar) { if (info.isAsar) {
const archive = getOrCreateArchive(info.asarPath); const archive = getOrCreateArchive(info.asarPath);
const stats = archive!.stat(p); if (!archive) continue;
const stats = archive.stat(info.filePath);
if (!stats) continue; if (!stats) continue;
names[i] = getDirent(p, names[i], stats.type); type = stats.type;
} else {
names[i] = getDirent(p, names[i], types[i]);
} }
names[i] = getDirent(p, names[i], type);
} }
return names; return names;
} }
@ -883,6 +884,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
const pathInfo = splitPath(originalPath); const pathInfo = splitPath(originalPath);
let queue: [string, string[]][] = []; let queue: [string, string[]][] = [];
const withFileTypes = Boolean(options?.withFileTypes);
let initialItem = []; let initialItem = [];
if (pathInfo.isAsar) { if (pathInfo.isAsar) {
@ -890,19 +892,29 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
if (!archive) return result; if (!archive) return result;
const files = archive.readdir(pathInfo.filePath); const files = archive.readdir(pathInfo.filePath);
if (!files) return result; if (!files) return result;
// If we're in an asar dir, we need to ensure the result is in the same format as the
// native call to readdir withFileTypes i.e. an array of arrays.
initialItem = files; initialItem = files;
if (withFileTypes) {
initialItem = [
[...initialItem], initialItem.map((p: string) => {
return internalBinding('fs').internalModuleStat(path.join(originalPath, p));
})
];
}
} else { } else {
initialItem = await binding.readdir( initialItem = await binding.readdir(
path.toNamespacedPath(originalPath), path.toNamespacedPath(originalPath),
options!.encoding, options!.encoding,
!!options!.withFileTypes, withFileTypes,
kUsePromises kUsePromises
); );
} }
queue = [[originalPath, initialItem]]; queue = [[originalPath, initialItem]];
if (options?.withFileTypes) { if (withFileTypes) {
while (queue.length > 0) { while (queue.length > 0) {
// @ts-expect-error this is a valid array destructure assignment. // @ts-expect-error this is a valid array destructure assignment.
const { 0: pathArg, 1: readDir } = queue.pop(); const { 0: pathArg, 1: readDir } = queue.pop();
@ -911,16 +923,22 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
if (dirent.isDirectory()) { if (dirent.isDirectory()) {
const direntPath = path.join(pathArg, dirent.name); const direntPath = path.join(pathArg, dirent.name);
const info = splitPath(direntPath); const info = splitPath(direntPath);
let archive;
let readdirResult; let readdirResult;
if (info.isAsar) { if (info.isAsar) {
archive = getOrCreateArchive(info.asarPath); const archive = getOrCreateArchive(info.asarPath);
if (!archive) continue; if (!archive) continue;
readdirResult = archive.readdir(info.filePath); const files = archive.readdir(info.filePath);
if (!files) continue;
readdirResult = [
[...files], files.map((p: string) => {
return internalBinding('fs').internalModuleStat(path.join(direntPath, p));
})
];
} else { } else {
readdirResult = await binding.readdir( readdirResult = await binding.readdir(
direntPath, direntPath,
options.encoding, options!.encoding,
true, true,
kUsePromises kUsePromises
); );
@ -973,15 +991,24 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
function read (pathArg: string) { function read (pathArg: string) {
let readdirResult; let readdirResult;
const pathInfo = splitPath(pathArg);
let archive; const pathInfo = splitPath(pathArg);
if (pathInfo.isAsar) { if (pathInfo.isAsar) {
const { asarPath, filePath } = pathInfo; const { asarPath, filePath } = pathInfo;
archive = getOrCreateArchive(asarPath); const archive = getOrCreateArchive(asarPath);
if (!archive) return; if (!archive) return;
readdirResult = archive.readdir(filePath); readdirResult = archive.readdir(filePath);
if (!readdirResult) return;
// If we're in an asar dir, we need to ensure the result is in the same format as the
// native call to readdir withFileTypes i.e. an array of arrays.
if (withFileTypes) {
readdirResult = [
[...readdirResult], readdirResult.map((p: string) => {
return internalBinding('fs').internalModuleStat(path.join(pathArg, p));
})
];
}
} else { } else {
readdirResult = binding.readdir( readdirResult = binding.readdir(
path.toNamespacedPath(pathArg), path.toNamespacedPath(pathArg),
@ -993,21 +1020,22 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
if (readdirResult === undefined) return; if (readdirResult === undefined) return;
if (withFileTypes) { if (withFileTypes) {
// Calling `readdir` with `withFileTypes=true`, the result is an array of arrays.
// The first array is the names, and the second array is the types.
// They are guaranteed to be the same length; hence, setting `length` to the length
// of the first array within the result.
const length = readdirResult[0].length; const length = readdirResult[0].length;
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
let dirent; const resultPath = path.join(pathArg, readdirResult[0][i]);
if (pathInfo.isAsar) { const info = splitPath(resultPath);
const stats = archive!.stat(pathArg);
let type = readdirResult[1][i];
if (info.isAsar) {
const archive = getOrCreateArchive(info.asarPath);
if (!archive) return;
const stats = archive.stat(info.filePath);
if (!stats) continue; if (!stats) continue;
dirent = getDirent(pathArg, readdirResult[0][i], stats.type); type = stats.type;
} else {
dirent = getDirent(pathArg, readdirResult[0][i], readdirResult[1][i]);
} }
const dirent = getDirent(pathArg, readdirResult[0][i], type);
readdirResults.push(dirent); readdirResults.push(dirent);
if (dirent.isDirectory()) { if (dirent.isDirectory()) {
pathsQueue.push(path.join(dirent.path, dirent.name)); pathsQueue.push(path.join(dirent.path, dirent.name));
@ -1018,12 +1046,9 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
const resultPath = path.join(pathArg, readdirResult[i]); const resultPath = path.join(pathArg, readdirResult[i]);
const relativeResultPath = path.relative(basePath, resultPath); const relativeResultPath = path.relative(basePath, resultPath);
const stat = internalBinding('fs').internalModuleStat(resultPath); const stat = internalBinding('fs').internalModuleStat(resultPath);
readdirResults.push(relativeResultPath);
// 1 indicates directory readdirResults.push(relativeResultPath);
if (stat === 1) { if (stat === 1) pathsQueue.push(resultPath);
pathsQueue.push(resultPath);
}
} }
} }
} }

Просмотреть файл

@ -898,9 +898,28 @@ describe('asar package', function () {
expect(dirs).to.deep.equal(['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']); expect(dirs).to.deep.equal(['dir1', 'dir2', 'dir3', 'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js']);
}); });
itremote('supports recursive readdirSync', async () => { itremote('supports recursive readdirSync withFileTypes', () => {
const dir = path.join(fixtures, 'recursive-asar'); const dir = path.join(fixtures, 'recursive-asar');
const files = await fs.readdirSync(dir, { recursive: true }); const files = fs.readdirSync(dir, { recursive: true, withFileTypes: true });
expect(files).to.have.length(24);
for (const file of files) {
expect(file).to.be.an.instanceOf(fs.Dirent);
}
const paths = files.map((a: any) => a.name);
expect(paths).to.have.members([
'a.asar', 'nested', 'test.txt', 'dir1', 'dir2', 'dir3',
'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js',
'hello.txt', 'file1', 'file2', 'file3', 'link1', 'link2',
'file1', 'file2', 'file3', 'file1', 'file2', 'file3'
]);
});
itremote('supports recursive readdirSync', () => {
const dir = path.join(fixtures, 'recursive-asar');
const files = fs.readdirSync(dir, { recursive: true });
expect(files).to.have.members([ expect(files).to.have.members([
'a.asar', 'a.asar',
'nested', 'nested',
@ -1007,6 +1026,25 @@ describe('asar package', function () {
]); ]);
}); });
itremote('supports readdir withFileTypes', async () => {
const dir = path.join(fixtures, 'recursive-asar');
const files = await promisify(fs.readdir)(dir, { recursive: true, withFileTypes: true });
expect(files).to.have.length(24);
for (const file of files) {
expect(file).to.be.an.instanceOf(fs.Dirent);
}
const paths = files.map((a: any) => a.name);
expect(paths).to.have.members([
'a.asar', 'nested', 'test.txt', 'dir1', 'dir2', 'dir3',
'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js',
'hello.txt', 'file1', 'file2', 'file3', 'link1', 'link2',
'file1', 'file2', 'file3', 'file1', 'file2', 'file3'
]);
});
itremote('supports withFileTypes', async () => { itremote('supports withFileTypes', async () => {
const p = path.join(asarDir, 'a.asar'); const p = path.join(asarDir, 'a.asar');
@ -1102,6 +1140,25 @@ describe('asar package', function () {
]); ]);
}); });
itremote('supports readdir withFileTypes', async () => {
const dir = path.join(fixtures, 'recursive-asar');
const files = await fs.promises.readdir(dir, { recursive: true, withFileTypes: true });
expect(files).to.have.length(24);
for (const file of files) {
expect(file).to.be.an.instanceOf(fs.Dirent);
}
const paths = files.map((a: any) => a.name);
expect(paths).to.have.members([
'a.asar', 'nested', 'test.txt', 'dir1', 'dir2', 'dir3',
'file1', 'file2', 'file3', 'link1', 'link2', 'ping.js',
'hello.txt', 'file1', 'file2', 'file3', 'link1', 'link2',
'file1', 'file2', 'file3', 'file1', 'file2', 'file3'
]);
});
itremote('supports withFileTypes', async function () { itremote('supports withFileTypes', async function () {
const p = path.join(asarDir, 'a.asar'); const p = path.join(asarDir, 'a.asar');
const dirs = await fs.promises.readdir(p, { withFileTypes: true }); const dirs = await fs.promises.readdir(p, { withFileTypes: true });