mirror of
https://github.com/Sevichecc/Urara-Blog.git
synced 2025-05-05 19:09:29 +08:00
362 lines
14 KiB
Text
362 lines
14 KiB
Text
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.Job = exports.nodeFileTrace = void 0;
|
|
const path_1 = require("path");
|
|
const graceful_fs_1 = __importDefault(require("graceful-fs"));
|
|
const analyze_1 = __importDefault(require("./analyze"));
|
|
const resolve_dependency_1 = __importDefault(require("./resolve-dependency"));
|
|
const micromatch_1 = require("micromatch");
|
|
const sharedlib_emit_1 = require("./utils/sharedlib-emit");
|
|
const path_2 = require("path");
|
|
const async_sema_1 = require("async-sema");
|
|
const fsReadFile = graceful_fs_1.default.promises.readFile;
|
|
const fsReadlink = graceful_fs_1.default.promises.readlink;
|
|
const fsStat = graceful_fs_1.default.promises.stat;
|
|
function inPath(path, parent) {
|
|
const pathWithSep = path_2.join(parent, path_1.sep);
|
|
return path.startsWith(pathWithSep) && path !== pathWithSep;
|
|
}
|
|
async function nodeFileTrace(files, opts = {}) {
|
|
const job = new Job(opts);
|
|
if (opts.readFile)
|
|
job.readFile = opts.readFile;
|
|
if (opts.stat)
|
|
job.stat = opts.stat;
|
|
if (opts.readlink)
|
|
job.readlink = opts.readlink;
|
|
if (opts.resolve)
|
|
job.resolve = opts.resolve;
|
|
job.ts = true;
|
|
await Promise.all(files.map(async (file) => {
|
|
const path = path_1.resolve(file);
|
|
await job.emitFile(path, 'initial');
|
|
return job.emitDependency(path);
|
|
}));
|
|
const result = {
|
|
fileList: job.fileList,
|
|
esmFileList: job.esmFileList,
|
|
reasons: job.reasons,
|
|
warnings: job.warnings
|
|
};
|
|
return result;
|
|
}
|
|
exports.nodeFileTrace = nodeFileTrace;
|
|
;
|
|
class Job {
|
|
constructor({ base = process.cwd(), processCwd, exports, conditions = exports || ['node'], exportsOnly = false, paths = {}, ignore, log = false, mixedModules = false, ts = true, analysis = {}, cache,
|
|
// we use a default of 1024 concurrency to balance
|
|
// performance and memory usage for fs operations
|
|
fileIOConcurrency = 1024, }) {
|
|
this.reasons = new Map();
|
|
this.ts = ts;
|
|
base = path_1.resolve(base);
|
|
this.ignoreFn = (path) => {
|
|
if (path.startsWith('..' + path_1.sep))
|
|
return true;
|
|
return false;
|
|
};
|
|
if (typeof ignore === 'string')
|
|
ignore = [ignore];
|
|
if (typeof ignore === 'function') {
|
|
const ig = ignore;
|
|
this.ignoreFn = (path) => {
|
|
if (path.startsWith('..' + path_1.sep))
|
|
return true;
|
|
if (ig(path))
|
|
return true;
|
|
return false;
|
|
};
|
|
}
|
|
else if (Array.isArray(ignore)) {
|
|
const resolvedIgnores = ignore.map(ignore => path_1.relative(base, path_1.resolve(base || process.cwd(), ignore)));
|
|
this.ignoreFn = (path) => {
|
|
if (path.startsWith('..' + path_1.sep))
|
|
return true;
|
|
if (micromatch_1.isMatch(path, resolvedIgnores))
|
|
return true;
|
|
return false;
|
|
};
|
|
}
|
|
this.base = base;
|
|
this.cwd = path_1.resolve(processCwd || base);
|
|
this.conditions = conditions;
|
|
this.exportsOnly = exportsOnly;
|
|
const resolvedPaths = {};
|
|
for (const path of Object.keys(paths)) {
|
|
const trailer = paths[path].endsWith('/');
|
|
const resolvedPath = path_1.resolve(base, paths[path]);
|
|
resolvedPaths[path] = resolvedPath + (trailer ? '/' : '');
|
|
}
|
|
this.paths = resolvedPaths;
|
|
this.log = log;
|
|
this.mixedModules = mixedModules;
|
|
this.fileIOQueue = new async_sema_1.Sema(fileIOConcurrency);
|
|
this.analysis = {};
|
|
if (analysis !== false) {
|
|
Object.assign(this.analysis, {
|
|
// whether to glob any analysis like __dirname + '/dir/' or require('x/' + y)
|
|
// that might output any file in a directory
|
|
emitGlobs: true,
|
|
// whether __filename and __dirname style
|
|
// expressions should be analyzed as file references
|
|
computeFileReferences: true,
|
|
// evaluate known bindings to assist with glob and file reference analysis
|
|
evaluatePureExpressions: true,
|
|
}, analysis === true ? {} : analysis);
|
|
}
|
|
this.fileCache = cache && cache.fileCache || new Map();
|
|
this.statCache = cache && cache.statCache || new Map();
|
|
this.symlinkCache = cache && cache.symlinkCache || new Map();
|
|
this.analysisCache = cache && cache.analysisCache || new Map();
|
|
if (cache) {
|
|
cache.fileCache = this.fileCache;
|
|
cache.statCache = this.statCache;
|
|
cache.symlinkCache = this.symlinkCache;
|
|
cache.analysisCache = this.analysisCache;
|
|
}
|
|
this.fileList = new Set();
|
|
this.esmFileList = new Set();
|
|
this.processed = new Set();
|
|
this.warnings = new Set();
|
|
}
|
|
async readlink(path) {
|
|
const cached = this.symlinkCache.get(path);
|
|
if (cached !== undefined)
|
|
return cached;
|
|
await this.fileIOQueue.acquire();
|
|
try {
|
|
const link = await fsReadlink(path);
|
|
// also copy stat cache to symlink
|
|
const stats = this.statCache.get(path);
|
|
if (stats)
|
|
this.statCache.set(path_1.resolve(path, link), stats);
|
|
this.symlinkCache.set(path, link);
|
|
return link;
|
|
}
|
|
catch (e) {
|
|
if (e.code !== 'EINVAL' && e.code !== 'ENOENT' && e.code !== 'UNKNOWN')
|
|
throw e;
|
|
this.symlinkCache.set(path, null);
|
|
return null;
|
|
}
|
|
finally {
|
|
this.fileIOQueue.release();
|
|
}
|
|
}
|
|
async isFile(path) {
|
|
const stats = await this.stat(path);
|
|
if (stats)
|
|
return stats.isFile();
|
|
return false;
|
|
}
|
|
async isDir(path) {
|
|
const stats = await this.stat(path);
|
|
if (stats)
|
|
return stats.isDirectory();
|
|
return false;
|
|
}
|
|
async stat(path) {
|
|
const cached = this.statCache.get(path);
|
|
if (cached)
|
|
return cached;
|
|
await this.fileIOQueue.acquire();
|
|
try {
|
|
const stats = await fsStat(path);
|
|
this.statCache.set(path, stats);
|
|
return stats;
|
|
}
|
|
catch (e) {
|
|
if (e.code === 'ENOENT') {
|
|
this.statCache.set(path, null);
|
|
return null;
|
|
}
|
|
throw e;
|
|
}
|
|
finally {
|
|
this.fileIOQueue.release();
|
|
}
|
|
}
|
|
async resolve(id, parent, job, cjsResolve) {
|
|
return resolve_dependency_1.default(id, parent, job, cjsResolve);
|
|
}
|
|
async readFile(path) {
|
|
const cached = this.fileCache.get(path);
|
|
if (cached !== undefined)
|
|
return cached;
|
|
await this.fileIOQueue.acquire();
|
|
try {
|
|
const source = (await fsReadFile(path)).toString();
|
|
this.fileCache.set(path, source);
|
|
return source;
|
|
}
|
|
catch (e) {
|
|
if (e.code === 'ENOENT' || e.code === 'EISDIR') {
|
|
this.fileCache.set(path, null);
|
|
return null;
|
|
}
|
|
throw e;
|
|
}
|
|
finally {
|
|
this.fileIOQueue.release();
|
|
}
|
|
}
|
|
async realpath(path, parent, seen = new Set()) {
|
|
if (seen.has(path))
|
|
throw new Error('Recursive symlink detected resolving ' + path);
|
|
seen.add(path);
|
|
const symlink = await this.readlink(path);
|
|
// emit direct symlink paths only
|
|
if (symlink) {
|
|
const parentPath = path_1.dirname(path);
|
|
const resolved = path_1.resolve(parentPath, symlink);
|
|
const realParent = await this.realpath(parentPath, parent);
|
|
if (inPath(path, realParent))
|
|
await this.emitFile(path, 'resolve', parent, true);
|
|
return this.realpath(resolved, parent, seen);
|
|
}
|
|
// keep backtracking for realpath, emitting folder symlinks within base
|
|
if (!inPath(path, this.base))
|
|
return path;
|
|
return path_2.join(await this.realpath(path_1.dirname(path), parent, seen), path_1.basename(path));
|
|
}
|
|
async emitFile(path, reasonType, parent, isRealpath = false) {
|
|
if (!isRealpath) {
|
|
path = await this.realpath(path, parent);
|
|
}
|
|
path = path_1.relative(this.base, path);
|
|
if (parent) {
|
|
parent = path_1.relative(this.base, parent);
|
|
}
|
|
let reasonEntry = this.reasons.get(path);
|
|
if (!reasonEntry) {
|
|
reasonEntry = {
|
|
type: [reasonType],
|
|
ignored: false,
|
|
parents: new Set()
|
|
};
|
|
this.reasons.set(path, reasonEntry);
|
|
}
|
|
else if (!reasonEntry.type.includes(reasonType)) {
|
|
reasonEntry.type.push(reasonType);
|
|
}
|
|
if (parent && this.ignoreFn(path, parent)) {
|
|
if (!this.fileList.has(path) && reasonEntry) {
|
|
reasonEntry.ignored = true;
|
|
}
|
|
return false;
|
|
}
|
|
if (parent) {
|
|
reasonEntry.parents.add(parent);
|
|
}
|
|
this.fileList.add(path);
|
|
return true;
|
|
}
|
|
async getPjsonBoundary(path) {
|
|
const rootSeparatorIndex = path.indexOf(path_1.sep);
|
|
let separatorIndex;
|
|
while ((separatorIndex = path.lastIndexOf(path_1.sep)) > rootSeparatorIndex) {
|
|
path = path.slice(0, separatorIndex);
|
|
if (await this.isFile(path + path_1.sep + 'package.json'))
|
|
return path;
|
|
}
|
|
return undefined;
|
|
}
|
|
async emitDependency(path, parent) {
|
|
if (this.processed.has(path)) {
|
|
if (parent) {
|
|
await this.emitFile(path, 'dependency', parent);
|
|
}
|
|
return;
|
|
}
|
|
;
|
|
this.processed.add(path);
|
|
const emitted = await this.emitFile(path, 'dependency', parent);
|
|
if (!emitted)
|
|
return;
|
|
if (path.endsWith('.json'))
|
|
return;
|
|
if (path.endsWith('.node'))
|
|
return await sharedlib_emit_1.sharedLibEmit(path, this);
|
|
// js files require the "type": "module" lookup, so always emit the package.json
|
|
if (path.endsWith('.js')) {
|
|
const pjsonBoundary = await this.getPjsonBoundary(path);
|
|
if (pjsonBoundary)
|
|
await this.emitFile(pjsonBoundary + path_1.sep + 'package.json', 'resolve', path);
|
|
}
|
|
let analyzeResult;
|
|
const cachedAnalysis = this.analysisCache.get(path);
|
|
if (cachedAnalysis) {
|
|
analyzeResult = cachedAnalysis;
|
|
}
|
|
else {
|
|
const source = await this.readFile(path);
|
|
if (source === null)
|
|
throw new Error('File ' + path + ' does not exist.');
|
|
analyzeResult = await analyze_1.default(path, source.toString(), this);
|
|
this.analysisCache.set(path, analyzeResult);
|
|
}
|
|
const { deps, imports, assets, isESM } = analyzeResult;
|
|
if (isESM)
|
|
this.esmFileList.add(path_1.relative(this.base, path));
|
|
await Promise.all([
|
|
...[...assets].map(async (asset) => {
|
|
const ext = path_1.extname(asset);
|
|
if (ext === '.js' || ext === '.mjs' || ext === '.node' || ext === '' ||
|
|
this.ts && (ext === '.ts' || ext === '.tsx') && asset.startsWith(this.base) && asset.slice(this.base.length).indexOf(path_1.sep + 'node_modules' + path_1.sep) === -1)
|
|
await this.emitDependency(asset, path);
|
|
else
|
|
await this.emitFile(asset, 'asset', path);
|
|
}),
|
|
...[...deps].map(async (dep) => {
|
|
try {
|
|
var resolved = await this.resolve(dep, path, this, !isESM);
|
|
}
|
|
catch (e) {
|
|
this.warnings.add(new Error(`Failed to resolve dependency ${dep}:\n${e && e.message}`));
|
|
return;
|
|
}
|
|
if (Array.isArray(resolved)) {
|
|
for (const item of resolved) {
|
|
// ignore builtins
|
|
if (item.startsWith('node:'))
|
|
return;
|
|
await this.emitDependency(item, path);
|
|
}
|
|
}
|
|
else {
|
|
// ignore builtins
|
|
if (resolved.startsWith('node:'))
|
|
return;
|
|
await this.emitDependency(resolved, path);
|
|
}
|
|
}),
|
|
...[...imports].map(async (dep) => {
|
|
try {
|
|
var resolved = await this.resolve(dep, path, this, false);
|
|
}
|
|
catch (e) {
|
|
this.warnings.add(new Error(`Failed to resolve dependency ${dep}:\n${e && e.message}`));
|
|
return;
|
|
}
|
|
if (Array.isArray(resolved)) {
|
|
for (const item of resolved) {
|
|
// ignore builtins
|
|
if (item.startsWith('node:'))
|
|
return;
|
|
await this.emitDependency(item, path);
|
|
}
|
|
}
|
|
else {
|
|
// ignore builtins
|
|
if (resolved.startsWith('node:'))
|
|
return;
|
|
await this.emitDependency(resolved, path);
|
|
}
|
|
})
|
|
]);
|
|
}
|
|
}
|
|
exports.Job = Job;
|