<commit_before>// This module handles the management of modules required by the runtime and flows.
// Essentially this means keeping track of what extra modules a flow requires,
// ensuring those modules are installed and providing a standard way for nodes
// to require those modules safely.

const fs = require("fs-extra");
const registryUtil = require("./util");
const path = require("path");
const clone = require("clone");
const exec = require("@node-red/util").exec;
const log = require("@node-red/util").log;

const BUILTIN_MODULES = require('module').builtinModules;
const EXTERNAL_MODULES_DIR = "externalModules";

// TODO: outsource running npm to a plugin
const NPM_COMMAND = (process.platform === "win32") ? "npm.cmd" : "npm";

let registeredTypes = {};
let subflowTypes = {};
let settings;

let knownExternalModules = {};

let installEnabled = true;
let installAllowList = ['*'];
let installDenyList = [];

function getInstallDir() {
    return path.resolve(path.join(settings.userDir || process.env.NODE_RED_HOME || ".", "externalModules"));
}

async function refreshExternalModules() {
    const externalModuleDir = path.resolve(path.join(settings.userDir || process.env.NODE_RED_HOME || ".", EXTERNAL_MODULES_DIR));
    try {
        const pkgFile = JSON.parse(await fs.readFile(path.join(externalModuleDir,"package.json"),"utf-8"));
        knownExternalModules = pkgFile.dependencies;
    } catch(err) {
    }
}

function init(_settings) {
    settings = _settings;
    knownExternalModules = {};
    installEnabled = true;
    if (settings.externalModules && settings.externalModules.modules) {
        if (settings.externalModules.modules.allowList || settings.externalModules.modules.denyList) {
            installAllowList = settings.externalModules.modules.allowList;
            installDenyList = settings.externalModules.modules.denyList;
        }
        if (settings.externalModules.modules.hasOwnProperty("allowInstall")) {
            installEnabled = settings.externalModules.modules.allowInstall
        }
    }
    installAllowList = registryUtil.parseModuleList(installAllowList);
    installDenyList = registryUtil.parseModuleList(installDenyList);
}

function register(type, dynamicModuleListProperty) {
    registeredTypes[type] = dynamicModuleListProperty;
}

function registerSubflow(type, subflowConfig) {
    subflowTypes[type] = subflowConfig;
}

function requireModule(module) {
    if (!registryUtil.checkModuleAllowed( module, null,installAllowList,installDenyList)) {
        const e = new Error("Module not allowed");
        e.code = "module_not_allowed";
        throw e;
    }
    if (BUILTIN_MODULES.indexOf(module) !== -1) {
        return require(module);
    }
    if (!knownExternalModules[module]) {
        const e = new Error("Module not allowed");
        e.code = "module_not_allowed";
        throw e;
    }
    const externalModuleDir = path.resolve(path.join(settings.userDir || process.env.NODE_RED_HOME || ".", EXTERNAL_MODULES_DIR));
    const moduleDir = path.join(externalModuleDir,"node_modules",module);
    return require(moduleDir);
}

function parseModuleName(module) {
    var match = /((?:@[^/]+\/)?[^/@]+)(?:@([\s\S]+))?/.exec(module);
    if (match) {
        return {
            spec: module,
            module: match[1],
            version: match[2],
            builtin: BUILTIN_MODULES.indexOf(match[1]) !== -1,
            known: !!knownExternalModules[match[1]]
        }<commit_msg>Support loading external module sub path Fixes #3023<commit_after>// This module handles the management of modules required by the runtime and flows.
// Essentially this means keeping track of what extra modules a flow requires,
// ensuring those modules are installed and providing a standard way for nodes
// to require those modules safely.

const fs = require("fs-extra");
const registryUtil = require("./util");
const path = require("path");
const clone = require("clone");
const exec = require("@node-red/util").exec;
const log = require("@node-red/util").log;

const BUILTIN_MODULES = require('module').builtinModules;
const EXTERNAL_MODULES_DIR = "externalModules";

// TODO: outsource running npm to a plugin
const NPM_COMMAND = (process.platform === "win32") ? "npm.cmd" : "npm";

let registeredTypes = {};
let subflowTypes = {};
let settings;

let knownExternalModules = {};

let installEnabled = true;
let installAllowList = ['*'];
let installDenyList = [];

function getInstallDir() {
    return path.resolve(path.join(settings.userDir || process.env.NODE_RED_HOME || ".", "externalModules"));
}

async function refreshExternalModules() {
    const externalModuleDir = path.resolve(path.join(settings.userDir || process.env.NODE_RED_HOME || ".", EXTERNAL_MODULES_DIR));
    try {
        const pkgFile = JSON.parse(await fs.readFile(path.join(externalModuleDir,"package.json"),"utf-8"));
        knownExternalModules = pkgFile.dependencies;
    } catch(err) {
    }
}

function init(_settings) {
    settings = _settings;
    knownExternalModules = {};
    installEnabled = true;
    if (settings.externalModules && settings.externalModules.modules) {
        if (settings.externalModules.modules.allowList || settings.externalModules.modules.denyList) {
            installAllowList = settings.externalModules.modules.allowList;
            installDenyList = settings.externalModules.modules.denyList;
        }
        if (settings.externalModules.modules.hasOwnProperty("allowInstall")) {
            installEnabled = settings.externalModules.modules.allowInstall
        }
    }
    installAllowList = registryUtil.parseModuleList(installAllowList);
    installDenyList = registryUtil.parseModuleList(installDenyList);
}

function register(type, dynamicModuleListProperty) {
    registeredTypes[type] = dynamicModuleListProperty;
}

function registerSubflow(type, subflowConfig) {
    subflowTypes[type] = subflowConfig;
}

function requireModule(module) {
    if (!registryUtil.checkModuleAllowed( module, null,installAllowList,installDenyList)) {
        const e = new Error("Module not allowed");
        e.code = "module_not_allowed";
        throw e;
    }

    const parsedModule = parseModuleName(module);

    if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) {
        return require(parsedModule.module);
    }
    if (!knownExternalModules[parsedModule.module]) {
        const e = new Error("Module not allowed");
        e.code = "module_not_allowed";
        throw e;
    }
    const externalModuleDir = path.resolve(path.join(settings.userDir || process.env.NODE_RED_HOME || ".", EXTERNAL_MODULES_DIR));
    const moduleDir = path.join(externalModuleDir,"node_modules",module);
    return require(moduleDir);
}

function parseModuleName(module) {
    var match = /((?:@[^/]+\/)?[^/@]+)(?:@([\s\S]+))?/.exec(module);
    if (match) {
        return {
            spec: module,
            module: match[1],
            version: match[2],
            builtin: BUILTIN_MODULES.indexOf(match[1]) !== -1,
            known: !!knownExternalModules[match[1]]
        }
{'fraction_non_alphanumeric': 0.12758132956152757, 'fraction_numerical': 0.0031117397454031117, 'mean_word_length': 3.820040899795501, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4939201', 'n_tokens_mistral': 2004, 'n_tokens_neox': 2033, 'n_words': 438}