expo-modules-autolinking
Version:
Scripts that autolink Expo modules.
124 lines (113 loc) • 4.05 kB
text/typescript
import fs from 'fs';
import path from 'path';
import { memoize } from './memoize';
/** List filtered top-level files in `targetPath` (returns absolute paths) */
export async function listFilesSorted(
targetPath: string,
filter: (basename: string) => boolean
): Promise<string[]> {
try {
// `readdir` isn't guaranteed to be sorted on Windows
return (await fs.promises.readdir(targetPath, { withFileTypes: true }))
.filter((entry) => entry.isFile() && filter(entry.name))
.sort((a, b) => a.name.localeCompare(b.name))
.map((entry) => path.join(targetPath, entry.name));
} catch {
return [];
}
}
/** List nested files in top-level directories in `targetPath` (returns relative paths) */
export async function listFilesInDirectories(
targetPath: string,
filter: (basename: string) => boolean
): Promise<string[]> {
return (
await Promise.all(
(await fs.promises.readdir(targetPath, { withFileTypes: true }))
.filter((entry) => entry.isDirectory() && entry.name !== 'node_modules')
.sort((a, b) => a.name.localeCompare(b.name))
.map(async (directory) => {
const entries = await fs.promises.readdir(path.join(targetPath, directory.name), {
withFileTypes: true,
});
return entries
.filter((entry) => entry.isFile() && filter(entry.name))
.sort((a, b) => a.name.localeCompare(b.name))
.map((entry) => path.join(directory.name, entry.name));
})
)
).flat(1);
}
/** Iterate folders recursively for files, optionally sorting results and filtering directories */
export async function* scanFilesRecursively(
parentPath: string,
includeDirectory?: (parentPath: string, name: string) => boolean,
sort = !fs.opendir
) {
const queue = [parentPath];
let targetPath: string | undefined;
while (queue.length > 0 && (targetPath = queue.shift()) != null) {
try {
const entries = sort
? (await fs.promises.readdir(targetPath, { withFileTypes: true })).sort((a, b) =>
a.name.localeCompare(b.name)
)
: await fs.promises.opendir(targetPath);
for await (const entry of entries) {
if (entry.isDirectory() && entry.name !== 'node_modules') {
if (!includeDirectory || includeDirectory(targetPath, entry.name)) {
queue.push(path.join(targetPath, entry.name));
}
} else if (entry.isFile()) {
yield {
path: path.join(targetPath, entry.name),
parentPath: targetPath,
name: entry.name,
} as const;
}
}
} catch {
continue;
}
}
}
export const fileExistsAsync = async (file: string): Promise<string | null> => {
const stat = await fs.promises.stat(file).catch(() => null);
return stat?.isFile() ? file : null;
};
export const fastJoin: (from: string, append: string) => string =
path.sep === '/'
? (from, append) => `${from}${path.sep}${append}`
: (from, append) =>
`${from}${path.sep}${append[0] === '@' ? append.replace('/', path.sep) : append}`;
export const maybeRealpath = async (target: string): Promise<string | null> => {
try {
return await fs.promises.realpath(target);
} catch {
return null;
}
};
export function isPathInside(child: string, parent: string): boolean {
const relative = path.relative(parent, child);
return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
}
export type PackageJson = Record<string, unknown> & {
name?: string;
version?: string;
peerDependencies?: Record<string, string>;
codegenConfig?: Record<string, unknown>;
};
export const loadPackageJson = memoize(async function loadPackageJson(
jsonPath: string
): Promise<PackageJson | null> {
try {
const packageJsonText = await fs.promises.readFile(jsonPath, 'utf8');
const json = JSON.parse(packageJsonText);
if (typeof json !== 'object' || json == null) {
return null;
}
return json;
} catch {
return null;
}
});