"use strict";
/* eslint-disable require-atomic-updates */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const assert_1 = __importDefault(require("assert"));
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const module_1 = __importStar(require("module"));
const picomatch_1 = __importDefault(require("picomatch"));
const tinyglobby_1 = require("tinyglobby");
const common_1 = require("./common");
const colors_1 = require("./colors");
const follow_1 = require("./follow");
const log_1 = require("./log");
const detector = __importStar(require("./detector"));
const options_1 = __importDefault(require("./options"));
// Note: as a developer, you can set the PKG_STRICT_VER variable.
//       this will turn on some assertion in the walker code below
//       to assert that each file content/state that we appending
//       to the virtual file system applies to  a real file,
//       not a symlink.
//       By default assertion are disabled as they can have a
//       performance hit.
const strictVerify = Boolean(process.env.PKG_STRICT_VER);
const win32 = process.platform === 'win32';
/**
 * Checks if a module is a core module
 * module.isBuiltin is available in Node.js 16.17.0 or later. Use that if available
 * as prefix-only modules (those starting with 'node:') can only be checked that way.
 */
function isBuiltin(moduleName) {
    if (Reflect.has(module_1.default, 'isBuiltin') &&
        typeof module_1.default.isBuiltin === 'function') {
        return module_1.default.isBuiltin(moduleName);
    }
    const moduleNameWithoutPrefix = moduleName.startsWith('node:')
        ? moduleName.slice(5)
        : moduleName;
    return module_1.builtinModules.includes(moduleNameWithoutPrefix);
}
function unlikelyJavascript(file) {
    return ['.css', '.html', '.json', '.vue'].includes(path_1.default.extname(file));
}
function isPublic(config) {
    if (config.private) {
        return false;
    }
    const { licenses } = config;
    let { license } = config;
    if (licenses) {
        license = licenses;
    }
    if (license && !Array.isArray(license)) {
        license = typeof license === 'string' ? license : license.type;
    }
    if (Array.isArray(license)) {
        license = license.map((c) => String(c.type || c)).join(',');
    }
    if (!license) {
        return false;
    }
    if (/^\(/.test(license)) {
        license = license.slice(1);
    }
    if (/\)$/.test(license)) {
        license = license.slice(0, -1);
    }
    license = license.toLowerCase();
    const allLicenses = Array.prototype.concat(license.split(' or '), license.split(' and '), license.split('/'), license.split(','));
    let result = false;
    const foss = [
        'isc',
        'mit',
        'apache-2.0',
        'apache 2.0',
        'public domain',
        'bsd',
        'bsd-2-clause',
        'bsd-3-clause',
        'wtfpl',
        'cc-by-3.0',
        'x11',
        'artistic-2.0',
        'gplv3',
        'mpl',
        'mplv2.0',
        'unlicense',
        'apache license 2.0',
        'zlib',
        'mpl-2.0',
        'nasa-1.3',
        'apache license, version 2.0',
        'lgpl-2.1+',
        'cc0-1.0',
    ];
    for (const c of allLicenses) {
        result = foss.indexOf(c) >= 0;
        if (result) {
            break;
        }
    }
    return result;
}
function upon(p, base) {
    if (typeof p !== 'string') {
        throw (0, log_1.wasReported)('Config items must be strings. See examples');
    }
    let negate = false;
    if (p[0] === '!') {
        p = p.slice(1);
        negate = true;
    }
    p = path_1.default.join(base, p);
    if (win32) {
        p = p.replace(/\\/g, '/');
    }
    if (negate) {
        p = `!${p}`;
    }
    return p;
}
function collect(ps) {
    return (0, tinyglobby_1.globSync)(ps, { absolute: true, dot: true });
}
function expandFiles(efs, base) {
    if (!Array.isArray(efs)) {
        efs = [efs];
    }
    efs = collect(efs.map((p) => upon(p, base)));
    return efs;
}
async function stepRead(record) {
    if (strictVerify) {
        (0, assert_1.default)(record.file === (0, common_1.toNormalizedRealPath)(record.file));
    }
    let body;
    try {
        body = await promises_1.default.readFile(record.file);
    }
    catch (error) {
        const exception = error;
        log_1.log.error(`Cannot read file, ${exception.code}`, record.file);
        throw (0, log_1.wasReported)(exception.message);
    }
    record.body = body;
}
function stepStrip(record) {
    let body = (record.body || '').toString('utf8');
    if (/^\ufeff/.test(body)) {
        body = body.replace(/^\ufeff/, '');
    }
    if (/^#!/.test(body)) {
        body = body.replace(/^#![^\n]*\n/, '\n');
    }
    record.body = body;
}
function stepDetect(record, marker, derivatives) {
    let { body = '' } = record;
    if (body instanceof Buffer) {
        body = body.toString();
    }
    try {
        detector.detect(body, (node, trying) => {
            const { toplevel } = marker;
            let d = detector.visitorSuccessful(node);
            if (d) {
                if (d.mustExclude) {
                    return false;
                }
                d.mayExclude = d.mayExclude || trying;
                derivatives.push(d);
                return false;
            }
            d = detector.visitorNonLiteral(node);
            if (d) {
                if (typeof d === 'object' && d.mustExclude) {
                    return false;
                }
                const debug = !toplevel || d.mayExclude || trying;
                const level = debug ? 'debug' : 'warn';
                log_1.log[level](`Cannot resolve '${d.alias}'`, [
                    record.file,
                    'Dynamic require may fail at run time, because the requested file',
                    'is unknown at compilation time and not included into executable.',
                    "Use a string literal as an argument for 'require', or leave it",
                    "as is and specify the resolved file name in 'scripts' option.",
                ]);
                return false;
            }
            d = detector.visitorMalformed(node);
            if (d) {
                // there is no 'mustExclude'
                const debug = !toplevel || trying;
                const level = debug ? 'debug' : 'warn'; // there is no 'mayExclude'
                log_1.log[level](`Malformed requirement for '${d.alias}'`, [record.file]);
                return false;
            }
            d = detector.visitorUseSCWD(node);
            if (d) {
                // there is no 'mustExclude'
                const level = 'debug'; // there is no 'mayExclude'
                log_1.log[level](`Path.resolve(${d.alias}) is ambiguous`, [
                    record.file,
                    "It resolves relatively to 'process.cwd' by default, however",
                    "you may want to use 'path.dirname(require.main.filename)'",
                ]);
                return false;
            }
            return true; // can i go inside?
        });
    }
    catch (error) {
        log_1.log.error(error.message, record.file);
        throw (0, log_1.wasReported)(error.message);
    }
}
/**
 * Find a common junction point between a symlink and the real file path.
 *
 * @param {string} file The file path, including symlink(s).
 * @param {string} realFile The real path to the file.
 *
 * @throws {Error} If no common junction point is found prior to hitting the
 *                 filesystem root.
 */
async function findCommonJunctionPoint(file, realFile) {
    // find common denominator => where the link changes
    while (true) {
        const stats = await promises_1.default.lstat(file);
        if (stats.isSymbolicLink()) {
            return { file, realFile };
        }
        file = path_1.default.dirname(file);
        realFile = path_1.default.dirname(realFile);
        // If the directory is /, break out of the loop and log an error.
        if (file === path_1.default.parse(file).root ||
            realFile === path_1.default.parse(realFile).root) {
            throw new Error('Reached root directory without finding a common junction point');
        }
    }
}
class Walker {
    constructor() {
        this.tasks = [];
        this.records = {};
        this.dictionary = {};
        this.patches = {};
        this.params = {};
        this.symLinks = {};
    }
    appendRecord({ file, store }) {
        if (this.records[file]) {
            return;
        }
        if (store === common_1.STORE_BLOB ||
            store === common_1.STORE_CONTENT ||
            store === common_1.STORE_LINKS) {
            // make sure we have a real file
            if (strictVerify) {
                (0, assert_1.default)(file === (0, common_1.toNormalizedRealPath)(file));
            }
        }
        this.records[file] = { file };
    }
    append(task) {
        if (strictVerify) {
            (0, assert_1.default)(typeof task.file === 'string');
            (0, assert_1.default)(task.file === (0, common_1.normalizePath)(task.file));
        }
        this.appendRecord(task);
        this.tasks.push(task);
        const what = {
            [common_1.STORE_BLOB]: 'Bytecode of',
            [common_1.STORE_CONTENT]: 'Content of',
            [common_1.STORE_LINKS]: 'Directory',
            [common_1.STORE_STAT]: 'Stat info of',
        }[task.store];
        if (task.reason) {
            log_1.log.debug(`${what} ${task.file} is added to queue. It was required from ${task.reason}`);
        }
        else {
            log_1.log.debug(`${what} ${task.file} is added to queue.`);
        }
    }
    async appendSymlink(file, realFile) {
        const a = await findCommonJunctionPoint(file, realFile);
        file = a.file;
        realFile = a.realFile;
        if (!this.symLinks[file]) {
            const dn = path_1.default.dirname(file);
            this.appendFileInFolder({
                file: dn,
                store: common_1.STORE_LINKS,
                data: path_1.default.basename(file),
            });
            log_1.log.debug(`adding symlink ${file}  => ${path_1.default.relative(file, realFile)}`);
            this.symLinks[file] = realFile;
            this.appendStat({
                file: realFile,
                store: common_1.STORE_STAT,
            });
            this.appendStat({
                file: dn,
                store: common_1.STORE_STAT,
            });
            this.appendStat({
                file,
                store: common_1.STORE_STAT,
            });
        }
    }
    appendStat(task) {
        (0, assert_1.default)(task.store === common_1.STORE_STAT);
        this.append(task);
    }
    appendFileInFolder(task) {
        if (strictVerify) {
            (0, assert_1.default)(task.store === common_1.STORE_LINKS);
            (0, assert_1.default)(typeof task.file === 'string');
        }
        const realFile = (0, common_1.toNormalizedRealPath)(task.file);
        if (realFile === task.file) {
            this.append(task);
            return;
        }
        this.append(Object.assign(Object.assign({}, task), { file: realFile }));
        this.appendStat({
            file: task.file,
            store: common_1.STORE_STAT,
        });
        this.appendStat({
            file: path_1.default.dirname(task.file),
            store: common_1.STORE_STAT,
        });
    }
    async appendBlobOrContent(task) {
        if (strictVerify) {
            (0, assert_1.default)(task.file === (0, common_1.normalizePath)(task.file));
        }
        (0, assert_1.default)(task.store === common_1.STORE_BLOB || task.store === common_1.STORE_CONTENT);
        (0, assert_1.default)(typeof task.file === 'string');
        const realFile = (0, common_1.toNormalizedRealPath)(task.file);
        const { ignore } = options_1.default.get();
        if (ignore) {
            // check if the file matches one of the ignore regex patterns
            const match = picomatch_1.default.isMatch(realFile, ignore, {
                windows: win32,
            });
            if (match) {
                log_1.log.debug(`Ignoring file: ${realFile} due to top level config ignore pattern`);
                return;
            }
        }
        if (realFile === task.file) {
            this.append(task);
            return;
        }
        this.append(Object.assign(Object.assign({}, task), { file: realFile }));
        await this.appendSymlink(task.file, realFile);
        this.appendStat({
            file: task.file,
            store: common_1.STORE_STAT,
        });
    }
    async appendFilesFromConfig(marker) {
        const { config, configPath, base } = marker;
        const pkgConfig = config === null || config === void 0 ? void 0 : config.pkg;
        if (pkgConfig) {
            let { scripts } = pkgConfig;
            if (scripts) {
                scripts = expandFiles(scripts, base);
                for (const script of scripts) {
                    const stat = await promises_1.default.stat(script);
                    if (stat.isFile()) {
                        if (!(0, common_1.isDotJS)(script) && !(0, common_1.isDotJSON)(script) && !(0, common_1.isDotNODE)(script)) {
                            log_1.log.warn("Non-javascript file is specified in 'scripts'.", [
                                'Pkg will probably fail to parse. Specify *.js in glob.',
                                script,
                            ]);
                        }
                        await this.appendBlobOrContent({
                            file: (0, common_1.normalizePath)(script),
                            marker,
                            store: common_1.STORE_BLOB,
                            reason: configPath,
                        });
                    }
                }
            }
            let { assets } = pkgConfig;
            if (assets) {
                assets = expandFiles(assets, base);
                for (const asset of assets) {
                    log_1.log.debug(' Adding asset : .... ', asset);
                    const stat = await promises_1.default.stat(asset);
                    if (stat.isFile()) {
                        await this.appendBlobOrContent({
                            file: (0, common_1.normalizePath)(asset),
                            marker,
                            store: common_1.STORE_CONTENT,
                            reason: configPath,
                        });
                    }
                }
            }
        }
        else if (config) {
            let { files } = config;
            if (files) {
                files = expandFiles(files, base);
                for (let file of files) {
                    file = (0, common_1.normalizePath)(file);
                    const stat = await promises_1.default.stat(file);
                    if (stat.isFile()) {
                        // 1) remove sources of top-level(!) package 'files' i.e. ship as BLOB
                        // 2) non-source (non-js) files of top-level package are shipped as CONTENT
                        // 3) parsing some js 'files' of non-top-level packages fails, hence all CONTENT
                        if (marker.toplevel) {
                            await this.appendBlobOrContent({
                                file,
                                marker,
                                store: (0, common_1.isDotJS)(file) ? common_1.STORE_BLOB : common_1.STORE_CONTENT,
                                reason: configPath,
                            });
                        }
                        else {
                            await this.appendBlobOrContent({
                                file,
                                marker,
                                store: common_1.STORE_CONTENT,
                                reason: configPath,
                            });
                        }
                    }
                }
            }
        }
    }
    async stepActivate(marker, derivatives) {
        if (!marker) {
            (0, assert_1.default)(false);
        }
        if (marker.activated) {
            return;
        }
        const { config, base } = marker;
        if (!config) {
            (0, assert_1.default)(false);
        }
        const { name } = config;
        if (name) {
            const d = this.dictionary[name];
            if (d) {
                if (typeof config.dependencies === 'object' &&
                    typeof d.dependencies === 'object') {
                    Object.assign(config.dependencies, d.dependencies);
                    delete d.dependencies;
                }
                Object.assign(config, d);
                marker.hasDictionary = true;
            }
        }
        const { dependencies } = config;
        if (typeof dependencies === 'object') {
            for (const dependency in dependencies) {
                // it may be `undefined` - overridden
                // in dictionary (see publicsuffixlist)
                if (dependencies[dependency]) {
                    derivatives.push({
                        alias: dependency,
                        aliasType: common_1.ALIAS_AS_RESOLVABLE,
                        fromDependencies: true,
                    });
                    derivatives.push({
                        alias: `${dependency}/package.json`,
                        aliasType: common_1.ALIAS_AS_RESOLVABLE,
                        fromDependencies: true,
                    });
                }
            }
        }
        const pkgConfig = config.pkg;
        if (pkgConfig) {
            const { patches } = pkgConfig;
            if (patches) {
                for (const key in patches) {
                    if (patches[key]) {
                        const p = path_1.default.join(base, key);
                        this.patches[p] = patches[key];
                    }
                }
            }
            const { deployFiles } = pkgConfig;
            if (deployFiles) {
                marker.hasDeployFiles = true;
                for (const deployFile of deployFiles) {
                    const type = deployFile[2] || 'file';
                    log_1.log.warn(`Cannot include ${type} %1 into executable.`, [
                        `The ${type} must be distributed with executable as %2.`,
                        `%1: ${path_1.default.relative(process.cwd(), path_1.default.join(base, deployFile[0]))}`,
                        `%2: path-to-executable/${deployFile[1]}`,
                    ]);
                }
            }
            if (pkgConfig.log) {
                pkgConfig.log(log_1.log, { packagePath: base });
            }
        }
        await this.appendFilesFromConfig(marker);
        marker.public = isPublic(config);
        if (!marker.public && marker.toplevel) {
            marker.public = this.params.publicToplevel;
        }
        if (!marker.public && !marker.toplevel && this.params.publicPackages) {
            marker.public =
                this.params.publicPackages[0] === '*' ||
                    (!!name && this.params.publicPackages.indexOf(name) !== -1);
        }
        marker.activated = true;
        // assert no further work with config
        delete marker.config;
    }
    hasPatch(record) {
        const patch = this.patches[record.file];
        if (!patch) {
            return;
        }
        return true;
    }
    stepPatch(record) {
        const patch = this.patches[record.file];
        if (!patch) {
            return;
        }
        let body = (record.body || '').toString('utf8');
        for (let i = 0; i < patch.length; i += 2) {
            if (typeof patch[i] === 'object') {
                if (patch[i].do === 'erase') {
                    body = patch[i + 1];
                }
                else if (patch[i].do === 'prepend') {
                    body = patch[i + 1] + body;
                }
                else if (patch[i].do === 'append') {
                    body += patch[i + 1];
                }
            }
            else if (typeof patch[i] === 'string') {
                // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
                // function escapeRegExp
                const esc = patch[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                const regexp = new RegExp(esc, 'g');
                body = body.replace(regexp, patch[i + 1]);
            }
        }
        record.body = body;
    }
    async stepDerivatives_ALIAS_AS_RELATIVE(record, marker, derivative) {
        const file = (0, common_1.normalizePath)(path_1.default.join(path_1.default.dirname(record.file), derivative.alias));
        let stat;
        try {
            stat = await promises_1.default.stat(file);
        }
        catch (error) {
            const { toplevel } = marker;
            const exception = error;
            const debug = !toplevel && exception.code === 'ENOENT';
            const level = debug ? 'debug' : 'warn';
            log_1.log[level](`Cannot stat, ${exception.code}`, [
                file,
                `The file was required from '${record.file}'`,
            ]);
        }
        if (stat && stat.isFile()) {
            await this.appendBlobOrContent({
                file,
                marker,
                store: common_1.STORE_CONTENT,
                reason: record.file,
            });
        }
    }
    async stepDerivatives_ALIAS_AS_RESOLVABLE(record, marker, derivative) {
        var _a, _b;
        const newPackages = [];
        const catchReadFile = (file) => {
            (0, assert_1.default)((0, common_1.isPackageJson)(file), `walker: ${file} must be package.json`);
            newPackages.push({ packageJson: file });
        };
        const catchPackageFilter = (config, base) => {
            const newPackage = newPackages[newPackages.length - 1];
            newPackage.marker = {
                config,
                configPath: newPackage.packageJson,
                base,
            };
        };
        let newFile = '';
        let failure;
        const basedir = path_1.default.dirname(record.file);
        try {
            newFile = await (0, follow_1.follow)(derivative.alias, {
                basedir,
                // default is extensions: ['.js'], but
                // it is not enough because 'typos.json'
                // is not taken in require('./typos')
                // in 'normalize-package-data/lib/fixer.js'
                extensions: ['.js', '.json', '.node'],
                catchReadFile,
                catchPackageFilter,
            });
        }
        catch (error) {
            failure = error;
        }
        if (failure) {
            const { toplevel } = marker;
            const mainNotFound = newPackages.length > 0 && !((_b = (_a = newPackages[0].marker) === null || _a === void 0 ? void 0 : _a.config) === null || _b === void 0 ? void 0 : _b.main);
            const debug = !toplevel ||
                derivative.mayExclude ||
                (mainNotFound && derivative.fromDependencies);
            const level = debug ? 'debug' : 'warn';
            if (mainNotFound) {
                const message = "Entry 'main' not found in %1";
                log_1.log[level](message, [
                    `%1: ${newPackages[0].packageJson}`,
                    `%2: ${record.file}`,
                ]);
            }
            else {
                log_1.log[level](`${colors_1.pc.yellow(failure.message)}  in ${record.file}`);
            }
            return;
        }
        let newPackageForNewRecords;
        for (const newPackage of newPackages) {
            let newFile2;
            try {
                newFile2 = await (0, follow_1.follow)(derivative.alias, {
                    basedir: path_1.default.dirname(record.file),
                    extensions: ['.js', '.json', '.node'],
                    ignoreFile: newPackage.packageJson,
                });
                if (strictVerify) {
                    (0, assert_1.default)(newFile2 === (0, common_1.normalizePath)(newFile2));
                }
            }
            catch (_) {
                // not setting is enough
            }
            if (newFile2 !== newFile) {
                newPackageForNewRecords = newPackage;
                break;
            }
        }
        if (newPackageForNewRecords) {
            if (strictVerify) {
                (0, assert_1.default)(newPackageForNewRecords.packageJson ===
                    (0, common_1.normalizePath)(newPackageForNewRecords.packageJson));
            }
            await this.appendBlobOrContent({
                file: newPackageForNewRecords.packageJson,
                marker: newPackageForNewRecords.marker,
                store: common_1.STORE_CONTENT,
                reason: record.file,
            });
        }
        await this.appendBlobOrContent({
            file: newFile,
            marker: newPackageForNewRecords ? newPackageForNewRecords.marker : marker,
            store: common_1.STORE_BLOB,
            reason: record.file,
        });
    }
    async stepDerivatives(record, marker, derivatives) {
        for (const derivative of derivatives) {
            // TODO: actually use the target node version
            if (isBuiltin(derivative.alias))
                continue;
            switch (derivative.aliasType) {
                case common_1.ALIAS_AS_RELATIVE:
                    await this.stepDerivatives_ALIAS_AS_RELATIVE(record, marker, derivative);
                    break;
                case common_1.ALIAS_AS_RESOLVABLE:
                    await this.stepDerivatives_ALIAS_AS_RESOLVABLE(record, marker, derivative);
                    break;
                default:
                    (0, assert_1.default)(false, `walker: unknown aliasType ${derivative.aliasType}`);
            }
        }
    }
    async step_STORE_ANY(record, marker, store) {
        if (strictVerify) {
            (0, assert_1.default)(record.file === (0, common_1.toNormalizedRealPath)(record.file));
        }
        if (record[store] !== undefined)
            return;
        record[store] = false; // default is discard
        this.appendStat({
            file: record.file,
            store: common_1.STORE_STAT,
        });
        const derivatives1 = [];
        await this.stepActivate(marker, derivatives1);
        await this.stepDerivatives(record, marker, derivatives1);
        if (store === common_1.STORE_BLOB) {
            if (unlikelyJavascript(record.file) || (0, common_1.isDotNODE)(record.file)) {
                await this.appendBlobOrContent({
                    file: record.file,
                    marker,
                    store: common_1.STORE_CONTENT,
                });
                return; // discard
            }
            if (marker.public || marker.hasDictionary) {
                await this.appendBlobOrContent({
                    file: record.file,
                    marker,
                    store: common_1.STORE_CONTENT,
                });
            }
        }
        if (store === common_1.STORE_BLOB || this.hasPatch(record)) {
            if (!record.body) {
                await stepRead(record);
                this.stepPatch(record);
                if (store === common_1.STORE_BLOB) {
                    stepStrip(record);
                }
            }
            if (store === common_1.STORE_BLOB) {
                const derivatives2 = [];
                stepDetect(record, marker, derivatives2);
                await this.stepDerivatives(record, marker, derivatives2);
            }
        }
        record[store] = true;
    }
    step_STORE_LINKS(record, data) {
        if (strictVerify) {
            (0, assert_1.default)(record.file === (0, common_1.toNormalizedRealPath)(record.file), ' expecting real file !!!');
        }
        if (record[common_1.STORE_LINKS]) {
            record[common_1.STORE_LINKS].push(data);
            return;
        }
        record[common_1.STORE_LINKS] = [data];
        if (record[common_1.STORE_STAT]) {
            return;
        }
        this.appendStat({
            file: record.file,
            store: common_1.STORE_STAT,
        });
    }
    async step_STORE_STAT(record) {
        if (record[common_1.STORE_STAT])
            return;
        const realPath = (0, common_1.toNormalizedRealPath)(record.file);
        if (realPath !== record.file) {
            this.appendStat({
                file: realPath,
                store: common_1.STORE_STAT,
            });
        }
        try {
            const valueStat = await promises_1.default.stat(record.file);
            const value = {
                mode: valueStat.mode,
                size: valueStat.isFile() ? valueStat.size : 0,
                isFileValue: valueStat.isFile(),
                isDirectoryValue: valueStat.isDirectory(),
                isSocketValue: valueStat.isSocket(),
                isSymbolicLinkValue: valueStat.isSymbolicLink(),
            };
            record[common_1.STORE_STAT] = value;
        }
        catch (error) {
            const exception = error;
            log_1.log.error(`Cannot stat, ${exception.code}`, record.file);
            throw (0, log_1.wasReported)(exception.message);
        }
        if (path_1.default.dirname(record.file) !== record.file) {
            // root directory
            this.appendFileInFolder({
                file: path_1.default.dirname(record.file),
                store: common_1.STORE_LINKS,
                data: path_1.default.basename(record.file),
            });
        }
    }
    async step(task) {
        const { file, store, data } = task;
        const record = this.records[file];
        switch (store) {
            case common_1.STORE_BLOB:
            case common_1.STORE_CONTENT:
                // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
                await this.step_STORE_ANY(record, task.marker, store);
                break;
            case common_1.STORE_LINKS:
                this.step_STORE_LINKS(record, data);
                break;
            case common_1.STORE_STAT:
                await this.step_STORE_STAT(record);
                break;
            default:
                (0, assert_1.default)(false, `walker: unknown store ${store}`);
        }
    }
    async readDictionary(marker) {
        var _a, _b, _c;
        if (((_a = this.params.noDictionary) === null || _a === void 0 ? void 0 : _a[0]) === '*') {
            return;
        }
        const dd = path_1.default.join(__dirname, '../dictionary');
        const files = await promises_1.default.readdir(dd);
        for (const file of files) {
            if (/\.js$/.test(file)) {
                const name = file.slice(0, -3);
                if ((_b = this.params.noDictionary) === null || _b === void 0 ? void 0 : _b.includes(file)) {
                    continue;
                }
                // eslint-disable-next-line import/no-dynamic-require, global-require, @typescript-eslint/no-var-requires
                const config = require(path_1.default.join(dd, file));
                this.dictionary[name] = config;
            }
        }
        const pkgConfig = (_c = marker.config) === null || _c === void 0 ? void 0 : _c.pkg;
        if (pkgConfig) {
            const { dictionary } = pkgConfig;
            if (dictionary) {
                for (const name in dictionary) {
                    if (dictionary[name]) {
                        this.dictionary[name] = { pkg: dictionary[name] };
                    }
                }
            }
        }
    }
    async start(marker, entrypoint, addition, params) {
        this.params = params;
        this.symLinks = {};
        await this.readDictionary(marker);
        entrypoint = (0, common_1.normalizePath)(entrypoint);
        await this.appendBlobOrContent({
            file: entrypoint,
            marker,
            store: common_1.STORE_BLOB,
        });
        if (addition) {
            addition = (0, common_1.normalizePath)(addition);
            await this.appendBlobOrContent({
                file: addition,
                marker,
                store: common_1.STORE_CONTENT,
            });
        }
        const { tasks } = this;
        for (let i = 0; i < tasks.length; i += 1) {
            // NO MULTIPLE WORKERS! THIS WILL LEAD TO NON-DETERMINISTIC
            // ORDER. one-by-one fifo is the only way to iterate tasks
            await this.step(tasks[i]);
        }
        return {
            symLinks: this.symLinks,
            records: this.records,
            entrypoint: (0, common_1.normalizePath)(entrypoint),
        };
    }
}
async function walker(...args) {
    const w = new Walker();
    return w.start(...args);
}
exports.default = walker;
//# sourceMappingURL=walker.js.map