<commit_before>const fs     = require('fs')
const path   = require('path')
const script = require('../assets/nexrender.jsx')

/* helpers */
const escape = str => {
    str = JSON.stringify(str)
    str = str.substring(1, str.length-1)
    str = `'${str.replace(/\'/g, '\\\'')}'`
    return str
}

const selectLayers = ({ composition, layerName, layerIndex }, callbackString) => {
    const method = layerName ? 'selectLayersByName' : 'selectLayersByIndex';
    const compo  = composition === undefined ? 'null' : escape(composition);
    const value  = layerName ? escape(layerName) : layerIndex;

    return (`nexrender.${method}(${compo}, ${value}, ${callbackString});`);
}

const renderIf = (value, string) => {
    const encoded = typeof value == 'string' ? escape(value) : JSON.stringify(value);
    return value === undefined ? '' : string.replace('$value', encoded);
}

const partsOfKeypath = (keypath) => {
    var parts = keypath.split('->');
    return (parts.length === 1) ? keypath.split('.') : parts
}

/* scripting wrappers */
const wrapFootage = ({ dest, ...asset }) => (`(function() {
    ${selectLayers(asset, `function(layer) {
        nexrender.replaceFootage(layer, '${dest.replace(/\\/g, "\\\\")}')
    }`)}
})();\n`)

const wrapData = ({ property, value, expression, ...asset }) => (`(function() {
    ${selectLayers(asset, /* syntax:js */`function(layer) {

        var parts = ${JSON.stringify(partsOfKeypath(property))};
        ${renderIf(value, `var value = { "value": $value }`)}
        ${renderIf(expression, `var value = { "expression": $value }`)}
        nexrender.changeValueForKeypath(layer, parts, value);

        return true;
    }`)}
})();\n`)

// @deprecated in favor of wrapEnhancedScript (implementation below)
const wrapScript = ({ dest }) => (`(function() {
    ${fs.readFileSync(dest, 'utf8')}
})();\n`)

/*
    Wrap Enhanced Script
    ====================
    @author Dilip Ramírez (https://github.com/dukuo | https://notimetoexplain.co)
    @description        Parse a script from a source, and injects a configuration object named ${keyword} based on the "parameters" array of the script asset if any.

                        If parameters or functions deriving from the configuration object are being used in the script, but no parameters are set, then it succeeds but
                        displays a warning with the missing JSX/JSON matches, and sets all the missing ones to null for a soft fault tolerance at runtime.

                        Example JSON asset declaration:

                        "assets": [
                            {
                                "src": "file:///C:/sample/sampleParamInjection.jsx",
                                "type": "script",
                                "parameters": [
                                    {
                                        "key": "name",
                                        "value": "Dilip"
                                    }
                                ]
                            }
                        ]

                        By default ${keyword} = "NX", so you would use a dynamic variable like NX.name or a function like NX.something(). To change this keyword simply
                        set "keyword" as shown below:

                        "assets": [
                            {
                                "src": "file:///C:/sample/sampleParamInjection.jsx",
                                "type": "script",
                                "keyword": "_settings",
                                "parameters": [
                                    {
                                        "key": "name",
                                        "value": "Dilip"
                                    }
                                ]
                            }
                        ]

                        That way instead of NX.name it would be _settings.name

                        All dynamic parameters used in the script should have a JSX default by stating a local ${keyword} variable like on the example below.

                        Example JSX Script with defaults:

                        {
                            var NX = NX || { name : John }; // Setting default variables.

                            return "Hello " + NX.name;
                        }

                        The code above will output either:
                        a) "Hello John" if no parameter defined on the JSON "parameters" array.
                        b) "Hello NAME" if parameter "name" has a "value" of NAME on the JSON "parameters" array.

                        Example JSX Script without defaults:

                        {
                            return "There are " + NX.beerBottlesAmount + " beer bottles ready to drink!"
                        }

                        The code above will output either:
                        a) "There are null beer bottles ready to drink!" if no parameter defined on the JSON Asset "parameters" array.
                        b) "There are X beer bottles ready to drink!" if parameter "beerBottlesAmount" has a "value" of X on the JSON Asset "parameters" array.

    @param src                 The JSX script
    @param parameters          (Array<Object>) Argument array described in the Asset JSON object inside the Job description
    @param keyword             (String) Name for the exported variable holding configuration parameters. Defaults to NX as in NeXrender.

    @return string             (String) The compiled script with parameter injection outside its original scope to avoid user-defined defaults collision.
*/
const wrapEnhancedScript = ({ dest, parameters = [], keyword = "NX",  ...asset }, jobID, settings) => {
    // Initialization

    // Byte stream from download.js helper. Not to be confused  with src which is the plaintext path to the file.
    var script = fs.readFileSync(dest, 'utf8');

    // Parameter argument injection template literal. See at the end for the final definition.
    var argumentInjection = "";

    // Regular Expression to match all {keyword} occurrences. For example, if keyword==NX then it matches variables such as NX.sample or functions such as NX.call()

    // The following regex is just for reference purposes, since the lookbehind part is not currently working.
    // (NX\.)([a-zA-Z0-9_-]{1,}[\(]?(?(?<=\()\)|[a-zA-Z0-9_\-\(\),.]{1,}\))?)?
    var regex = new RegExp(`(${keyword}\\.)([a-zA-Z0-9_-]{1,}[\\(]?)?`, "gm");

    // Keys of the missing parameters. See below for further explanation.
    var missingMatches = {
        fn: [],
        vars: [],
        needsDefault: [],
    };

    // Helper functions

    /*
        Setup Parameter Injection
        ==========================
        @description            Creates the string initializing a scoped variable with parameters from either the Script Asset JSON configuration or a placeholder array with null values
                                by finding uses of the ${keyword} variable in the JSX script provided that the user didn't define its own default values.
        @param keyword          (String) Keyword to define as the final variable name. Defaults to NX as in NeXrender.
        @param parameters       (Object) Object with the parameters to inject. Defaults to {}
        @param script           (string) JSX Script to inject the variable to.
        @param logger           (Object) Logger to output warning. Defaults to global logger (console)
        @return string          (String) Final template literal to place at the compiled script.
    */

   const setupInjection = (keyword, parameters, script, logger ) => {
    var str = ``;
    var injectedParams = {};

    // Regex to find a local scoped instance of ${keyword}, to avoid overriding local defaults with null values.
    var regx = new RegExp(`(var|const|let) ${keyword}`, "gm");

    // And we finally inject the parameters to the script outside the script scope to avoid conflicts with user-defined defaults.
    // If no parameter is set in the JSON declaration, and no default initialization is defined in the script then we inject an object with nulled missing parameters.
    if(Object.keys(parameters).length > 0) {
        parameters.forEach(p => injectedParams[p.key] = p.value);
        str = `var ${keyword} = ${JSON.stringify(injectedParams)};`;
    } else if( script.match(regx) == null) {
        // Fill with null all the missing arguments currently being used in the JSX script but not defined on the JSON Asset.
        str = `var ${keyword} = ${JSON.stringify(fillObject(missingMatches.needsDefault, injectedParams))}`;
    }

    if(Object.keys(parameters).length == 0) {
        logger.log(`[${jobID}] ${displayAlert(missingMatches, script.match(regx) == null, str)}`);
    }

    return str;
}

    /*
        Generated Placeholder Parameters
        ================================
        @description            Generates placeholder a "parameters" JSON Object based on keys from an array.
        @param keys             (Array) Array of strings.

        @return string          (String) JSON "parameters" object.
    */
    var generatedPlaceholderParameters = (keys = []) => {
        const template = (key) => `
                {
                    "${key}"  :   "null"
                }\n
        `;

        return `
            "parameters" : [
                ${keys.map((k, i) => `${template(k)}${Object.keys(keys).length - 1 != i ? "," : ""}`).join()}
            ]
        `
    }
    /*
        Display Missing Alert
        =====================
        @description              Display a log message if theres any missing parameter set on the JSON configuration but is being referred in the script.

        Parameters:
        @param m                 (Object) Missing Parameters object. See below for its construction. Must have child objects `fn` and `vars`
        @param showJSXWarning    (Boolean) Flag for whether or not to display warning about not initializing variable in JSX script. Defaults to false.
        @param injectionVar      (String) Variable initialized with placeholder values. Defaults to "".

        @return string           (String) The template literal string displaying all the occurences if any.
    */
    var displayAlert = (m, showJSXWarning = false, injectionVar = "") => {
        const areFnMissing = (m.fn != undefined && Object.keys(m.fn).length > 0);
        const areVarsMissing = (m.vars != undefined && Object.keys(m.vars).length > 0);

        return ` -- W A R N I N G --
        The following ${areVarsMissing ? 'variables ' : "" }${areVarsMissing && areFnMissing ? 'and ' : "" }${areFnMissing ? 'functions ' : "" }on the script are not defined in the Asset JSON configuration:
        ${areFnMissing ? `Functions: ${m.fn.join(",")}` : ""}
        ${areVarsMissing ? `Variables: ${m.vars.join(",")}` : ""}

        Please set defaults in your JSX script (see documentation) or copy the following placeholder JSON code snippet and replace the value with your own:

        ${generatedPlaceholderParameters([...m.fn, ...m.vars])}

        ${showJSXWarning ?
        `Additionally, your JSX script has no initialization of the variables/functions above, which can cause it to crash if executed directly in After Effects.
        Copy and paste the following placeholder code snippet and replace the values with your own:`:'\033[A'}

        ${showJSXWarning ?
        injectionVar : '\033[A'
            }
        `
    }

    /*
        Fill Missing Matches
        ====================
        @description            Creates a placeholder array with all matches within the script
        @param keys             (Array) Names of the keys to fill the array. Default = [].
        @param placeholder      (Object) Placeholder array to fill values with default value. Default = {}
        @param defaultValue     (Null) Default value to fill objects with. Default = null

        @return                 (Object) placeholder object with names keys set to null.
    */
   const fillObject = (keys = [], placeholder = {}, defaultValue = null) => {
        keys.forEach( v => placeholder[v] = `${defaultValue}`);
        return placeholder;
   };

    // Parse all occurrences of the usage of NX on the provided script.
    // const nxMatches = Array.from(script.matchAll(regex)); // String.matchAll is available from Node version 12.0.0
    const nxMatches = script.match(regex); // Backwards compatibility (see above)

    if (nxMatches != null ) {
        // Since the current regex catches ocurrences like NX.call() as `NX.call(` we split the matches as either functions or variables for further debugging.
        for( var i = 0; i < Object.keys(nxMatches).length; i++ ) {

            // var nxMatch = nxMatches[Object.keys(nxMatches)[i]][2]; // String.matchAll is available from Node version 12.0.0
            var nxMatch = nxMatches[i].substr(keyword.length + 1); // Backwards compatibility (see above)

            if ( (nxMatch && parameters.length == 0) || parameters.length > 0 && parameters.filter(o => o.key == nxMatch ).length > 0) { // If there's no parameter object set but there are matches we proceed.
                if(nxMatch.slice(-1) == "(") { // It's a function.
                    missingMatches.fn.push(nxMatch.replace("(", "")); // Sanitize match.
                } else {
                    missingMatches.vars.push(nxMatch); // It's a variable/object.
                }
            }
            if( (parameters.filter(o => o.key != nxMatch).length == 0) ) { // If theres a variable on the script but not in the parameters...
                missingMatches.needsDefault.push(nxMatch);  // Set for null filling.
            }
        }
    }

    // If there's anything that's missing, we proceed with the injection.
    if([...missingMatches.fn, ...missingMatches.vars].length > 0) {
        argumentInjection = setupInjection(keyword, parameters, script, settings.logger);
    }

    // Et voilà, winner winner chicken dinner!
    const compiledScript = `(function() {
        ${argumentInjection}
        ${script}
    })();\n`;

    return (compiledScript)

}

module.exports = (job, settings) => {
    settings.logger.log(`[${job.uid}] running script assemble...`);

    const data = [];
    const base = job.workpath;

    job.assets.map(asset => {
        switch (asset.type) {
            case 'video':
            case 'audio':
            case 'image':
                data.push(wrapFootage(asset));
                break;

            case 'data':
                data.push(wrapData(asset));
                break;

            case 'script':
                data.push(wrapEnhancedScript(asset, job.uid, settings));
                break;
        }
    });

    /* write out assembled custom script file in the workpath */
    job.scriptfile = path.join(base, `nexrender-${job.uid}-script.jsx`);
    fs.writeFileSync(job.scriptfile, script
        .replace('/*COMPOSITION*/', job.template.composition)
        .replace('/*USERSCRIPT*/', data.join('\n'))
    );

    return Promise.resolve(job)
}
<commit_msg>Bugfix: Avoidance of `keyword` usage or initialization. Scope: nexrender-core Description: Edge case fix to avoid mismatching the usage of the `keyword` variable on comment lines and blocks.<commit_after>const fs     = require('fs')
const path   = require('path')
const script = require('../assets/nexrender.jsx')

/* helpers */
const escape = str => {
    str = JSON.stringify(str)
    str = str.substring(1, str.length-1)
    str = `'${str.replace(/\'/g, '\\\'')}'`
    return str
}

const selectLayers = ({ composition, layerName, layerIndex }, callbackString) => {
    const method = layerName ? 'selectLayersByName' : 'selectLayersByIndex';
    const compo  = composition === undefined ? 'null' : escape(composition);
    const value  = layerName ? escape(layerName) : layerIndex;

    return (`nexrender.${method}(${compo}, ${value}, ${callbackString});`);
}

const renderIf = (value, string) => {
    const encoded = typeof value == 'string' ? escape(value) : JSON.stringify(value);
    return value === undefined ? '' : string.replace('$value', encoded);
}

const partsOfKeypath = (keypath) => {
    var parts = keypath.split('->');
    return (parts.length === 1) ? keypath.split('.') : parts
}

/* scripting wrappers */
const wrapFootage = ({ dest, ...asset }) => (`(function() {
    ${selectLayers(asset, `function(layer) {
        nexrender.replaceFootage(layer, '${dest.replace(/\\/g, "\\\\")}')
    }`)}
})();\n`)

const wrapData = ({ property, value, expression, ...asset }) => (`(function() {
    ${selectLayers(asset, /* syntax:js */`function(layer) {

        var parts = ${JSON.stringify(partsOfKeypath(property))};
        ${renderIf(value, `var value = { "value": $value }`)}
        ${renderIf(expression, `var value = { "expression": $value }`)}
        nexrender.changeValueForKeypath(layer, parts, value);

        return true;
    }`)}
})();\n`)

// @deprecated in favor of wrapEnhancedScript (implementation below)
const wrapScript = ({ dest }) => (`(function() {
    ${fs.readFileSync(dest, 'utf8')}
})();\n`)

/*
    Wrap Enhanced Script
    ====================
    @author Dilip Ramírez (https://github.com/dukuo | https://notimetoexplain.co)
    @description        Parse a script from a source, and injects a configuration object named ${keyword} based on the "parameters" array of the script asset if any.

                        If parameters or functions deriving from the configuration object are being used in the script, but no parameters are set, then it succeeds but
                        displays a warning with the missing JSX/JSON matches, and sets all the missing ones to null for a soft fault tolerance at runtime.

                        Example JSON asset declaration:

                        "assets": [
                            {
                                "src": "file:///C:/sample/sampleParamInjection.jsx",
                                "type": "script",
                                "parameters": [
                                    {
                                        "key": "name",
                                        "value": "Dilip"
                                    }
                                ]
                            }
                        ]

                        By default ${keyword} = "NX", so you would use a dynamic variable like NX.name or a function like NX.something(). To change this keyword simply
                        set "keyword" as shown below:

                        "assets": [
                            {
                                "src": "file:///C:/sample/sampleParamInjection.jsx",
                                "type": "script",
                                "keyword": "_settings",
                                "parameters": [
                                    {
                                        "key": "name",
                                        "value": "Dilip"
                                    }
                                ]
                            }
                        ]

                        That way instead of NX.name it would be _settings.name

                        All dynamic parameters used in the script should have a JSX default by stating a local ${keyword} variable like on the example below.

                        Example JSX Script with defaults:

                        {
                            var NX = NX || { name : John }; // Setting default variables.

                            return "Hello " + NX.name;
                        }

                        The code above will output either:
                        a) "Hello John" if no parameter defined on the JSON "parameters" array.
                        b) "Hello NAME" if parameter "name" has a "value" of NAME on the JSON "parameters" array.

                        Example JSX Script without defaults:

                        {
                            return "There are " + NX.beerBottlesAmount + " beer bottles ready to drink!"
                        }

                        The code above will output either:
                        a) "There are null beer bottles ready to drink!" if no parameter defined on the JSON Asset "parameters" array.
                        b) "There are X beer bottles ready to drink!" if parameter "beerBottlesAmount" has a "value" of X on the JSON Asset "parameters" array.

                        An example of a compiled script without JSON nor JSX initialization (auto generated null values) would look like the following ( minus the comments )

                        ```
                        (function() {
                            // Generated based on the parameters on the script, with no JSON parameters initialization and no local variable defined.
                            var NX = {"name":"null"};

                            // Original script from jsx file. Pretty much the same as the behaviour before my PR.

                            // Note that this can, and most positively will, crash if executed directly in After Effects. With a local definition of the variable and default
                            // parameters this would be fixed.
                            {
                                // Example of local definition:
                                // var NX = NX || { name : "John" };
                                alert("Hello " + NX.name);
                            }
                            // End of jsx script
                        })();
                        ```

    @param src                 The JSX script
    @param parameters          (Array<Object>)  Argument array described in the Asset JSON object inside the Job description
    @param keyword             (String)         Name for the exported variable holding configuration parameters. Defaults to NX as in NeXrender.

    @return string             (String)         The compiled script with parameter injection outside its original scope to avoid user-defined defaults collision.
*/
const wrapEnhancedScript = ({ dest, parameters = [], keyword = "NX",  ...asset }, jobID, settings) => {
    // Initialization

    // Byte stream from download.js helper. Not to be confused  with src which is the plaintext path to the file.
    var script = fs.readFileSync(dest, 'utf8');

    // Parameter argument injection template literal. See at the end for the final definition.
    var argumentInjection = "";

    // Regular Expression to match all {keyword} occurrences. For example, if keyword==NX then it matches variables such as NX.sample or functions such as NX.call()

    // The following regex is just for reference purposes, since the lookbehind part is not currently working.
    // (NX\.)([a-zA-Z0-9_-]{1,}[\(]?(?(?<=\()\)|[a-zA-Z0-9_\-\(\),.]{1,}\))?)?

    // This regex matches any occurence of the `keyword` usage, except on comments to avoid misinterpretation of code.
    var regex = new RegExp(`(?<!(?:[\\/ ]))(?<!(?:[\\* ]))(?:[ ]){0,}?(${keyword}\\.)([a-zA-Z0-9_-]{1,}[\\(]?)`, "gm");

    // Keys of the missing parameters. See below for further explanation.
    var missingMatches = {
        fn: [],
        vars: [],
        needsDefault: [],
    };

    // Helper functions

    /*
        Setup Parameter Injection
        ==========================
        @description            Creates the string initializing a scoped variable with parameters from either the Script Asset JSON configuration or a placeholder array with null values
                                by finding uses of the ${keyword} variable in the JSX script provided that the user didn't define its own default values.
        @param keyword          (String)    Keyword to define as the final variable name. Defaults to NX as in NeXrender.
        @param parameters       (ArrayzObject>)    Array with the parameters to inject. Defaults to []
        @param script           (string)    JSX Script to inject the variable to.
        @param logger           (Object)    Logger to output warning. Defaults to global logger (console)
        @return string          (String)    Final template literal to place at the compiled script.
    */

   const setupInjection = (keyword, parameters, script, logger ) => {
        var str = ``;
        var injectedParams = {};

        // [ EXPERIMENTAL!! ] See method documentation for more info.
        script = stripCommentsFromScript(script);

        // Regex to find a local scoped instance of ${keyword}, to avoid overriding local defaults with null values.
        var regx = new RegExp(`(?<!(?:[\\/ ]))(?<!(?:[\\* ]))(?:[ ]){0,}?(var|const|let) ${keyword}`, "gm");

        // And we finally inject the parameters to the script outside the script scope to avoid conflicts with user-defined defaults.
        // If no parameter is set in the JSON declaration, and no default initialization is defined in the script then we inject an object with nulled missing parameters.
        if(Object.keys(parameters).length > 0) {
            parameters.forEach(p => injectedParams[p.key] = p.value);
            str = `var ${keyword} = ${JSON.stringify(injectedParams)};`;
        } else if( script.match(regx) == null) {
            // Fill with null all the missing arguments currently being used in the JSX script but not defined on the JSON Asset.
            str = `var ${keyword} = ${JSON.stringify(fillObject(missingMatches.needsDefault, injectedParams))}`;
        }

        if(Object.keys(parameters).length == 0) {
            logger.log(`[${jobID}] ${displayAlert(missingMatches, script.match(regx) == null, str)}`);
        }

        return str;
    }

    /*
        Generated Placeholder Parameters
        ================================
        @description            Generates placeholder for "parameters" JSON Object based on keys from an array.
        @param keys             (Array)     Array of strings. These should be the occurences of `keyword` variable use on the JSX script.

        @return string          (String)    JSON "parameters" object.
    */
    var generatedPlaceholderParameters = (keys = []) => {
        const template = (key) => `
                {
                    "${key}"  :   "null"
                }\n
        `;

        return `
            "parameters" : [
                ${keys.map((k, i) => `${template(k)}${Object.keys(keys).length - 1 != i ? "," : ""}`).join()}
            ]
        `
    }
    /*
        Display Missing Alert
        =====================
        @description              Display a log message if theres any missing parameter set on the JSON configuration but is being referred in the script.

        @param m                 (Object)   Missing Parameters object. See below for its construction. Must have child objects `fn` and `vars`
        @param showJSXWarning    (Boolean)  Flag for whether or not to display warning about not initializing variable in JSX script. Defaults to false.
        @param injectionVar      (String)   Variable initialized with placeholder values. Defaults to "".

        @return string           (String)   The template literal string displaying all the occurences if any.
    */
    var displayAlert = (m, showJSXWarning = false, injectionVar = "") => {
        const areFnMissing = (m.fn != undefined && Object.keys(m.fn).length > 0);
        const areVarsMissing = (m.vars != undefined && Object.keys(m.vars).length > 0);

        return ` -- W A R N I N G --
        The following ${areVarsMissing ? 'variables ' : "" }${areVarsMissing && areFnMissing ? 'and ' : "" }${areFnMissing ? 'functions ' : "" }on the script are not defined in the Asset JSON configuration:
        ${areFnMissing ? `Functions: ${m.fn.join(",")}` : ""}
        ${areVarsMissing ? `Variables: ${m.vars.join(",")}` : ""}

        Please set defaults in your JSX script (see documentation) or copy the following placeholder JSON code snippet and replace the value with your own:

        ${generatedPlaceholderParameters([...m.fn, ...m.vars])}

        ${showJSXWarning ?
        `Additionally, your JSX script has no initialization of the variables/functions above, which can cause it to crash if executed directly in After Effects.
        Copy and paste the following placeholder code snippet and replace the values with your own:`:'\033[A'}

        ${showJSXWarning ?
        injectionVar : '\033[A'
            }
        `
    }

    /*
        Fill Missing Matches
        ====================
        @description            Creates a placeholder array with all matches within the script
        @param keys             (Array)     Names of the keys to fill the array. Default = [].
        @param placeholder      (Object)    Placeholder array to fill values with default value. Default = {}
        @param defaultValue     (Null)      Default value to fill objects with. Default = null

        @return                 (Object) placeholder object with names keys set to null.
    */
   const fillObject = (keys = [], placeholder = {}, defaultValue = null) => {
        keys.forEach( v => placeholder[v] = `${defaultValue}`);
        return placeholder;
   };

   /*
        Strip comment blocks from Script [EXPERIMENTAL]
        ================================
        @description                Removes /* * / comments  from script to avoid mismatching occurrences.
        @param script               (String)            The target script to strip.
        @returns string             (String)            A one-line version of the original script without comment blocks.
   */

   const stripCommentsFromScript = (script) => {
        // Experimental: Strip comment blocks from script to avoid mismatching of commented usage of the `keyword` variable
       // https://levelup.gitconnected.com/advanced-regex-find-and-remove-multi-line-comments-in-your-code-c162ba6e5811
       return script = script.replace(/\n/g, " ").replace(/\/\*.*\*\//g, " ")
       .replace(/\s+/g, " ").trim();
   }

   /*
        Find Missing Matches
        ====================
        @description                RegEx Searches a given JSX script to find occurences
        @param script               (String)            JSX script to find occurences in.
        @param regex                (Object)            RegEx object to match against JSX script.
        @param parameters           (Array<Object>)     Array with the parameters to compare against the matches.
        @return bool                (Boolean)           Whether or not there are any variables to inject. Defaults to false.
   */

   const findMatches = (script, regex, parameters, missingMatches = { fn: [], vars: [], needsDefault: [] }, logger) => {

        // [ EXPERIMENTAL !! ] See method documentation for more info.
        script = stripCommentsFromScript(script);

        // Parse all occurrences of the usage of NX on the provided script.
        // const nxMatches = Array.from(script.matchAll(regex)); // String.matchAll is available from Node version 12.0.0
        const nxMatches = script.match(regex); // Backwards compatibility (see above)

        if (nxMatches != null ) {
            // Since the current regex catches ocurrences like NX.call() as `NX.call(` we split the matches as either functions or variables for further debugging.
            for( var i = 0; i < Object.keys(nxMatches).length; i++ ) {

                // var nxMatch = nxMatches[Object.keys(nxMatches)[i]][2]; // String.matchAll is available from Node version 12.0.0
                var nxMatch = nxMatches[i].replace(" ", '').substr(keyword.length + 1); // Backwards compatibility (see above)

                logger.log(JSON.stringify(nxMatch));
                if ( (nxMatch && parameters.length == 0) || parameters.length > 0 && parameters.filter(o => o.key == nxMatch ).length > 0) { // If there's no parameter object set but there are matches we proceed.
                    if(nxMatch.slice(-1) == "(") { // It's a function.
                        missingMatches.fn.push(nxMatch.replace("(", "")); // Sanitize match.
                    } else {
                        missingMatches.vars.push(nxMatch); // It's a variable/object.
                    }
                }
                if( (parameters.filter(o => o.key != nxMatch).length == 0) ) { // If theres a variable on the script but not in the parameters...
                    missingMatches.needsDefault.push(nxMatch);  // Set for null filling.
                }
            }
        }
        return [...missingMatches.fn, ...missingMatches.vars].length > 0;
   }

    // If there's anything that's missing, we proceed with the injection.
    if(findMatches(script, regex, parameters, missingMatches, settings.logger)) {
        argumentInjection = setupInjection(keyword, parameters, script, settings.logger);
    }

    // Et voilà!
    const compiledScript = `(function() {
        ${argumentInjection}
        ${script}
    })();\n`;

    // Uncomment the following line to preview the compiled script on console.
    // settings.logger.log(compiledScript);

    return (compiledScript)

}

module.exports = (job, settings) => {
    settings.logger.log(`[${job.uid}] running script assemble...`);

    const data = [];
    const base = job.workpath;

    job.assets.map(asset => {
        switch (asset.type) {
            case 'video':
            case 'audio':
            case 'image':
                data.push(wrapFootage(asset));
                break;

            case 'data':
                data.push(wrapData(asset));
                break;

            case 'script':
                data.push(wrapEnhancedScript(asset, job.uid, settings));
                break;
        }
    });

    /* write out assembled custom script file in the workpath */
    job.scriptfile = path.join(base, `nexrender-${job.uid}-script.jsx`);
    fs.writeFileSync(job.scriptfile, script
        .replace('/*COMPOSITION*/', job.template.composition)
        .replace('/*USERSCRIPT*/', data.join('\n'))
    );

    return Promise.resolve(job)
}
<|endoftext|>


<commit_before>"use strict"

var fs = require("fs")
var path = require("path")
var proc = require("child_process")

function read(filepath) {
	try {return fs.readFileSync(filepath, "utf8")} catch (e) {throw new Error("File does not exist: " + filepath)}
}
function isFile(filepath) {
	try {return fs.statSync(filepath).isFile()} catch (e) {return false}
}
function parse(file) {
	var json = read(file)
	try {return JSON.parse(json)} catch (e) {throw new Error("invalid JSON: " + json)}
}

function run(input, output) {
	try {
		var modules = {}
		var bindings = {}
		var declaration = /^\s*(?:var|let|const|function)[\t ]+([\w_$]+)/gm
		var include = /(?:((?:var|let|const|,|)[\t ]*)([\w_$\.]+)(\s*=\s*))?require\(([^\)]+)\)(\s*[`\.\(\[])?/gm
		var uuid = 0
		var process = function(filepath, data) {
			data.replace(declaration, function(match, binding) {bindings[binding] = 0})

			return data.replace(include, function(match, def, variable, eq, dep, rest) {
				var filename = new Function("return " + dep).call(), pre = ""

				def = def || "", variable = variable || "", eq = eq || "", rest = rest || ""
				if (def[0] === ",") def = "\nvar ", pre = "\n"
				var dependency = resolve(filepath, filename)
				var code = process(dependency, pre + (modules[dependency] == null ? exportCode(filename, dependency, def, variable, eq, rest, uuid) : def + variable + eq + modules[dependency]))
				modules[dependency] = rest ? "_" + uuid : variable
				uuid++
				return code + rest
			})
		}

		var resolve = function(filepath, filename) {
			if (filename[0] !== ".") {
				// resolve as npm dependency
				var packagePath = "./node_modules/" + filename + "/package.json"
				var meta = isFile(packagePath) ? parse(packagePath) : {}
				var main = "./node_modules/" + filename + "/" + (meta.main || filename + ".js")
				return path.resolve(isFile(main) ? main : "./node_modules/" + filename + "/index.js")
			}
			else {
				// resolve as local dependency
				return path.resolve(path.dirname(filepath), filename + ".js")
			}
		}

		var exportCode = function(filename, filepath, def, variable, eq, rest, uuid) {
			var code = read(filepath)
			// if there's a syntax error, report w/ proper stack trace
			try {new Function(code)} catch (e) {
				proc.exec("node " + filename, function(error) {
					if (error !== null) console.log("\x1b[31m" + error.message)
				})
			}

			// disambiguate collisions
			var ignored = {}
			code.replace(include, function(match, def, variable, eq, dep) {
				var filename = new Function("return " + dep).call()
				var binding = modules[resolve(filepath, filename)]
				if (binding != null) ignored[binding] = true
			})
			if (code.match(new RegExp("module\\.exports\\s*=\\s*" + variable + "\s*$", "m"))) ignored[variable] = true
			for (var binding in bindings) {
				if (!ignored[binding]) {
					var before = code
					code = code.replace(new RegExp("(\\b)" + binding + "\\b", "g"), binding + bindings[binding])
					if (before !== code) bindings[binding]++
				}
			}

			// fix strings that got mangled by collision disambiguation
			var string = /(["'])((?:\\\1|.)*?)(\1)/g
			var candidates = Object.keys(bindings).map(function(binding) {return binding + (bindings[binding] - 1)}).join("|")
			code = code.replace(string, function(match, open, data, close) {
				var variables = new RegExp(Object.keys(bindings).map(function(binding) {return binding + (bindings[binding] - 1)}).join("|"), "g")
				var fixed = data.replace(variables, function(match) {
					return match.replace(/\d+$/, "")
				})
				return open + fixed + close
			})

			//fix props
			var props = new RegExp("(\\.\\s*)(" + candidates + ")|([\\{,]\\s*)(" + candidates + ")(\\s*:)", "gm")
			code = code.replace(props, function(match, dot, a, pre, b, post) {
				if (dot) return dot + a.replace(/\d+$/, "")
				else return pre + b.replace(/\d+$/, "") + post
			})

			return code
				.replace(/("|')use strict\1;?/gm, "") // remove extraneous "use strict"
				.replace(/module\.exports\s*=\s*/gm, rest ? "var _" + uuid + eq : def + (rest ? "_" : "") + variable + eq) // export
				+ (rest ? "\n" + def + variable + eq + "_" + uuid : "") // if `rest` is truthy, it means the expression is fluent or higher-order (e.g. require(path).foo or require(path)(foo)
		}

		var versionTag = "bleeding-edge"
		var packageFile = __dirname + "/../package.json"
		var code = process(path.resolve(input), read(input))
			.replace(/^\s*((?:var|let|const|)[\t ]*)([\w_$\.]+)(\s*=\s*)(\2)(?=[\s]+(\w)|;|$)/gm, "") // remove assignments to self
			.replace(/;+(\r|\n|$)/g, ";$1") // remove redundant semicolons
			.replace(/(\r|\n)+/g, "\n").replace(/(\r|\n)$/, "") // remove multiline breaks
			.replace(versionTag, isFile(packageFile) ? parse(packageFile).version : versionTag) // set version

		fs.writeFileSync(output, "new function() {\n" + code + "\n}", "utf8")
	}
	catch (e) {
		console.error(e.message)
	}
}

module.exports = function(input, output, options) {
	run(input, output)
	if (options && options.watch) {
		fs.watch(process.cwd(), {recursive: true}, function(file) {
			if (typeof file === "string" && path.resolve(output) !== path.resolve(file)) run(input, output)
		})
	}
}
<commit_msg>don re-emit if not needed<commit_after>"use strict"

var fs = require("fs")
var path = require("path")
var proc = require("child_process")

function read(filepath) {
	try {return fs.readFileSync(filepath, "utf8")} catch (e) {throw new Error("File does not exist: " + filepath)}
}
function isFile(filepath) {
	try {return fs.statSync(filepath).isFile()} catch (e) {return false}
}
function parse(file) {
	var json = read(file)
	try {return JSON.parse(json)} catch (e) {throw new Error("invalid JSON: " + json)}
}

function run(input, output) {
	try {
		var modules = {}
		var bindings = {}
		var declaration = /^\s*(?:var|let|const|function)[\t ]+([\w_$]+)/gm
		var include = /(?:((?:var|let|const|,|)[\t ]*)([\w_$\.]+)(\s*=\s*))?require\(([^\)]+)\)(\s*[`\.\(\[])?/gm
		var uuid = 0
		var process = function(filepath, data) {
			data.replace(declaration, function(match, binding) {bindings[binding] = 0})

			return data.replace(include, function(match, def, variable, eq, dep, rest) {
				var filename = new Function("return " + dep).call(), pre = ""

				def = def || "", variable = variable || "", eq = eq || "", rest = rest || ""
				if (def[0] === ",") def = "\nvar ", pre = "\n"
				var dependency = resolve(filepath, filename)
				var code = process(dependency, pre + (modules[dependency] == null ? exportCode(filename, dependency, def, variable, eq, rest, uuid) : def + variable + eq + modules[dependency]))
				modules[dependency] = rest ? "_" + uuid : variable
				uuid++
				return code + rest
			})
		}

		var resolve = function(filepath, filename) {
			if (filename[0] !== ".") {
				// resolve as npm dependency
				var packagePath = "./node_modules/" + filename + "/package.json"
				var meta = isFile(packagePath) ? parse(packagePath) : {}
				var main = "./node_modules/" + filename + "/" + (meta.main || filename + ".js")
				return path.resolve(isFile(main) ? main : "./node_modules/" + filename + "/index.js")
			}
			else {
				// resolve as local dependency
				return path.resolve(path.dirname(filepath), filename + ".js")
			}
		}

		var exportCode = function(filename, filepath, def, variable, eq, rest, uuid) {
			var code = read(filepath)
			// if there's a syntax error, report w/ proper stack trace
			try {new Function(code)} catch (e) {
				proc.exec("node " + filename, function(error) {
					if (error !== null) console.log("\x1b[31m" + error.message)
				})
			}

			// disambiguate collisions
			var ignored = {}
			code.replace(include, function(match, def, variable, eq, dep) {
				var filename = new Function("return " + dep).call()
				var binding = modules[resolve(filepath, filename)]
				if (binding != null) ignored[binding] = true
			})
			if (code.match(new RegExp("module\\.exports\\s*=\\s*" + variable + "\s*$", "m"))) ignored[variable] = true
			for (var binding in bindings) {
				if (!ignored[binding]) {
					var before = code
					code = code.replace(new RegExp("(\\b)" + binding + "\\b", "g"), binding + bindings[binding])
					if (before !== code) bindings[binding]++
				}
			}

			// fix strings that got mangled by collision disambiguation
			var string = /(["'])((?:\\\1|.)*?)(\1)/g
			var candidates = Object.keys(bindings).map(function(binding) {return binding + (bindings[binding] - 1)}).join("|")
			code = code.replace(string, function(match, open, data, close) {
				var variables = new RegExp(Object.keys(bindings).map(function(binding) {return binding + (bindings[binding] - 1)}).join("|"), "g")
				var fixed = data.replace(variables, function(match) {
					return match.replace(/\d+$/, "")
				})
				return open + fixed + close
			})

			//fix props
			var props = new RegExp("(\\.\\s*)(" + candidates + ")|([\\{,]\\s*)(" + candidates + ")(\\s*:)", "gm")
			code = code.replace(props, function(match, dot, a, pre, b, post) {
				if (dot) return dot + a.replace(/\d+$/, "")
				else return pre + b.replace(/\d+$/, "") + post
			})

			return code
				.replace(/("|')use strict\1;?/gm, "") // remove extraneous "use strict"
				.replace(/module\.exports\s*=\s*/gm, rest ? "var _" + uuid + eq : def + (rest ? "_" : "") + variable + eq) // export
				+ (rest ? "\n" + def + variable + eq + "_" + uuid : "") // if `rest` is truthy, it means the expression is fluent or higher-order (e.g. require(path).foo or require(path)(foo)
		}

		var versionTag = "bleeding-edge"
		var packageFile = __dirname + "/../package.json"
		var code = process(path.resolve(input), read(input))
			.replace(/^\s*((?:var|let|const|)[\t ]*)([\w_$\.]+)(\s*=\s*)(\2)(?=[\s]+(\w)|;|$)/gm, "") // remove assignments to self
			.replace(/;+(\r|\n|$)/g, ";$1") // remove redundant semicolons
			.replace(/(\r|\n)+/g, "\n").replace(/(\r|\n)$/, "") // remove multiline breaks
			.replace(versionTag, isFile(packageFile) ? parse(packageFile).version : versionTag) // set version

		code = "new function() {\n" + code + "\n}"
		if (code !== read(output)) fs.writeFileSync(output, code, "utf8")
	}
	catch (e) {
		console.error(e.message)
	}
}

module.exports = function(input, output, options) {
	run(input, output)
	if (options && options.watch) {
		fs.watch(process.cwd(), {recursive: true}, function(file) {
			if (typeof file === "string" && path.resolve(output) !== path.resolve(file)) run(input, output)
		})
	}
}
<|endoftext|>


<commit_before>var _ = require("lodash");
var λ = require("contra");
var DB = require("./DB");
var getArg = require("./getArg");
var Future = require("fibers/future");
var modules = require("./modules");
var krl_stdlib = require("krl-stdlib");
var KRLClosure = require("./KRLClosure");
var SymbolTable = require("symbol-table");
var applyInFiber = require("./applyInFiber");
var EventEmitter = require("events");
var evalRuleInFiber = require("./evalRuleInFiber");
var selectRulesToEvalFuture = Future.wrap(require("./selectRulesToEval"));

module.exports = function(conf){
  var db = Future.wrap(DB(conf.db));
  var compileAndLoadRuleset = conf.compileAndLoadRuleset;

  var rulesets = {};
  var salience_graph = {};

  var emitter = new EventEmitter();

  var mkCTX = function(ctx){
    ctx.db = db;
    ctx.getArg = getArg;
    ctx.modules = modules;
    ctx.KRLClosure = KRLClosure;
    ctx.emit = function(type, val, message){//for stdlib
      var info = {rid: ctx.rid};
      if(ctx.event){
        info.event = {
          eci: ctx.event.eci,
          eid: ctx.event.eid,
          domain: ctx.event.domain,
          type: ctx.event.type,
        };
      }
      emitter.emit(type, info, val, message);
    };
    ctx.callKRLstdlib = function(fn_name){
      var args = _.toArray(arguments);
      args[0] = ctx;
      return krl_stdlib[fn_name].apply(void 0, args);
    };
    return ctx;
  };

  var installRulesetInFiber = function(rs){
    rs.scope = SymbolTable();
    var ctx = mkCTX({
      scope: rs.scope
    });
    if(_.isFunction(rs.meta && rs.meta.configure)){
      rs.meta.configure(ctx);
    }
    if(_.isFunction(rs.global)){
      rs.global(ctx);
    }
    rs.modules_used = {};
    _.each(rs.meta && rs.meta.use, function(use){
      if(use.kind !== "module"){
        throw new Error("Unsupported 'use' kind: " + use.kind);
      }
      if(!_.has(rulesets, use.rid)){
        throw new Error("Dependant module not loaded: " + use.rid);
      }
      var dep_rs = rulesets[use.rid];
      var ctx = mkCTX({
        scope: SymbolTable()//or dep_rs.scope.push() ??? TODO
      });
      if(_.isFunction(dep_rs.meta && dep_rs.meta.configure)){
        dep_rs.meta.configure(ctx);
      }
      if(_.isFunction(use["with"])){
        use["with"](ctx);
      }
      if(_.isFunction(dep_rs.global)){
        dep_rs.global(ctx);
      }
      rs.modules_used[use.alias] = {
        rid: use.rid,
        scope: ctx.scope,
        provides: dep_rs.meta.provides
      };
    });
    _.each(rs.rules, function(rule){
      rule.rid = rs.rid;

      _.each(rule.select && rule.select.graph, function(g, domain){
        _.each(g, function(exprs, type){
          _.set(salience_graph, [domain, type, rule.rid, rule.name], true);
        });
      });
    });
    rulesets[rs.rid] = rs;
  };

  var installRuleset = function(rs, callback){
    applyInFiber(installRulesetInFiber, null, [rs], callback);
  };

  var getRulesetForRID = function(rid, callback){
    if(conf._dont_check_enabled_before_installing){//for testing
      compileAndLoadRuleset({rid: rid}, callback);
      return;
    }
    db.getEnableRuleset(rid, function(err, data){
      if(err) return callback(err);
      compileAndLoadRuleset({
        rid: rid,
        src: data.src,
        hash: data.hash
      }, function(err, rs){
        if(err){
          db.disableRuleset(rid, function(){
            callback(err);
          });
          return;
        }
        callback(void 0, rs);
      });
    });
  };

  var installRID = function(rid, callback){
    getRulesetForRID(rid, function(err, rs){
      if(err) return callback(err);
      installRuleset(rs, callback);
    });
  };

  //TODO standard startup-phase
  db.getAllEnableRulesets(function(err, rids){
    if(err){
      throw err;//TODO handle this somehow?
    }
    λ.map(rids, getRulesetForRID, function(err, rs_list){
      if(err){
        throw err;//TODO handle this somehow?
      }
      //TODO load in order of dependancies? or simply index all then install?
      _.each(rs_list, function(rs){
        installRuleset(rs, function(err){
          if(err){
            throw err;//TODO handle this somehow?
          }
        });
      });
    });
  });

  var signalEventInFiber = function(event){
    event.timestamp = new Date();

    var ctx = mkCTX({
      engine: engine,
      event: event
    });

    ctx.emit("debug", "event recieved");

    ctx.pico = db.getPicoByECIFuture(event.eci).wait();
    if(!ctx.pico){
      throw new Error("Invalid eci: " + event.eci);
    }

    ctx.emit("debug", "pico selected");

    var rules = selectRulesToEvalFuture(ctx, salience_graph, rulesets).wait();
    var responses = _.map(rules, function(rule){

      ctx.emit("debug", "rule selected: " + rule.rid + " -> " + rule.name);

      ctx.rid = rule.rid;
      ctx.rule = rule;
      ctx.scope = rule.scope;
      if(_.has(rulesets, rule.rid)){
        ctx.modules_used = rulesets[rule.rid].modules_used;
      }

      return evalRuleInFiber(rule, ctx);
    });

    var res_by_type = _.groupBy(_.flattenDeep(_.values(responses)), "type");

    //TODO other types
    return {
      directives:  _.map(res_by_type.directive, function(d){
        return _.omit(d, "type");
      })
    };
  };

  var signalEvent = function(event, callback){
    applyInFiber(signalEventInFiber, void 0, [event], callback);
  };

  var engine = Future.wrap({
    installRID: installRID,
    signalEvent: signalEvent
  });

  return {
    db: db,
    emitter: emitter,
    isInstalled: function(rid){
      return _.has(rulesets, rid);
    },
    installRID: installRID,
    signalEvent: signalEvent,
    runQuery: function(query, callback){
      db.getPicoByECI(query.eci, function(err, pico){
        if(err) return callback(err);
        if(!pico){
          return callback(new Error("Bad eci"));
        }
        if(!_.has(pico.ruleset, query.rid)){
          return callback(new Error("Pico does not have that rid"));
        }
        if(!_.has(rulesets, query.rid)){
          return callback(new Error("Not found: rid"));
        }
        var rs = rulesets[query.rid];
        var shares = _.get(rs, ["meta", "shares"]);
        if(!_.isArray(shares) || !_.includes(shares, query.name)){
          return callback(new Error("Not shared"));
        }
        if(!rs.scope.has(query.name)){
          //TODO throw -or- nil????
          return callback(new Error("Shared, but not defined: " + query.name));
        }

        ////////////////////////////////////////////////////////////////////////
        var ctx = mkCTX({
          rid: rs.rid,
          pico: pico,
          engine: engine,
          modules_used: rs.modules_used,
          scope: rs.scope
        });

        var val = ctx.scope.get(query.name);
        if(_.isFunction(val)){
          applyInFiber(val, null, [ctx, query.args], function(err, resp){
            if(err) return callback(err);
            callback(undefined, resp);
          });
        }else{
          callback(undefined, val);
        }
      });
    }
  };
};
<commit_msg>cleanup<commit_after>var _ = require("lodash");
var λ = require("contra");
var DB = require("./DB");
var getArg = require("./getArg");
var Future = require("fibers/future");
var modules = require("./modules");
var krl_stdlib = require("krl-stdlib");
var KRLClosure = require("./KRLClosure");
var SymbolTable = require("symbol-table");
var applyInFiber = require("./applyInFiber");
var EventEmitter = require("events");
var evalRuleInFiber = require("./evalRuleInFiber");
var selectRulesToEvalFuture = Future.wrap(require("./selectRulesToEval"));

module.exports = function(conf){
  var db = Future.wrap(DB(conf.db));
  var compileAndLoadRuleset = conf.compileAndLoadRuleset;

  var rulesets = {};
  var salience_graph = {};

  var emitter = new EventEmitter();

  var mkCTX = function(ctx){
    ctx.db = db;
    ctx.getArg = getArg;
    ctx.modules = modules;
    ctx.KRLClosure = KRLClosure;
    ctx.emit = function(type, val, message){//for stdlib
      var info = {rid: ctx.rid};
      if(ctx.event){
        info.event = {
          eci: ctx.event.eci,
          eid: ctx.event.eid,
          domain: ctx.event.domain,
          type: ctx.event.type,
        };
      }
      emitter.emit(type, info, val, message);
    };
    ctx.callKRLstdlib = function(fn_name){
      var args = _.toArray(arguments);
      args[0] = ctx;
      return krl_stdlib[fn_name].apply(void 0, args);
    };
    return ctx;
  };

  var installRulesetInFiber = function(rs){
    rs.scope = SymbolTable();
    var ctx = mkCTX({
      scope: rs.scope
    });
    if(_.isFunction(rs.meta && rs.meta.configure)){
      rs.meta.configure(ctx);
    }
    if(_.isFunction(rs.global)){
      rs.global(ctx);
    }
    rs.modules_used = {};
    _.each(rs.meta && rs.meta.use, function(use){
      if(use.kind !== "module"){
        throw new Error("Unsupported 'use' kind: " + use.kind);
      }
      if(!_.has(rulesets, use.rid)){
        throw new Error("Dependant module not loaded: " + use.rid);
      }
      var dep_rs = rulesets[use.rid];
      var ctx = mkCTX({
        scope: SymbolTable()//or dep_rs.scope.push() ??? TODO
      });
      if(_.isFunction(dep_rs.meta && dep_rs.meta.configure)){
        dep_rs.meta.configure(ctx);
      }
      if(_.isFunction(use["with"])){
        use["with"](ctx);
      }
      if(_.isFunction(dep_rs.global)){
        dep_rs.global(ctx);
      }
      rs.modules_used[use.alias] = {
        rid: use.rid,
        scope: ctx.scope,
        provides: dep_rs.meta.provides
      };
    });
    _.each(rs.rules, function(rule){
      rule.rid = rs.rid;

      _.each(rule.select && rule.select.graph, function(g, domain){
        _.each(g, function(exprs, type){
          _.set(salience_graph, [domain, type, rule.rid, rule.name], true);
        });
      });
    });
    rulesets[rs.rid] = rs;
  };

  var installRuleset = function(rs, callback){
    applyInFiber(installRulesetInFiber, null, [rs], callback);
  };

  var getRulesetForRID = function(rid, callback){
    if(conf._dont_check_enabled_before_installing){//for testing
      compileAndLoadRuleset({rid: rid}, callback);
      return;
    }
    db.getEnableRuleset(rid, function(err, data){
      if(err) return callback(err);
      compileAndLoadRuleset({
        rid: rid,
        src: data.src,
        hash: data.hash
      }, function(err, rs){
        if(err){
          db.disableRuleset(rid, function(){
            callback(err);
          });
          return;
        }
        callback(void 0, rs);
      });
    });
  };

  var installRID = function(rid, callback){
    getRulesetForRID(rid, function(err, rs){
      if(err) return callback(err);
      installRuleset(rs, callback);
    });
  };

  var installAllEnableRulesets = function(callback){
    db.getAllEnableRulesets(function(err, rids){
      if(err)return callback(err);
      λ.map(rids, getRulesetForRID, function(err, rs_list){
        if(err)return callback(err);
        //TODO load in order of dependancies? or simply index all then install?
        λ.each(rs_list, installRuleset, callback);
      });
    });
  };

  //TODO standard startup-phase
  installAllEnableRulesets(function(err){
    if(err){
      throw err;//TODO handle this somehow?
    }
  });

  var signalEventInFiber = function(event){
    event.timestamp = new Date();

    var ctx = mkCTX({
      engine: engine,
      event: event
    });

    ctx.emit("debug", "event recieved");

    ctx.pico = db.getPicoByECIFuture(event.eci).wait();
    if(!ctx.pico){
      throw new Error("Invalid eci: " + event.eci);
    }

    ctx.emit("debug", "pico selected");

    var rules = selectRulesToEvalFuture(ctx, salience_graph, rulesets).wait();
    var responses = _.map(rules, function(rule){

      ctx.emit("debug", "rule selected: " + rule.rid + " -> " + rule.name);

      ctx.rid = rule.rid;
      ctx.rule = rule;
      ctx.scope = rule.scope;
      if(_.has(rulesets, rule.rid)){
        ctx.modules_used = rulesets[rule.rid].modules_used;
      }

      return evalRuleInFiber(rule, ctx);
    });

    var res_by_type = _.groupBy(_.flattenDeep(_.values(responses)), "type");

    //TODO other types
    return {
      directives:  _.map(res_by_type.directive, function(d){
        return _.omit(d, "type");
      })
    };
  };

  var signalEvent = function(event, callback){
    applyInFiber(signalEventInFiber, void 0, [event], callback);
  };

  var engine = Future.wrap({
    installRID: installRID,
    signalEvent: signalEvent
  });

  return {
    db: db,
    emitter: emitter,
    isInstalled: function(rid){
      return _.has(rulesets, rid);
    },
    installRID: installRID,
    signalEvent: signalEvent,
    runQuery: function(query, callback){
      db.getPicoByECI(query.eci, function(err, pico){
        if(err) return callback(err);
        if(!pico){
          return callback(new Error("Bad eci"));
        }
        if(!_.has(pico.ruleset, query.rid)){
          return callback(new Error("Pico does not have that rid"));
        }
        if(!_.has(rulesets, query.rid)){
          return callback(new Error("Not found: rid"));
        }
        var rs = rulesets[query.rid];
        var shares = _.get(rs, ["meta", "shares"]);
        if(!_.isArray(shares) || !_.includes(shares, query.name)){
          return callback(new Error("Not shared"));
        }
        if(!rs.scope.has(query.name)){
          //TODO throw -or- nil????
          return callback(new Error("Shared, but not defined: " + query.name));
        }

        ////////////////////////////////////////////////////////////////////////
        var ctx = mkCTX({
          rid: rs.rid,
          pico: pico,
          engine: engine,
          modules_used: rs.modules_used,
          scope: rs.scope
        });

        var val = ctx.scope.get(query.name);
        if(_.isFunction(val)){
          applyInFiber(val, null, [ctx, query.args], function(err, resp){
            if(err) return callback(err);
            callback(undefined, resp);
          });
        }else{
          callback(undefined, val);
        }
      });
    }
  };
};
<|endoftext|>


<commit_before>import React, { useEffect, useReducer } from 'react';
import axios from 'axios';
import { camelCase, get, omit, upperFirst, pick } from 'lodash';
import { Redirect, useRouteMatch, useHistory } from 'react-router-dom';
import { auth, useQuery } from 'strapi-helper-plugin';
import { Padded } from '@buffetjs/core';
import PropTypes from 'prop-types';
import BaselineAlignment from '../../components/BaselineAlignement';
import NavTopRightWrapper from '../../components/NavTopRightWrapper';
import PageTitle from '../../components/PageTitle';
import LocaleToggle from '../LocaleToggle';
import checkFormValidity from '../../utils/checkFormValidity';
import formatAPIErrors from '../../utils/formatAPIErrors';
import { forms } from './utils';
import init from './init';
import { initialState, reducer } from './reducer';

const AuthPage = ({ hasAdmin }) => {
  const { push } = useHistory();
  const {
    params: { authType },
  } = useRouteMatch('/auth/:authType');
  const query = useQuery();
  const registrationToken = query.get('registrationToken');
  const { Component, endPoint, fieldsToDisable, fieldsToOmit, inputsPrefix, schema } = get(
    forms,
    authType,
    {}
  );
  const [{ formErrors, modifiedData, requestError }, dispatch] = useReducer(
    reducer,
    initialState,
    init
  );
  const CancelToken = axios.CancelToken;
  const source = CancelToken.source();

  useEffect(() => {
    // Cancel request on unmount
    return () => {
      source.cancel('Component unmounted');
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Reset the state on navigation change
  useEffect(() => {
    dispatch({
      type: 'RESET_PROPS',
    });
  }, [authType]);

  useEffect(() => {
    if (authType === 'register') {
      const getData = async () => {
        try {
          const {
            data: { data },
          } = await axios.get(
            `${strapi.backendURL}/admin/registration-info?registrationToken=${registrationToken}`
          );

          dispatch({
            type: 'SET_DATA',
            data: { registrationToken, userInfo: data },
          });
        } catch (err) {
          const errorMessage = get(err, ['response', 'data', 'message'], 'An error occured');

          strapi.notification.error(errorMessage);

          // Redirect to the oops page in case of an invalid token
          // @alexandrebodin @JAB I am not sure it is the wanted behavior
          push(`/auth/oops?info=${encodeURIComponent(errorMessage)}`);
        }
      };

      getData();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [authType]);

  const handleChange = ({ target: { name, value } }) => {
    dispatch({
      type: 'ON_CHANGE',
      keys: name,
      value,
    });
  };

  const handleSubmit = async e => {
    e.preventDefault();

    dispatch({
      type: 'SET_ERRORS',
      errors: {},
    });

    const errors = await checkFormValidity(modifiedData, schema);

    dispatch({
      type: 'SET_ERRORS',
      errors: errors || {},
    });

    if (!errors) {
      const body = omit(modifiedData, fieldsToOmit);
      const requestURL = `/admin/${endPoint}`;

      if (authType === 'login') {
        await loginRequest(body, requestURL);
      }

      if (authType === 'register' || authType === 'register-admin') {
        await registerRequest(body, requestURL);
      }

      if (authType === 'forgot-password') {
        await forgotPasswordRequest(body, requestURL);
      }

      if (authType === 'reset-password') {
        await resetPasswordRequest(body, requestURL);
      }
    }
  };

  const forgotPasswordRequest = async (body, requestURL) => {
    try {
      await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: body,
        cancelToken: source.token,
      });

      push('/auth/forgot-password-success');
    } catch (err) {
      console.error(err);

      strapi.notification.error('notification.error');
    }
  };

  const loginRequest = async (body, requestURL) => {
    try {
      const {
        data: {
          data: { token, user },
        },
      } = await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: body,
        cancelToken: source.token,
      });

      auth.setToken(token, modifiedData.rememberMe);
      auth.setUserInfo(user, modifiedData.rememberMe);

      push('/');
    } catch (err) {
      if (err.response) {
        const errorMessage = get(err, ['response', 'data', 'message'], 'Something went wrong');
        const errorStatus = get(err, ['response', 'data', 'statusCode'], 400);

        if (camelCase(errorMessage).toLowerCase() === 'usernotactive') {
          push('/auth/oops');

          dispatch({
            type: 'RESET_PROPS',
          });

          return;
        }

        dispatch({
          type: 'SET_REQUEST_ERROR',
          errorMessage,
          errorStatus,
        });
      }
    }
  };

  const registerRequest = async (body, requestURL) => {
    try {
      const {
        data: {
          data: { token, user },
        },
      } = await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: body,
        cancelToken: source.token,
      });

      auth.setToken(token, false);
      auth.setUserInfo(user, false);

      if (
        (authType === 'register' && modifiedData.userInfo.news === true) ||
        (authType === 'register-admin' && modifiedData.news === true)
      ) {
        axios({
          method: 'POST',
          url: 'https://analytics.strapi.io/register',
          data: pick(modifiedData, ['userInfo.email', 'userInfo.firstname']),
        });
      }
      // Redirect to the homePage
      push('/');
    } catch (err) {
      if (err.response) {
        const { data } = err.response;
        const apiErrors = formatAPIErrors(data);

        dispatch({
          type: 'SET_ERRORS',
          errors: apiErrors,
        });
      }
    }
  };

  const resetPasswordRequest = async (body, requestURL) => {
    try {
      const {
        data: {
          data: { token, user },
        },
      } = await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: { ...body, resetPasswordToken: query.get('code') },
        cancelToken: source.token,
      });

      auth.setToken(token, false);
      auth.setUserInfo(user, false);

      // Redirect to the homePage
      push('/');
    } catch (err) {
      if (err.response) {
        const errorMessage = get(err, ['response', 'data', 'message'], 'Something went wrong');
        const errorStatus = get(err, ['response', 'data', 'statusCode'], 400);

        dispatch({
          type: 'SET_REQUEST_ERROR',
          errorMessage,
          errorStatus,
        });
      }
    }
  };

  // Redirect the user to the login page if the endpoint does not exist
  if (!forms[authType]) {
    return <Redirect to="/" />;
  }

  // Redirect the user to the login page if there is already an admin user
  if (hasAdmin && authType === 'register-admin') {
    return <Redirect to="/" />;
  }

  // Redirect the user to the register-admin if it is the first user
  if (!hasAdmin && authType !== 'register-admin') {
    return <Redirect to="/auth/register-admin" />;
  }

  // Redirect the user to the homepage if he is logged in
  if (auth.getToken()) {
    return <Redirect to="/" />;
  }

  return (
    <>
      <Padded bottom size="md">
        <PageTitle title={upperFirst(authType)} />
        <NavTopRightWrapper>
          <LocaleToggle isLogged className="localeDropdownMenuNotLogged" />
        </NavTopRightWrapper>
        <BaselineAlignment top size="78px">
          <Component
            fieldsToDisable={fieldsToDisable}
            formErrors={formErrors}
            inputsPrefix={inputsPrefix}
            modifiedData={modifiedData}
            onChange={handleChange}
            onSubmit={handleSubmit}
            requestError={requestError}
          />
        </BaselineAlignment>
      </Padded>
    </>
  );
};

AuthPage.defaultProps = {
  hasAdmin: false,
};

AuthPage.propTypes = {
  hasAdmin: PropTypes.bool,
};

export default AuthPage;
<commit_msg>Fix analytics registration bad info<commit_after>import React, { useEffect, useReducer } from 'react';
import axios from 'axios';
import { camelCase, get, omit, upperFirst } from 'lodash';
import { Redirect, useRouteMatch, useHistory } from 'react-router-dom';
import { auth, useQuery } from 'strapi-helper-plugin';
import { Padded } from '@buffetjs/core';
import PropTypes from 'prop-types';
import BaselineAlignment from '../../components/BaselineAlignement';
import NavTopRightWrapper from '../../components/NavTopRightWrapper';
import PageTitle from '../../components/PageTitle';
import LocaleToggle from '../LocaleToggle';
import checkFormValidity from '../../utils/checkFormValidity';
import formatAPIErrors from '../../utils/formatAPIErrors';
import { forms } from './utils';
import init from './init';
import { initialState, reducer } from './reducer';

const AuthPage = ({ hasAdmin }) => {
  const { push } = useHistory();
  const {
    params: { authType },
  } = useRouteMatch('/auth/:authType');
  const query = useQuery();
  const registrationToken = query.get('registrationToken');
  const { Component, endPoint, fieldsToDisable, fieldsToOmit, inputsPrefix, schema } = get(
    forms,
    authType,
    {}
  );
  const [{ formErrors, modifiedData, requestError }, dispatch] = useReducer(
    reducer,
    initialState,
    init
  );
  const CancelToken = axios.CancelToken;
  const source = CancelToken.source();

  useEffect(() => {
    // Cancel request on unmount
    return () => {
      source.cancel('Component unmounted');
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Reset the state on navigation change
  useEffect(() => {
    dispatch({
      type: 'RESET_PROPS',
    });
  }, [authType]);

  useEffect(() => {
    if (authType === 'register') {
      const getData = async () => {
        try {
          const {
            data: { data },
          } = await axios.get(
            `${strapi.backendURL}/admin/registration-info?registrationToken=${registrationToken}`
          );

          dispatch({
            type: 'SET_DATA',
            data: { registrationToken, userInfo: data },
          });
        } catch (err) {
          const errorMessage = get(err, ['response', 'data', 'message'], 'An error occured');

          strapi.notification.error(errorMessage);

          // Redirect to the oops page in case of an invalid token
          // @alexandrebodin @JAB I am not sure it is the wanted behavior
          push(`/auth/oops?info=${encodeURIComponent(errorMessage)}`);
        }
      };

      getData();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [authType]);

  const handleChange = ({ target: { name, value } }) => {
    dispatch({
      type: 'ON_CHANGE',
      keys: name,
      value,
    });
  };

  const handleSubmit = async e => {
    e.preventDefault();

    dispatch({
      type: 'SET_ERRORS',
      errors: {},
    });

    const errors = await checkFormValidity(modifiedData, schema);

    dispatch({
      type: 'SET_ERRORS',
      errors: errors || {},
    });

    if (!errors) {
      const body = omit(modifiedData, fieldsToOmit);
      const requestURL = `/admin/${endPoint}`;

      if (authType === 'login') {
        await loginRequest(body, requestURL);
      }

      if (authType === 'register' || authType === 'register-admin') {
        await registerRequest(body, requestURL);
      }

      if (authType === 'forgot-password') {
        await forgotPasswordRequest(body, requestURL);
      }

      if (authType === 'reset-password') {
        await resetPasswordRequest(body, requestURL);
      }
    }
  };

  const forgotPasswordRequest = async (body, requestURL) => {
    try {
      await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: body,
        cancelToken: source.token,
      });

      push('/auth/forgot-password-success');
    } catch (err) {
      console.error(err);

      strapi.notification.error('notification.error');
    }
  };

  const loginRequest = async (body, requestURL) => {
    try {
      const {
        data: {
          data: { token, user },
        },
      } = await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: body,
        cancelToken: source.token,
      });

      auth.setToken(token, modifiedData.rememberMe);
      auth.setUserInfo(user, modifiedData.rememberMe);

      push('/');
    } catch (err) {
      if (err.response) {
        const errorMessage = get(err, ['response', 'data', 'message'], 'Something went wrong');
        const errorStatus = get(err, ['response', 'data', 'statusCode'], 400);

        if (camelCase(errorMessage).toLowerCase() === 'usernotactive') {
          push('/auth/oops');

          dispatch({
            type: 'RESET_PROPS',
          });

          return;
        }

        dispatch({
          type: 'SET_REQUEST_ERROR',
          errorMessage,
          errorStatus,
        });
      }
    }
  };

  const registerRequest = async (body, requestURL) => {
    try {
      const {
        data: {
          data: { token, user },
        },
      } = await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: body,
        cancelToken: source.token,
      });

      auth.setToken(token, false);
      auth.setUserInfo(user, false);

      if (
        (authType === 'register' && modifiedData.userInfo.news === true) ||
        (authType === 'register-admin' && modifiedData.news === true)
      ) {
        axios({
          method: 'POST',
          url: 'https://analytics.strapi.io/register',
          data: {
            email: user.email,
            username: user.firstname,
          },
        });
      }
      // Redirect to the homePage
      push('/');
    } catch (err) {
      if (err.response) {
        const { data } = err.response;
        const apiErrors = formatAPIErrors(data);

        dispatch({
          type: 'SET_ERRORS',
          errors: apiErrors,
        });
      }
    }
  };

  const resetPasswordRequest = async (body, requestURL) => {
    try {
      const {
        data: {
          data: { token, user },
        },
      } = await axios({
        method: 'POST',
        url: `${strapi.backendURL}${requestURL}`,
        data: { ...body, resetPasswordToken: query.get('code') },
        cancelToken: source.token,
      });

      auth.setToken(token, false);
      auth.setUserInfo(user, false);

      // Redirect to the homePage
      push('/');
    } catch (err) {
      if (err.response) {
        const errorMessage = get(err, ['response', 'data', 'message'], 'Something went wrong');
        const errorStatus = get(err, ['response', 'data', 'statusCode'], 400);

        dispatch({
          type: 'SET_REQUEST_ERROR',
          errorMessage,
          errorStatus,
        });
      }
    }
  };

  // Redirect the user to the login page if the endpoint does not exist
  if (!forms[authType]) {
    return <Redirect to="/" />;
  }

  // Redirect the user to the login page if there is already an admin user
  if (hasAdmin && authType === 'register-admin') {
    return <Redirect to="/" />;
  }

  // Redirect the user to the register-admin if it is the first user
  if (!hasAdmin && authType !== 'register-admin') {
    return <Redirect to="/auth/register-admin" />;
  }

  // Redirect the user to the homepage if he is logged in
  if (auth.getToken()) {
    return <Redirect to="/" />;
  }

  return (
    <>
      <Padded bottom size="md">
        <PageTitle title={upperFirst(authType)} />
        <NavTopRightWrapper>
          <LocaleToggle isLogged className="localeDropdownMenuNotLogged" />
        </NavTopRightWrapper>
        <BaselineAlignment top size="78px">
          <Component
            fieldsToDisable={fieldsToDisable}
            formErrors={formErrors}
            inputsPrefix={inputsPrefix}
            modifiedData={modifiedData}
            onChange={handleChange}
            onSubmit={handleSubmit}
            requestError={requestError}
          />
        </BaselineAlignment>
      </Padded>
    </>
  );
};

AuthPage.defaultProps = {
  hasAdmin: false,
};

AuthPage.propTypes = {
  hasAdmin: PropTypes.bool,
};

export default AuthPage;
<|endoftext|>


<commit_before>const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const VueLoaderPlugin = require('@dcloudio/vue-cli-plugin-uni/packages/vue-loader/lib/plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')

const {
  getNVueMainEntry,
  nvueJsPreprocessOptions,
  nvueHtmlPreprocessOptions,
  getTemplatePath
} = require('@dcloudio/uni-cli-shared')
const fileLoader = require('@dcloudio/uni-cli-shared/lib/file-loader')
const WebpackAppPlusNVuePlugin = process.env.UNI_USING_V3
  ? require('../packages/webpack-app-plus-plugin')
  : require('../packages/webpack-app-plus-nvue-plugin')

const WebpackErrorsPlugin = require('@dcloudio/vue-cli-plugin-uni/packages/webpack-errors-plugin')
const WebpackUniMPPlugin = require('@dcloudio/webpack-uni-mp-loader/lib/plugin/index-new')

const onErrors = require('@dcloudio/vue-cli-plugin-uni/util/on-errors')
const onWarnings = require('@dcloudio/vue-cli-plugin-uni/util/on-warnings')

const cssLoaders = require('./css-loader.conf')
const vueLoaderOptions = require('./vue-loader.conf')

const jsPreprocessorLoader = {
  loader: '@dcloudio/vue-cli-plugin-uni/packages/webpack-preprocess-loader',
  options: nvueJsPreprocessOptions
}

const htmlPreprocessorLoader = {
  loader: '@dcloudio/vue-cli-plugin-uni/packages/webpack-preprocess-loader',
  options: nvueHtmlPreprocessOptions
}

const uniPath = process.env.UNI_USING_V8
  ? '../packages/uni-app-plus-nvue-v8/dist/index.js'
  : '../packages/uni-app-plus-nvue/dist/index.js'

const uniCloudPath = require.resolve('@dcloudio/vue-cli-plugin-uni/packages/uni-cloud/dist/index.js')

const provide = {
  uniCloud: [uniCloudPath, 'default']
}

if (
  process.env.UNI_USING_V3 ||
  process.env.UNI_USING_NATIVE ||
  process.env.UNI_USING_V3_NATIVE
) {
  provide['uni.getCurrentSubNVue'] = [path.resolve(__dirname,
    '../packages/uni-app-plus-nvue/dist/get-current-sub-nvue.js'), 'default']
  provide['uni.requireNativePlugin'] = [path.resolve(__dirname,
    '../packages/uni-app-plus-nvue/dist/require-native-plugin.js'), 'default']
}

if (!process.env.UNI_USING_V3 && !process.env.UNI_USING_V3_NATIVE) {
  if (!process.env.UNI_USING_NATIVE) {
    provide.uni = [path.resolve(__dirname, uniPath), 'default']
  }

  if (process.env.UNI_USING_V8) {
    provide.plus = [path.resolve(__dirname, uniPath), 'weexPlus']
  }
}

if (
  process.env.UNI_PLATFORM === 'app-plus' &&
  process.env.UNI_USING_V8
) {
  provide.__f__ = [require.resolve('@dcloudio/vue-cli-plugin-uni/lib/format-log.js'), 'default']
  provide.crypto = [require.resolve('@dcloudio/vue-cli-plugin-uni/lib/crypto.js'), 'default']
}

const plugins = [
  new VueLoaderPlugin(),
  new webpack.DefinePlugin({
    'process.env': {
      NODE_ENV: JSON.stringify(process.env.NODE_ENV),
      UNI_APP_ID: JSON.stringify(process.env.UNI_APP_ID),
      UNI_APP_NAME: JSON.stringify(process.env.UNI_APP_NAME),
      UNI_PLATFORM: JSON.stringify(process.env.UNI_PLATFORM),
      VUE_APP_PLATFORM: JSON.stringify(process.env.UNI_PLATFORM),
      UNI_CLOUD_PROVIDER: process.env.UNI_CLOUD_PROVIDER,
      UNICLOUD_DEBUG: process.env.UNICLOUD_DEBUG,
      RUN_BY_HBUILDERX: process.env.RUN_BY_HBUILDERX,
      HBX_USER_TOKEN: JSON.stringify(process.env.HBX_USER_TOKEN || ''),
      UNI_AUTOMATOR_WS_ENDPOINT: JSON.stringify(process.env.UNI_AUTOMATOR_WS_ENDPOINT)
    }
  }),
  new webpack.BannerPlugin({
    banner: '"use weex:vue";',
    raw: true,
    exclude: 'Vue'
  }),
  new webpack.ProvidePlugin(provide),
  new WebpackErrorsPlugin({
    onErrors,
    onWarnings
  }),
  new WebpackAppPlusNVuePlugin()
]

if (process.env.NODE_ENV === 'development') {
  plugins.push(require('@dcloudio/uni-cli-shared/lib/source-map').createEvalSourceMapDevToolPlugin())
}

// const excludeModuleReg = /node_modules(?!(\/|\\).*(weex).*)/

const rules = [{
  test: /\.(png|jpg|gif|ttf|eot|woff|woff2)$/i,
  use: [fileLoader]
}, {
  test: path.resolve(process.env.UNI_INPUT_DIR, 'pages.json'),
  use: [{
    loader: '@dcloudio/webpack-uni-pages-loader'
  }],
  type: 'javascript/auto'
}, {
  test: path.resolve(process.env.UNI_INPUT_DIR, getNVueMainEntry()),
  use: [{
    loader: '@dcloudio/vue-cli-plugin-hbuilderx/packages/webpack-uni-nvue-loader/lib/main'
  }]
}, {
  test: /\.js$/,
  use: [{
    loader: 'babel-loader',
    options: {
      babelrc: false
    }
  },
  jsPreprocessorLoader
  ]
  // exclude (modulePath) { // nvue js均提供babel，否则还得提供transpileDependencies配置
  //   return excludeModuleReg.test(modulePath) && modulePath.indexOf('@dcloudio') === -1
  // }
},
{
  test: [/\.nvue(\?[^?]+)?$/, /\.vue(\?[^?]+)?$/],
  use: [{
    loader: require.resolve('@dcloudio/vue-cli-plugin-uni/packages/vue-loader'),
    options: vueLoaderOptions
  }]
},
{
  test: /\.pug$/,
  oneOf: [{
    resourceQuery: /vue/,
    use: [{
      loader: 'pug-plain-loader'
    }]
  },
  {
    use: [{
      loader: 'raw-loader'
    }, {
      loader: 'pug-plain-loader'
    }]
  }
  ]
},
{
  resourceQuery: /vue&type=template/,
  use: [htmlPreprocessorLoader]
}
].concat(cssLoaders)

if (process.env.UNI_USING_NVUE_COMPILER || process.env.UNI_USING_V3_NATIVE) {
  rules.unshift({
    resourceQuery: function (query) {
      return query.indexOf('vue&type=template') !== -1 && query.indexOf('mpType=page') !== -1
    },
    use: [{
      loader: '@dcloudio/vue-cli-plugin-hbuilderx/packages/webpack-uni-nvue-loader/lib/template'
    }, {
      loader: '@dcloudio/vue-cli-plugin-uni/packages/webpack-uni-app-loader/page-meta'
    }]
  })
}
rules.unshift({
  resourceQuery: function (query) {
    return query.indexOf('vue&type=template') !== -1 && query.indexOf('mpType=page') === -1
  },
  use: [{
    loader: '@dcloudio/vue-cli-plugin-hbuilderx/packages/webpack-uni-nvue-loader/lib/template.recycle'
  }]
})

if (process.env.UNI_USING_V3_NATIVE) {
  try {
    const automatorJson = require.resolve('@dcloudio/uni-automator/dist/automator.json')
    plugins.push(new CopyWebpackPlugin([{
      from: automatorJson,
      to: '../.automator/' + (process.env.UNI_SUB_PLATFORM || process.env.UNI_PLATFORM) +
        '/.automator.json',
      transform (content) {
        if (process.env.UNI_AUTOMATOR_WS_ENDPOINT) {
          return JSON.stringify({
            version: require('@dcloudio/uni-automator/package.json').version,
            wsEndpoint: process.env.UNI_AUTOMATOR_WS_ENDPOINT
          })
        }
        return ''
      }
    }]))
  } catch (e) {}
}

if (process.env.UNI_USING_NATIVE || process.env.UNI_USING_V3_NATIVE) {
  plugins.push(new WebpackUniMPPlugin())
  const array = [{
    from: path.resolve(process.env.UNI_INPUT_DIR, 'static'),
    to: 'static'
  }]

  const androidPrivacyPath = path.resolve(process.env.UNI_INPUT_DIR, 'androidPrivacy.json')
  if (fs.existsSync(androidPrivacyPath)) {
    array.push({
      from: androidPrivacyPath,
      to: 'androidPrivacy.json'
    })
  }

  const hybridHtmlPath = path.resolve(process.env.UNI_INPUT_DIR, 'hybrid/html')
  if (fs.existsSync(hybridHtmlPath)) {
    array.push({
      from: hybridHtmlPath,
      to: 'hybrid/html'
    })
  }

  if (process.env.UNI_USING_NVUE_COMPILER) {
    array.push({
      from: path.resolve(getTemplatePath(), 'common'),
      to: process.env.UNI_OUTPUT_DIR
    })
  } else if (process.env.UNI_USING_V3_NATIVE) {
    array.push({
      from: path.resolve(getTemplatePath(), 'weex'),
      to: process.env.UNI_OUTPUT_DIR
    })
  } else {
    let nativeTemplatePath = path.resolve(process.env.UNI_HBUILDERX_PLUGINS, 'weapp-tools/template/v8-native')
    if (!fs.existsSync(nativeTemplatePath)) { // 兼容旧版本
      nativeTemplatePath = path.resolve(
        process.env.UNI_HBUILDERX_PLUGINS,
        'weapp-tools/template/v8'
      )
    }
    array.push({
      from: nativeTemplatePath,
      to: process.env.UNI_OUTPUT_DIR
    }, {
      from: path.resolve(
        process.env.UNI_HBUILDERX_PLUGINS,
        'weapp-tools/template/common'
      ),
      to: process.env.UNI_OUTPUT_DIR,
      ignore: [
        '*.js',
        '*.json',
        '__uniapppicker.html',
        '__uniappview.html'
      ]
    })
  }
  plugins.push(new CopyWebpackPlugin(array))
}

try {
  if (process.env.UNI_HBUILDERX_PLUGINS) {
    require(path.resolve(process.env.UNI_HBUILDERX_PLUGINS, 'uni_helpers/lib/bytenode'))
    const {
      W
    } = require(path.resolve(process.env.UNI_HBUILDERX_PLUGINS, 'uni_helpers'))
    plugins.push(new W({
      dir: process.env.UNI_INPUT_DIR
    }))
  }
} catch (e) {}

module.exports = function () {
  return {
    target: 'node', // 激活 vue-loader 的 isServer 逻辑
    mode: process.env.NODE_ENV,
    devtool: false,
    watch: process.env.NODE_ENV === 'development',
    entry () {
      return process.UNI_NVUE_ENTRY
    },
    externals: {
      vue: 'Vue'
    },
    performance: {
      hints: false
    },
    optimization: {
      namedModules: false,
      minimizer: [
        new TerserPlugin({
          terserOptions: {
            output: {
              ascii_only: true
            }
          }
        })
      ]
    },
    output: {
      path: process.env.UNI_OUTPUT_DIR,
      filename: '[name].js'
    },
    resolve: {
      extensions: ['.js', '.nvue', '.vue', '.json'],
      alias: {
        '@': process.env.UNI_INPUT_DIR,
        'uni-pages': path.resolve(process.env.UNI_INPUT_DIR, 'pages.json'),
        '@dcloudio/uni-stat': require.resolve('@dcloudio/uni-stat'),
        'uni-app-style': path.resolve(process.env.UNI_INPUT_DIR, getNVueMainEntry()) + '?' + JSON.stringify({
          type: 'appStyle'
        }),
        'uni-stat-config': path.resolve(process.env.UNI_INPUT_DIR, 'pages.json') +
          '?' +
          JSON.stringify({
            type: 'stat'
          })
      },
      modules: [
        'node_modules',
        path.resolve(process.env.UNI_CLI_CONTEXT, 'node_modules'),
        path.resolve(process.env.UNI_INPUT_DIR, 'node_modules')
      ]
    },
    resolveLoader: {
      alias: {
        'babel-loader': require.resolve('babel-loader', {
          paths: [require.resolve('@vue/cli-plugin-babel')]
        })
      }
    },
    module: {
      rules
    },
    plugins,
    stats: {
      reasons: true,
      errorDetails: true
    },
    node: {
      global: false,
      Buffer: false,
      __filename: false,
      __dirname: false,
      setImmediate: false,
      clearImmediate: false,
      assert: false,
      buffer: false,
      child_process: false,
      cluster: false,
      console: false,
      constants: false,
      crypto: false,
      dgram: false,
      dns: false,
      domain: false,
      events: false,
      fs: false,
      http: false,
      https: false,
      module: false,
      net: false,
      os: false,
      path: false,
      process: false,
      punycode: false,
      querystring: false,
      readline: false,
      repl: false,
      stream: false,
      string_decoder: false,
      sys: false,
      timers: false,
      tls: false,
      tty: false,
      url: false,
      util: false,
      vm: false,
      zlib: false
    }
  }
}
<commit_msg>fix: remove useless define<commit_after>const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const VueLoaderPlugin = require('@dcloudio/vue-cli-plugin-uni/packages/vue-loader/lib/plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')

const {
  getNVueMainEntry,
  nvueJsPreprocessOptions,
  nvueHtmlPreprocessOptions,
  getTemplatePath
} = require('@dcloudio/uni-cli-shared')
const fileLoader = require('@dcloudio/uni-cli-shared/lib/file-loader')
const WebpackAppPlusNVuePlugin = process.env.UNI_USING_V3
  ? require('../packages/webpack-app-plus-plugin')
  : require('../packages/webpack-app-plus-nvue-plugin')

const WebpackErrorsPlugin = require('@dcloudio/vue-cli-plugin-uni/packages/webpack-errors-plugin')
const WebpackUniMPPlugin = require('@dcloudio/webpack-uni-mp-loader/lib/plugin/index-new')

const onErrors = require('@dcloudio/vue-cli-plugin-uni/util/on-errors')
const onWarnings = require('@dcloudio/vue-cli-plugin-uni/util/on-warnings')

const cssLoaders = require('./css-loader.conf')
const vueLoaderOptions = require('./vue-loader.conf')

const jsPreprocessorLoader = {
  loader: '@dcloudio/vue-cli-plugin-uni/packages/webpack-preprocess-loader',
  options: nvueJsPreprocessOptions
}

const htmlPreprocessorLoader = {
  loader: '@dcloudio/vue-cli-plugin-uni/packages/webpack-preprocess-loader',
  options: nvueHtmlPreprocessOptions
}

const uniPath = process.env.UNI_USING_V8
  ? '../packages/uni-app-plus-nvue-v8/dist/index.js'
  : '../packages/uni-app-plus-nvue/dist/index.js'

const uniCloudPath = require.resolve('@dcloudio/vue-cli-plugin-uni/packages/uni-cloud/dist/index.js')

const provide = {
  uniCloud: [uniCloudPath, 'default']
}

if (
  process.env.UNI_USING_V3 ||
  process.env.UNI_USING_NATIVE ||
  process.env.UNI_USING_V3_NATIVE
) {
  provide['uni.getCurrentSubNVue'] = [path.resolve(__dirname,
    '../packages/uni-app-plus-nvue/dist/get-current-sub-nvue.js'), 'default']
  provide['uni.requireNativePlugin'] = [path.resolve(__dirname,
    '../packages/uni-app-plus-nvue/dist/require-native-plugin.js'), 'default']
}

if (!process.env.UNI_USING_V3 && !process.env.UNI_USING_V3_NATIVE) {
  if (!process.env.UNI_USING_NATIVE) {
    provide.uni = [path.resolve(__dirname, uniPath), 'default']
  }

  if (process.env.UNI_USING_V8) {
    provide.plus = [path.resolve(__dirname, uniPath), 'weexPlus']
  }
}

if (
  process.env.UNI_PLATFORM === 'app-plus' &&
  process.env.UNI_USING_V8
) {
  provide.__f__ = [require.resolve('@dcloudio/vue-cli-plugin-uni/lib/format-log.js'), 'default']
  provide.crypto = [require.resolve('@dcloudio/vue-cli-plugin-uni/lib/crypto.js'), 'default']
}

const plugins = [
  new VueLoaderPlugin(),
  new webpack.DefinePlugin({
    'process.env': {
      NODE_ENV: JSON.stringify(process.env.NODE_ENV),
      UNI_APP_ID: JSON.stringify(process.env.UNI_APP_ID),
      UNI_APP_NAME: JSON.stringify(process.env.UNI_APP_NAME),
      UNI_PLATFORM: JSON.stringify(process.env.UNI_PLATFORM),
      VUE_APP_PLATFORM: JSON.stringify(process.env.UNI_PLATFORM),
      UNI_CLOUD_PROVIDER: process.env.UNI_CLOUD_PROVIDER,
      UNICLOUD_DEBUG: process.env.UNICLOUD_DEBUG,
      RUN_BY_HBUILDERX: process.env.RUN_BY_HBUILDERX,
      UNI_AUTOMATOR_WS_ENDPOINT: JSON.stringify(process.env.UNI_AUTOMATOR_WS_ENDPOINT)
    }
  }),
  new webpack.BannerPlugin({
    banner: '"use weex:vue";',
    raw: true,
    exclude: 'Vue'
  }),
  new webpack.ProvidePlugin(provide),
  new WebpackErrorsPlugin({
    onErrors,
    onWarnings
  }),
  new WebpackAppPlusNVuePlugin()
]

if (process.env.NODE_ENV === 'development') {
  plugins.push(require('@dcloudio/uni-cli-shared/lib/source-map').createEvalSourceMapDevToolPlugin())
}

// const excludeModuleReg = /node_modules(?!(\/|\\).*(weex).*)/

const rules = [{
  test: /\.(png|jpg|gif|ttf|eot|woff|woff2)$/i,
  use: [fileLoader]
}, {
  test: path.resolve(process.env.UNI_INPUT_DIR, 'pages.json'),
  use: [{
    loader: '@dcloudio/webpack-uni-pages-loader'
  }],
  type: 'javascript/auto'
}, {
  test: path.resolve(process.env.UNI_INPUT_DIR, getNVueMainEntry()),
  use: [{
    loader: '@dcloudio/vue-cli-plugin-hbuilderx/packages/webpack-uni-nvue-loader/lib/main'
  }]
}, {
  test: /\.js$/,
  use: [{
    loader: 'babel-loader',
    options: {
      babelrc: false
    }
  },
  jsPreprocessorLoader
  ]
  // exclude (modulePath) { // nvue js均提供babel，否则还得提供transpileDependencies配置
  //   return excludeModuleReg.test(modulePath) && modulePath.indexOf('@dcloudio') === -1
  // }
},
{
  test: [/\.nvue(\?[^?]+)?$/, /\.vue(\?[^?]+)?$/],
  use: [{
    loader: require.resolve('@dcloudio/vue-cli-plugin-uni/packages/vue-loader'),
    options: vueLoaderOptions
  }]
},
{
  test: /\.pug$/,
  oneOf: [{
    resourceQuery: /vue/,
    use: [{
      loader: 'pug-plain-loader'
    }]
  },
  {
    use: [{
      loader: 'raw-loader'
    }, {
      loader: 'pug-plain-loader'
    }]
  }
  ]
},
{
  resourceQuery: /vue&type=template/,
  use: [htmlPreprocessorLoader]
}
].concat(cssLoaders)

if (process.env.UNI_USING_NVUE_COMPILER || process.env.UNI_USING_V3_NATIVE) {
  rules.unshift({
    resourceQuery: function (query) {
      return query.indexOf('vue&type=template') !== -1 && query.indexOf('mpType=page') !== -1
    },
    use: [{
      loader: '@dcloudio/vue-cli-plugin-hbuilderx/packages/webpack-uni-nvue-loader/lib/template'
    }, {
      loader: '@dcloudio/vue-cli-plugin-uni/packages/webpack-uni-app-loader/page-meta'
    }]
  })
}
rules.unshift({
  resourceQuery: function (query) {
    return query.indexOf('vue&type=template') !== -1 && query.indexOf('mpType=page') === -1
  },
  use: [{
    loader: '@dcloudio/vue-cli-plugin-hbuilderx/packages/webpack-uni-nvue-loader/lib/template.recycle'
  }]
})

if (process.env.UNI_USING_V3_NATIVE) {
  try {
    const automatorJson = require.resolve('@dcloudio/uni-automator/dist/automator.json')
    plugins.push(new CopyWebpackPlugin([{
      from: automatorJson,
      to: '../.automator/' + (process.env.UNI_SUB_PLATFORM || process.env.UNI_PLATFORM) +
        '/.automator.json',
      transform (content) {
        if (process.env.UNI_AUTOMATOR_WS_ENDPOINT) {
          return JSON.stringify({
            version: require('@dcloudio/uni-automator/package.json').version,
            wsEndpoint: process.env.UNI_AUTOMATOR_WS_ENDPOINT
          })
        }
        return ''
      }
    }]))
  } catch (e) {}
}

if (process.env.UNI_USING_NATIVE || process.env.UNI_USING_V3_NATIVE) {
  plugins.push(new WebpackUniMPPlugin())
  const array = [{
    from: path.resolve(process.env.UNI_INPUT_DIR, 'static'),
    to: 'static'
  }]

  const androidPrivacyPath = path.resolve(process.env.UNI_INPUT_DIR, 'androidPrivacy.json')
  if (fs.existsSync(androidPrivacyPath)) {
    array.push({
      from: androidPrivacyPath,
      to: 'androidPrivacy.json'
    })
  }

  const hybridHtmlPath = path.resolve(process.env.UNI_INPUT_DIR, 'hybrid/html')
  if (fs.existsSync(hybridHtmlPath)) {
    array.push({
      from: hybridHtmlPath,
      to: 'hybrid/html'
    })
  }

  if (process.env.UNI_USING_NVUE_COMPILER) {
    array.push({
      from: path.resolve(getTemplatePath(), 'common'),
      to: process.env.UNI_OUTPUT_DIR
    })
  } else if (process.env.UNI_USING_V3_NATIVE) {
    array.push({
      from: path.resolve(getTemplatePath(), 'weex'),
      to: process.env.UNI_OUTPUT_DIR
    })
  } else {
    let nativeTemplatePath = path.resolve(process.env.UNI_HBUILDERX_PLUGINS, 'weapp-tools/template/v8-native')
    if (!fs.existsSync(nativeTemplatePath)) { // 兼容旧版本
      nativeTemplatePath = path.resolve(
        process.env.UNI_HBUILDERX_PLUGINS,
        'weapp-tools/template/v8'
      )
    }
    array.push({
      from: nativeTemplatePath,
      to: process.env.UNI_OUTPUT_DIR
    }, {
      from: path.resolve(
        process.env.UNI_HBUILDERX_PLUGINS,
        'weapp-tools/template/common'
      ),
      to: process.env.UNI_OUTPUT_DIR,
      ignore: [
        '*.js',
        '*.json',
        '__uniapppicker.html',
        '__uniappview.html'
      ]
    })
  }
  plugins.push(new CopyWebpackPlugin(array))
}

try {
  if (process.env.UNI_HBUILDERX_PLUGINS) {
    require(path.resolve(process.env.UNI_HBUILDERX_PLUGINS, 'uni_helpers/lib/bytenode'))
    const {
      W
    } = require(path.resolve(process.env.UNI_HBUILDERX_PLUGINS, 'uni_helpers'))
    plugins.push(new W({
      dir: process.env.UNI_INPUT_DIR
    }))
  }
} catch (e) {}

module.exports = function () {
  return {
    target: 'node', // 激活 vue-loader 的 isServer 逻辑
    mode: process.env.NODE_ENV,
    devtool: false,
    watch: process.env.NODE_ENV === 'development',
    entry () {
      return process.UNI_NVUE_ENTRY
    },
    externals: {
      vue: 'Vue'
    },
    performance: {
      hints: false
    },
    optimization: {
      namedModules: false,
      minimizer: [
        new TerserPlugin({
          terserOptions: {
            output: {
              ascii_only: true
            }
          }
        })
      ]
    },
    output: {
      path: process.env.UNI_OUTPUT_DIR,
      filename: '[name].js'
    },
    resolve: {
      extensions: ['.js', '.nvue', '.vue', '.json'],
      alias: {
        '@': process.env.UNI_INPUT_DIR,
        'uni-pages': path.resolve(process.env.UNI_INPUT_DIR, 'pages.json'),
        '@dcloudio/uni-stat': require.resolve('@dcloudio/uni-stat'),
        'uni-app-style': path.resolve(process.env.UNI_INPUT_DIR, getNVueMainEntry()) + '?' + JSON.stringify({
          type: 'appStyle'
        }),
        'uni-stat-config': path.resolve(process.env.UNI_INPUT_DIR, 'pages.json') +
          '?' +
          JSON.stringify({
            type: 'stat'
          })
      },
      modules: [
        'node_modules',
        path.resolve(process.env.UNI_CLI_CONTEXT, 'node_modules'),
        path.resolve(process.env.UNI_INPUT_DIR, 'node_modules')
      ]
    },
    resolveLoader: {
      alias: {
        'babel-loader': require.resolve('babel-loader', {
          paths: [require.resolve('@vue/cli-plugin-babel')]
        })
      }
    },
    module: {
      rules
    },
    plugins,
    stats: {
      reasons: true,
      errorDetails: true
    },
    node: {
      global: false,
      Buffer: false,
      __filename: false,
      __dirname: false,
      setImmediate: false,
      clearImmediate: false,
      assert: false,
      buffer: false,
      child_process: false,
      cluster: false,
      console: false,
      constants: false,
      crypto: false,
      dgram: false,
      dns: false,
      domain: false,
      events: false,
      fs: false,
      http: false,
      https: false,
      module: false,
      net: false,
      os: false,
      path: false,
      process: false,
      punycode: false,
      querystring: false,
      readline: false,
      repl: false,
      stream: false,
      string_decoder: false,
      sys: false,
      timers: false,
      tls: false,
      tty: false,
      url: false,
      util: false,
      vm: false,
      zlib: false
    }
  }
}
<|endoftext|>