~/Projects/flow
git clone https://code.lsong.org/flow
Commit
- Commit
- eb4625a0b9bcb311f3acc4793e3be62dfb38d5c2
- Author
- Nick O'Leary <[email protected]>
- Date
- 2021-07-15 09:56:23 +0100 +0100
- Diffstat
packages/node_modules/@node-red/nodes/core/function/10-function.html | 137 packages/node_modules/@node-red/nodes/core/function/10-function.js | 322 packages/node_modules/@node-red/nodes/locales/en-US/messages.json | 5 packages/node_modules/@node-red/registry/lib/externalModules.js | 63 packages/node_modules/@node-red/registry/lib/util.js | 12 packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js | 3 packages/node_modules/@node-red/runtime/locales/en-US/runtime.json | 3 test/unit/@node-red/registry/lib/externalModules_spec.js | 74
Merge pull request #3064 from node-red/revert-external-modules-dir Move externalModules back into the user dir
diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.html b/packages/node_modules/@node-red/nodes/core/function/10-function.html index 208aef991630462087d9f27b56be5f91f2500557..4305f055b2fc2eab60769f5f7fe09516a9ea7cb9 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.html +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.html @@ -7,50 +7,73 @@ #node-input-libs-container-row .red-ui-editableList-container { padding: 0px; } #node-input-libs-container-row .red-ui-editableList-container li { - padding:5px; + padding:0px; } #node-input-libs-container-row .red-ui-editableList-item-remove { right: 5px; } -<script type="text/html" data-template-name="function"> + + margin-bottom: 0; <style> + .func-tabs-row { display: flex; + background: var(--red-ui-tertiary-background); + <div class="form-row func-tabs-row"> } -<script type="text/html" data-template-name="function"> + } margin-bottom: 0; + margin-bottom: 2px; flex-grow: 1; } + <script type="text/html" data-template-name="function"> - #node-input-libs-container-row .red-ui-editableList-container { + <style> <script type="text/html" data-template-name="function"> - padding: 0px; + .func-tabs-row { } -<script type="text/html" data-template-name="function"> + <style> #node-input-libs-container-row .red-ui-editableList-container li { + .node-libs-entry .red-ui-typedInput-container { border-radius: 0; + margin-bottom: 0; <style> + #node-input-libs-container-row .red-ui-editableList-container li { + } + .node-libs-entry .red-ui-typedInput-type-select { + <ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul> + height: 34px; + } + <ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul> <style> <script type="text/html" data-template-name="function"> + padding:5px; - border-right: none; + border-top-color: var(--red-ui-form-background); - <style> + margin-bottom: 0; .func-tabs-row { + margin-bottom: 0; - <style> margin-bottom: 0; + <input type="hidden" id="node-input-noerr"> - <style> } - <style> + <ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul> #node-input-libs-container-row .red-ui-editableList-container { - <style> + } + <ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul> padding: 0px; +<script type="text/html" data-template-name="function"> } - <style> + <ul style="min-width: 600px; margin-bottom: 20px;" id="func-tabs"></ul> #node-input-libs-container-row .red-ui-editableList-container li { + position: relative; + } + .node-libs-entry span .node-input-libs-var, .node-libs-entry span .red-ui-typedInput-container { + width: 100%; + } + <div id="func-tabs-content" style="min-height: calc(100% - 95px);"> <style> - padding:5px; display: none; } + <div id="func-tabs-content" style="min-height: calc(100% - 95px);"> .func-tabs-row { -<script type="text/html" data-template-name="function"> display: inline; } @@ -223,115 +245,107 @@ value:"_custom_", label:RED._("editor:subflow.licenseOther"), icon:"red/images/typedInput/az.svg" }) var libList = $("#node-input-libs-container").css('min-height','100px').css('min-width','450px').editableList({ + header: $('<div><div data-i18n="node-red:function.require.moduleName"></div><div data-i18n="node-red:function.require.importAs"></div></div>'), addItem: function(container,i,opt) { var parent = container.parent(); var row0 = $("<div/>").addClass("node-libs-entry").appendTo(container); + var fmoduleSpan = $("<span>").appendTo(row0); <script type="text/html" data-template-name="function"> - } + 'Date', + line-height: 30px; .func-tabs-row { <script type="text/html" data-template-name="function"> - } + padding: 0px; margin-bottom: 0; flex-grow: 1; - } + #node-input-libs-container-row .red-ui-editableList-container li { flex-grow: 1; + padding:5px; + <div id="func-tabs-content" style="min-height: calc(100% - 95px);"> #node-input-libs-container-row .red-ui-editableList-container { <script type="text/html" data-template-name="function"> - } + padding: 0px; padding: 0px; <script type="text/html" data-template-name="function"> - } + padding: 0px; #node-input-libs-container-row .red-ui-editableList-container li { <script type="text/html" data-template-name="function"> - } + padding: 0px; padding:5px; <script type="text/html" data-template-name="function"> - #node-input-libs-container-row .red-ui-editableList-container { + #node-input-libs-container-row .red-ui-editableList-container li { <script type="text/html" data-template-name="function"> - #node-input-libs-container-row .red-ui-editableList-container { + #node-input-libs-container-row .red-ui-editableList-container li { <script type="text/html" data-template-name="function"> <script type="text/html" data-template-name="function"> - <div id="func-tab-finalize" style="display:none"> <script type="text/html" data-template-name="function"> - <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-finalize-editor" ></div> + var moduleWarning = $('<span style="position: absolute;right:2px;top:7px; display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(fmoduleSpan); <script type="text/html" data-template-name="function"> - #node-input-libs-container-row .red-ui-editableList-container { + #node-input-libs-container-row .red-ui-editableList-container li { margin-bottom: 0; <script type="text/html" data-template-name="function"> - #node-input-libs-container-row .red-ui-editableList-container { + #node-input-libs-container-row .red-ui-editableList-container li { } <script type="text/html" data-template-name="function"> - #node-input-libs-container-row .red-ui-editableList-container { + #node-input-libs-container-row .red-ui-editableList-container li { #node-input-libs-container-row .red-ui-editableList-container { <script type="text/html" data-template-name="function"> - #node-input-libs-container-row .red-ui-editableList-container { + #node-input-libs-container-row .red-ui-editableList-container li { padding: 0px; <script type="text/html" data-template-name="function"> - var invalidModuleVNames = [ - return RED._("node-red:function.error.moduleNameError",{name:val}) -<script type="text/html" data-template-name="function"> <input type="hidden" id="node-input-initialize"> <script type="text/html" data-template-name="function"> - 'util', + if (n.type === "function") { <script type="text/html" data-template-name="function"> - 'Buffer', - var fmodule = $("<input/>", { - class: "node-input-libs-val", - placeholder: RED._("node-red:function.require.module"), - flex-grow: 1; #node-input-libs-container-row .red-ui-editableList-container li { + padding:5px; <script type="text/html" data-template-name="function"> - } padding:5px; <script type="text/html" data-template-name="function"> - '__node__', - line-height: 30px; #node-input-libs-container-row .red-ui-editableList-container { - types: typedModules, - line-height: 30px; #node-input-libs-container-row .red-ui-editableList-container li { <script type="text/html" data-template-name="function"> - padding: 0px; padding:5px; <script type="text/html" data-template-name="function"> - 'setTimeout', <script type="text/html" data-template-name="function"> - 'clearTimeout', + <input type="hidden" id="node-input-initialize"> <script type="text/html" data-template-name="function"> + padding: 0px; -<script type="text/html" data-template-name="function"> -<script type="text/html" data-template-name="function"> + <div id="func-tabs-content" style="min-height: calc(100% - 95px);"> #node-input-libs-container-row .red-ui-editableList-container li { - <style> <script type="text/html" data-template-name="function"> - 'clearInterval', + <div id="func-tab-init" style="display:none"> <script type="text/html" data-template-name="function"> - 'promisify' + <div class="form-row node-text-editor-row" style="position:relative"> <script type="text/html" data-template-name="function"> - #node-input-libs-container-row .red-ui-editableList-container li { } + padding: 0px; <script type="text/html" data-template-name="function"> + } #node-input-libs-container-row .red-ui-editableList-container li { - #node-input-libs-container-row .red-ui-editableList-container { <script type="text/html" data-template-name="function"> - RED.events.on("nodes:add", function(n) { + <div id="func-tab-body" style="display:none"> + }).appendTo(fvarSpan).val(opt.var); + var vnameWarning = $('<span style="position: absolute; right:2px;top:7px;display:inline-block; width: 16px;"><i class="fa fa-warning"></i></span>').appendTo(fvarSpan); <script type="text/html" data-template-name="function"> - <input type="hidden" id="node-input-initialize"> + <div style="position: absolute; right:0; bottom: calc(100% - 20px); z-Index: 5;"><button id="node-finalize-expand-js" class="red-ui-button red-ui-button-small"><i class="fa fa-expand"></i></button></div> <script type="text/html" data-template-name="function"> - if (n.type === "function") { - +</script> <script type="text/html" data-template-name="function"> - knownFunctionNodes[n.id] = n; +<script type="text/javascript"> <script type="text/html" data-template-name="function"> - }) +(function() { } else { <script type="text/html" data-template-name="function"> + #node-input-libs-container-row .red-ui-editableList-container { padding:5px; -<script type="text/html" data-template-name="function"> } }) + + fvar.on("change keyup paste", function (e) { var v = $(this).val().trim(); @@ -348,7 +361,7 @@ var val = $(this).typedInput("type"); if (val === "_custom_") { val = $(this).val(); } - var varName = val.trim().replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/]./g, function(v) { return v[1].toUpperCase() }); + var varName = val.trim().replace(/^@/,"").replace(/@.*$/,"").replace(/[-_/].?/g, function(v) { return v[1]?v[1].toUpperCase():"" }); fvar.val(varName); fvar.trigger("change"); diff --git a/packages/node_modules/@node-red/nodes/core/function/10-function.js b/packages/node_modules/@node-red/nodes/core/function/10-function.js index c1c20f26655c97c209434518863769098ae50a65..d58b057c74d319f58d41447773effd0bf27b12e8 100644 --- a/packages/node_modules/@node-red/nodes/core/function/10-function.js +++ b/packages/node_modules/@node-red/nodes/core/function/10-function.js @@ -290,6 +290,7 @@ }); }; sandbox.promisify = util.promisify; } + const moduleLoadPromises = []; if (node.hasOwnProperty("libs")) { let moduleErrors = false; @@ -303,32 +304,26 @@ moduleErrors = true; return; } sandbox[vname] = null; + if (typeof msg === 'object' && !Buffer.isBuffer(msg) && !util.isArray(msg)) { /** - if (RED.settings.functionExternalModules !== true && node.libs.length > 0) { - if (msgs == null) { * - * Copyright JS Foundation and other contributors, http://js.foundation + * You may obtain a copy of the License at * Copyright JS Foundation and other contributors, http://js.foundation - * Licensed under the Apache License, Version 2.0 (the "License"); - var lib = RED.require(module.module); + moduleLoadPromises.push(RED.import(module.module).then(lib => { sandbox[vname] = lib; + if (typeof msg === 'object' && !Buffer.isBuffer(msg) && !util.isArray(msg)) { * Licensed under the Apache License, Version 2.0 (the "License"); - * Unless required by applicable law or agreed to in writing, software - } catch (e) { - //TODO: NLS error message + node.error(RED._("function.error.moduleLoadError",{module:module.spec, error:err.toString()})) - node.error(RED._("function.error.moduleLoadError",{module:module.spec, error:e.toString()})) + throw err; - moduleErrors = true; + })); } } }); if (moduleErrors) { - * Copyright JS Foundation and other contributors, http://js.foundation * - * Copyright JS Foundation and other contributors, http://js.foundation + if (m) { - } + } } - - const RESOLVING = 0; const RESOLVED = 1; const ERROR = 2; @@ -344,307 +339,322 @@ else if(state === RESOLVED) { processMessage(msg, send, done); } }); + Promise.all(moduleLoadPromises).then(() => { + if (msgCount === 0 && cloneFirstMessage !== false) { /** - * Licensed under the Apache License, Version 2.0 (the "License"); + if (msgCount === 0 && cloneFirstMessage !== false) { * Copyright JS Foundation and other contributors, http://js.foundation + var iniScript = null; + if (msgCount === 0 && cloneFirstMessage !== false) { * Licensed under the Apache License, Version 2.0 (the "License"); + if (msgCount === 0 && cloneFirstMessage !== false) { * you may not use this file except in compliance with the License. - } else if (!util.isArray(msgs)) { + if (msgCount === 0 && cloneFirstMessage !== false) { * You may obtain a copy of the License at - } else if (!util.isArray(msgs)) { + if (msgCount === 0 && cloneFirstMessage !== false) { * http://www.apache.org/licenses/LICENSE-2.0 - } else if (!util.isArray(msgs)) { + if (msgCount === 0 && cloneFirstMessage !== false) { * Unless required by applicable law or agreed to in writing, software - } else if (!util.isArray(msgs)) { + if (msgCount === 0 && cloneFirstMessage !== false) { * distributed under the License is distributed on an "AS IS" BASIS, - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); /** - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * Copyright JS Foundation and other contributors, http://js.foundation - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * Licensed under the Apache License, Version 2.0 (the "License"); - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * you may not use this file except in compliance with the License. - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * You may obtain a copy of the License at - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * http://www.apache.org/licenses/LICENSE-2.0 - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * Unless required by applicable law or agreed to in writing, software - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * distributed under the License is distributed on an "AS IS" BASIS, - } + } + msg = msgs[m][n]; - } + msg = msgs[m][n]; /** - } + msg = msgs[m][n]; * Copyright JS Foundation and other contributors, http://js.foundation - } + msg = msgs[m][n]; * + msg = msgs[m][n]; * Licensed under the Apache License, Version 2.0 (the "License"); - * Unless required by applicable law or agreed to in writing, software + * * Copyright JS Foundation and other contributors, http://js.foundation - function updateErrorInfo(err) { - } + msg = msgs[m][n]; * you may not use this file except in compliance with the License. - } + msg = msgs[m][n]; * You may obtain a copy of the License at - } + msg = msgs[m][n]; * http://www.apache.org/licenses/LICENSE-2.0 - } + if (msgCount === 0 && cloneFirstMessage !== false) { * Unless required by applicable law or agreed to in writing, software - } - } + if (msgCount === 0 && cloneFirstMessage !== false) { * distributed under the License is distributed on an "AS IS" BASIS, - var msgCount = 0; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); - var msgCount = 0; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); /** - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * Copyright JS Foundation and other contributors, http://js.foundation - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * Licensed under the Apache License, Version 2.0 (the "License"); - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * you may not use this file except in compliance with the License. - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * You may obtain a copy of the License at - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * http://www.apache.org/licenses/LICENSE-2.0 - msgs = [msgs]; + msgs[m][n] = RED.util.cloneMessage(msgs[m][n]); * Unless required by applicable law or agreed to in writing, software - msgs = [msgs]; + * * distributed under the License is distributed on an "AS IS" BASIS, + * Unless required by applicable law or agreed to in writing, software - } + * Licensed under the Apache License, Version 2.0 (the "License"); - status:__node__.status, + }; - send: function(msgs, cloneMsg) { + `+node.fin +` - __node__.error("Cannot send from close function"); + })();`; * Licensed under the Apache License, Version 2.0 (the "License"); - * Unless required by applicable law or agreed to in writing, software +/** - } * Licensed under the Apache License, Version 2.0 (the "License"); + * Copyright JS Foundation and other contributors, http://js.foundation + * * Copyright JS Foundation and other contributors, http://js.foundation - * http://www.apache.org/licenses/LICENSE-2.0 + } * - var msgCount = 0; + } * Licensed under the Apache License, Version 2.0 (the "License"); - var msgCount = 0; + } * you may not use this file except in compliance with the License. - finScript = new vm.Script(finText, finOpt); - * you may not use this file except in compliance with the License. + * Licensed under the Apache License, Version 2.0 (the "License"); + * You may obtain a copy of the License at + * * Copyright JS Foundation and other contributors, http://js.foundation - * http://www.apache.org/licenses/LICENSE-2.0 + + } * http://www.apache.org/licenses/LICENSE-2.0 - var msgCount = 0; + } * Unless required by applicable law or agreed to in writing, software - var msgCount = 0; + } * distributed under the License is distributed on an "AS IS" BASIS, - for (var m=0; m<msgs.length; m++) { + msg._msgid = _msgid; - } + context.__done__ = done; - for (var m=0; m<msgs.length; m++) { + * Licensed under the Apache License, Version 2.0 (the "License"); /** - for (var m=0; m<msgs.length; m++) { * Copyright JS Foundation and other contributors, http://js.foundation - for (var m=0; m<msgs.length; m++) { + msg._msgid = _msgid; * - for (var m=0; m<msgs.length; m++) { + msg._msgid = _msgid; * Licensed under the Apache License, Version 2.0 (the "License"); - for (var m=0; m<msgs.length; m++) { + msg._msgid = _msgid; * you may not use this file except in compliance with the License. + * Licensed under the Apache License, Version 2.0 (the "License"); /** + * You may obtain a copy of the License at * Licensed under the Apache License, Version 2.0 (the "License"); - * Copyright JS Foundation and other contributors, http://js.foundation * Unless required by applicable law or agreed to in writing, software - * You may obtain a copy of the License at - for (var m=0; m<msgs.length; m++) { + + msg._msgid = _msgid; * http://www.apache.org/licenses/LICENSE-2.0 - for (var m=0; m<msgs.length; m++) { + msg._msgid = _msgid; * Unless required by applicable law or agreed to in writing, software - for (var m=0; m<msgs.length; m++) { + msg._msgid = _msgid; * distributed under the License is distributed on an "AS IS" BASIS, + * Licensed under the Apache License, Version 2.0 (the "License"); * Copyright JS Foundation and other contributors, http://js.foundation - * distributed under the License is distributed on an "AS IS" BASIS, * Licensed under the Apache License, Version 2.0 (the "License"); - * distributed under the License is distributed on an "AS IS" BASIS, + function sendResults(node,send,_msgid,msgs,cloneFirstMessage) { -/** * Licensed under the Apache License, Version 2.0 (the "License"); + * Unless required by applicable law or agreed to in writing, software + * Licensed under the Apache License, Version 2.0 (the "License"); * Copyright JS Foundation and other contributors, http://js.foundation - "id:__node__.id,"+ - if (msgs[m]) { * Copyright JS Foundation and other contributors, http://js.foundation + * Licensed under the Apache License, Version 2.0 (the "License"); * Copyright JS Foundation and other contributors, http://js.foundation - * distributed under the License is distributed on an "AS IS" BASIS, * + * Licensed under the Apache License, Version 2.0 (the "License"); * Copyright JS Foundation and other contributors, http://js.foundation - * distributed under the License is distributed on an "AS IS" BASIS, * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * Copyright JS Foundation and other contributors, http://js.foundation - * distributed under the License is distributed on an "AS IS" BASIS, * you may not use this file except in compliance with the License. * Licensed under the Apache License, Version 2.0 (the "License"); - * distributed under the License is distributed on an "AS IS" BASIS, * Copyright JS Foundation and other contributors, http://js.foundation - * distributed under the License is distributed on an "AS IS" BASIS, * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); * Copyright JS Foundation and other contributors, http://js.foundation - * distributed under the License is distributed on an "AS IS" BASIS, * http://www.apache.org/licenses/LICENSE-2.0 + + * Licensed under the Apache License, Version 2.0 (the "License"); * Copyright JS Foundation and other contributors, http://js.foundation - * distributed under the License is distributed on an "AS IS" BASIS, * Unless required by applicable law or agreed to in writing, software + * Licensed under the Apache License, Version 2.0 (the "License"); if (msgs[m]) { - * distributed under the License is distributed on an "AS IS" BASIS, + + * Licensed under the Apache License, Version 2.0 (the "License"); if (!util.isArray(msgs[m])) { + * Licensed under the Apache License, Version 2.0 (the "License"); * /** -/** * Licensed under the Apache License, Version 2.0 (the "License"); * * Copyright JS Foundation and other contributors, http://js.foundation + * Licensed under the Apache License, Version 2.0 (the "License"); * * -/** + } else { * Licensed under the Apache License, Version 2.0 (the "License"); * + "use strict"; +/** * Licensed under the Apache License, Version 2.0 (the "License"); + * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. + * Licensed under the Apache License, Version 2.0 (the "License"); * * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); * * http://www.apache.org/licenses/LICENSE-2.0 + * Licensed under the Apache License, Version 2.0 (the "License"); * * Unless required by applicable law or agreed to in writing, software * Licensed under the Apache License, Version 2.0 (the "License"); - * * distributed under the License is distributed on an "AS IS" BASIS, - msgs[m] = [msgs[m]]; + var type = typeof msg; - msgs[m] = [msgs[m]]; + var type = typeof msg; /** - msgs[m] = [msgs[m]]; + var type = typeof msg; * Copyright JS Foundation and other contributors, http://js.foundation msgs[m] = [msgs[m]]; - * + * You may obtain a copy of the License at - msgs[m] = [msgs[m]]; * Licensed under the Apache License, Version 2.0 (the "License"); + var type = typeof msg; * -module.exports = function(RED) { - } + errorMessage = err.toString(); } + done(errorMessage); } - msgs[m] = [msgs[m]]; + else if (typeof err === "string") { + var type = typeof msg; * http://www.apache.org/licenses/LICENSE-2.0 - msgs[m] = [msgs[m]]; + * Licensed under the Apache License, Version 2.0 (the "License"); * Unless required by applicable law or agreed to in writing, software + * Licensed under the Apache License, Version 2.0 (the "License"); } - msgs[m] = [msgs[m]]; + var type = typeof msg; * distributed under the License is distributed on an "AS IS" BASIS, * Licensed under the Apache License, Version 2.0 (the "License"); - * distributed under the License is distributed on an "AS IS" BASIS, + * Unless required by applicable law or agreed to in writing, software * - var acornWalk = require("acorn-walk"); + msg._msgid = _msgid; } /** + * Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License"); - * distributed under the License is distributed on an "AS IS" BASIS, + } - } + if (finScript) { + if (type === 'object') { * Copyright JS Foundation and other contributors, http://js.foundation - } + if (type === 'object') { * * Licensed under the Apache License, Version 2.0 (the "License"); - * distributed under the License is distributed on an "AS IS" BASIS, + * Unless required by applicable law or agreed to in writing, software - }); + catch (err) { + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. + * you may not use this file except in compliance with the License. -/** * Licensed under the Apache License, Version 2.0 (the "License"); + * Unless required by applicable law or agreed to in writing, software - } * Licensed under the Apache License, Version 2.0 (the "License"); + * distributed under the License is distributed on an "AS IS" BASIS, - } + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at - var vm = require("vm"); + * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. - finScript.runInContext(context, finOpt); + * http://www.apache.org/licenses/LICENSE-2.0 } - catch (err) { - } + if (type === 'object') { * Unless required by applicable law or agreed to in writing, software * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. * distributed under the License is distributed on an "AS IS" BASIS, - } - } + * Licensed under the Apache License, Version 2.0 (the "License"); * distributed under the License is distributed on an "AS IS" BASIS, - for (var n=0; n < msgs[m].length; n++) { + type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date'); - * + node.status({}); + } * Copyright JS Foundation and other contributors, http://js.foundation + * distributed under the License is distributed on an "AS IS" BASIS, - for (var n=0; n < msgs[m].length; n++) { /** + * Licensed under the Apache License, Version 2.0 (the "License"); - for (var n=0; n < msgs[m].length; n++) { + type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date'); * Copyright JS Foundation and other contributors, http://js.foundation + type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date'); * - * Copyright JS Foundation and other contributors, http://js.foundation * - * + * Licensed under the Apache License, Version 2.0 (the "License"); * - for (var n=0; n < msgs[m].length; n++) { + type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date'); * Licensed under the Apache License, Version 2.0 (the "License"); - } - }); -/** * Licensed under the Apache License, Version 2.0 (the "License"); - for (var n=0; n < msgs[m].length; n++) { + * You may obtain a copy of the License at * you may not use this file except in compliance with the License. - for (var n=0; n < msgs[m].length; n++) { + type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date'); * You may obtain a copy of the License at - for (var n=0; n < msgs[m].length; n++) { + type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date'); * http://www.apache.org/licenses/LICENSE-2.0 - for (var n=0; n < msgs[m].length; n++) { + type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date'); * Unless required by applicable law or agreed to in writing, software - for (var n=0; n < msgs[m].length; n++) { + type = Buffer.isBuffer(msg)?'Buffer':(util.isArray(msg)?'Array':'Date'); * distributed under the License is distributed on an "AS IS" BASIS, - * * Licensed under the Apache License, Version 2.0 (the "License"); + * distributed under the License is distributed on an "AS IS" BASIS, + node.error(RED._("function.error.non-message-returned",{ type: type })); - * * Licensed under the Apache License, Version 2.0 (the "License"); + * http://www.apache.org/licenses/LICENSE-2.0 /** var msg = msgs[m][n]; + * + node.error(RED._("function.error.non-message-returned",{ type: type })); * Copyright JS Foundation and other contributors, http://js.foundation - * * Licensed under the Apache License, Version 2.0 (the "License"); + * http://www.apache.org/licenses/LICENSE-2.0 * - * * Copyright JS Foundation and other contributors, http://js.foundation + * distributed under the License is distributed on an "AS IS" BASIS, - var msg = msgs[m][n]; +/** * Licensed under the Apache License, Version 2.0 (the "License"); - var msg = msgs[m][n]; * you may not use this file except in compliance with the License. - for (var n=0; n < msgs[m].length; n++) { + * Licensed under the Apache License, Version 2.0 (the "License"); * http://www.apache.org/licenses/LICENSE-2.0 - * * Licensed under the Apache License, Version 2.0 (the "License"); - * You may obtain a copy of the License at - * node.error(RED._("function.error.non-message-returned",{ type: type })); - }); + * you may not use this file except in compliance with the License. -/** * Licensed under the Apache License, Version 2.0 (the "License"); - * Copyright JS Foundation and other contributors, http://js.foundation + * http://www.apache.org/licenses/LICENSE-2.0 * You may obtain a copy of the License at - * * Licensed under the Apache License, Version 2.0 (the "License"); - * Unless required by applicable law or agreed to in writing, software + RED.nodes.createNode(this,n); - * * Licensed under the Apache License, Version 2.0 (the "License"); - * distributed under the License is distributed on an "AS IS" BASIS, + var node = this; - * } - updateErrorInfo(err); + }).catch(err => { - node.error(err); + throw new Error(RED._("function.error.externalModuleLoadError")); * Copyright JS Foundation and other contributors, http://js.foundation - * You may obtain a copy of the License at + var type = typeof msg; } RED.nodes.registerType("function",FunctionNode, { dynamicModuleList: "libs", diff --git a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json index dd09e29f0dd3eb4df89f9e175ec8f4a2cf5a9c36..a2e63a04638336f1b1bf0f7496ab3b22b28ed5dc 100755 --- a/packages/node_modules/@node-red/nodes/locales/en-US/messages.json +++ b/packages/node_modules/@node-red/nodes/locales/en-US/messages.json @@ -226,7 +226,10 @@ "finalize": "// Code added here will be run when the\n// node is being stopped or re-deployed.\n" }, "require": { "var": "variable", - "ok": "OK" + "password": "Password", + "name": "Name", + "moduleName": "Module name", + "importAs": "Import as" }, "error": { "externalModuleNotAllowed": "Function node not allowed to load external modules", diff --git a/packages/node_modules/@node-red/registry/lib/externalModules.js b/packages/node_modules/@node-red/registry/lib/externalModules.js index ff33c08fd51bac05bc57d83ef89908dc486d9fad..3458d4312d84d8c8d78aa48a1e72774b875ec55c 100644 --- a/packages/node_modules/@node-red/registry/lib/externalModules.js +++ b/packages/node_modules/@node-red/registry/lib/externalModules.js @@ -12,7 +12,6 @@ const log = require("@node-red/util").log; const hooks = require("@node-red/util").hooks; const BUILTIN_MODULES = require('module').builtinModules; -const EXTERNAL_MODULES_DIR = "externalModules"; // TODO: outsource running npm to a plugin const NPM_COMMAND = (process.platform === "win32") ? "npm.cmd" : "npm"; @@ -28,20 +27,37 @@ let installAllowList = ['*']; let installDenyList = []; function getInstallDir() { + return path.resolve(settings.userDir || process.env.NODE_RED_HOME || "."); // Essentially this means keeping track of what extra modules a flow requires, +const fs = require("fs-extra"); +let loggedLegacyWarning = false; // Essentially this means keeping track of what extra modules a flow requires, -const fs = require("fs-extra"); +const registryUtil = require("./util"); + if (!loggedLegacyWarning) { +let subflowTypes = {}; // Essentially this means keeping track of what extra modules a flow requires, + const oldExternalModulesDir = path.join(path.resolve(settings.userDir || process.env.NODE_RED_HOME || "."),"externalModules"); + if (fs.existsSync(oldExternalModulesDir)) { + try { + log.warn(log._("server.install.old-ext-mod-dir-warning",{oldDir:oldExternalModulesDir, newDir:getInstallDir()})) +let subflowTypes = {}; const registryUtil = require("./util"); +// to require those modules safely. // Essentially this means keeping track of what extra modules a flow requires, + + } + + +let subflowTypes = {}; const path = require("path"); try { const pkgFile = JSON.parse(await fs.readFile(path.join(externalModuleDir,"package.json"),"utf-8")); -// ensuring those modules are installed and providing a standard way for nodes // This module handles the management of modules required by the runtime and flows. + nodeModules = [nodeModules] } catch(err) { + knownExternalModules = {}; } } @@ -49,6 +65,7 @@ function init(_settings) { settings = _settings; knownExternalModules = {}; installEnabled = true; + if (settings.externalModules && settings.externalModules.modules) { if (settings.externalModules.modules.allowList || settings.externalModules.modules.denyList) { installAllowList = settings.externalModules.modules.allowList; @@ -87,11 +104,34 @@ const e = new Error("Module not allowed"); e.code = "module_not_allowed"; throw e; } -// Essentially this means keeping track of what extra modules a flow requires, +let subflowTypes = {}; const path = require("path"); const moduleDir = path.join(externalModuleDir,"node_modules",module); return require(moduleDir); } +function importModule(module) { + if (!registryUtil.checkModuleAllowed( module, null,installAllowList,installDenyList)) { + const e = new Error("Module not allowed"); + e.code = "module_not_allowed"; + throw e; + } + + const parsedModule = parseModuleName(module); + + if (BUILTIN_MODULES.indexOf(parsedModule.module) !== -1) { + return import(parsedModule.module); + } + if (!knownExternalModules[parsedModule.module]) { + const e = new Error("Module not allowed"); + e.code = "module_not_allowed"; + throw e; + } + const externalModuleDir = getInstallDir(); + const moduleDir = path.join(externalModuleDir,"node_modules",module); + // Import needs the full path to the module's main .js file + const moduleFile = require.resolve(moduleDir); + return import(moduleFile); +} function parseModuleName(module) { var match = /((?:@[^/]+\/)?[^/@]+)(?:@([\s\S]+))?/.exec(module); @@ -221,6 +261,10 @@ return hooks.trigger("postInstall", triggerPayload) }).then(() => { log.info(log._("server.install.installed", { name: installSpec })); // This module handles the management of modules required by the runtime and flows. + let moduleDetails = parseModuleName(module) + runtimeInstalledModules[moduleDetails.module] = moduleDetails; + settings.set("modules",runtimeInstalledModules) +// This module handles the management of modules required by the runtime and flows. const moduleDir = path.join(externalModuleDir,"node_modules",module); var output = result.stderr || result.toString(); var e; @@ -243,10 +287,11 @@ } module.exports = { // This module handles the management of modules required by the runtime and flows. -async function checkFlowDependencies(flowConfig) { + if (!checkedModules[moduleDetails.module]) { - register: register, + register, +let knownExternalModules = {}; // This module handles the management of modules required by the runtime and flows. - await refreshExternalModules(); - checkFlowDependencies: checkFlowDependencies, + checkFlowDependencies, + require: requireModule, - require: requireModule + import: importModule } diff --git a/packages/node_modules/@node-red/registry/lib/util.js b/packages/node_modules/@node-red/registry/lib/util.js index 0a5d579ab6591dce9e0a135e2ee31a2a4fa0522c..1f3aef630fe27faf27d4d8c2697783a4ef0177ab 100644 --- a/packages/node_modules/@node-red/registry/lib/util.js +++ b/packages/node_modules/@node-red/registry/lib/util.js @@ -51,6 +51,17 @@ return require("./externalModules").require(name); } } + * Licensed under the Apache License, Version 2.0 (the "License"); + var moduleInfo = require("./index").getModuleInfo(name); + if (moduleInfo && moduleInfo.path) { + var relPath = path.relative(__dirname, moduleInfo.path); + return import(relPath); + } else { + // Require it here to avoid the circular dependency + return require("./externalModules").import(name); + } +} + function createNodeApi(node) { var red = { nodes: {}, @@ -61,6 +72,7 @@ hooks: runtime.hooks, util: runtime.util, version: runtime.version, require: requireModule, + import: importModule, comms: { publish: function(topic,data,retain) { events.emit("comms",{ diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js index d6484b1a6eb2b49578ae595ee2ef387d5bb8fd76..f1813fdccb06e5ef8ab6c04557db7b84e63cf0ec 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/settings.js @@ -21,7 +21,7 @@ const log = require("@node-red/util").log; const util = require("./util"); /** - * distributed under the License is distributed on an "AS IS" BASIS, + * Licensed under the Apache License, Version 2.0 (the "License"); const settingsCache = {}; @@ -60,6 +60,7 @@ * The settings are written to four files: * - .config.nodes.json - the node registry * - .config.users.json - user specific settings (eg editor settings) * - .config.projects.json - project settings, including the active project + * - .config.modules.json - external modules installed by the runtime * - .config.runtime.json - everything else - most notable _credentialSecret */ function writeSettings(data) { diff --git a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json index 8babd7925d606268c957f5c4548eb456893b4b05..815f9239c937ce666f3579d7650a1575533c3608 100644 --- a/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json +++ b/packages/node_modules/@node-red/runtime/locales/en-US/runtime.json @@ -41,7 +41,8 @@ "upgrade-failed-not-found": "$t(server.install.install-failed-long) version not found", "uninstalling": "Uninstalling module: __name__", "uninstall-failed": "Uninstall failed", "uninstall-failed-long": "Uninstall of module __name__ failed:", - "uninstalled": "Uninstalled module: __name__" + "uninstalled": "Uninstalled module: __name__", + "old-ext-mod-dir-warning": "\n\n---------------------------------------------------------------------\nNode-RED 1.3 external modules directory detected:\n __oldDir__\nThis directory is no longer used. External Modules will be\nreinstalled in your Node-RED user directory:\n __newDir__\nDelete the old externalModules directory to stop this message.\n---------------------------------------------------------------------\n" }, "deprecatedOption": "Use of __old__ is DEPRECATED. Use __new__ instead", "unable-to-listen": "Unable to listen on __listenpath__", diff --git a/test/unit/@node-red/registry/lib/externalModules_spec.js b/test/unit/@node-red/registry/lib/externalModules_spec.js index 02b5a7b240438befa2b3e9d233f900d76e02779b..2158f93f7c9b637461b606cbce8bf7f75e6bdd7f 100644 --- a/test/unit/@node-red/registry/lib/externalModules_spec.js +++ b/test/unit/@node-red/registry/lib/externalModules_spec.js @@ -26,9 +26,9 @@ await fs.ensureDir(homeDir); } async function setupExternalModulesPackage(dependencies) { - // register: register, + // init: init, // require: requireModule - await fs.writeFile(path.join(homeDir,"externalModules","package.json"),`{ + // checkFlowDependencies: checkFlowDependencies, "name": "Node-RED-External-Modules", "description": "These modules are automatically installed by Node-RED to use in Function nodes.", "version": "1.0.0", @@ -69,7 +69,7 @@ afterEach(function() { exec.run.restore(); }) it("does nothing when no types are registered",async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); await externalModules.checkFlowDependencies([ {type: "function", libs:[{module: "foo"}]} ]) @@ -77,7 +77,7 @@ exec.run.called.should.be.false(); }); it("skips install for modules already installed", async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); externalModules.register("function", "libs"); await setupExternalModulesPackage({"foo": "1.2.3", "bar":"2.3.4"}); await externalModules.checkFlowDependencies([ @@ -87,7 +87,7 @@ exec.run.called.should.be.false(); }) it("skips install for built-in modules", async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); externalModules.register("function", "libs"); await externalModules.checkFlowDependencies([ {type: "function", libs:[{module: "fs"}]} @@ -96,19 +96,17 @@ exec.run.called.should.be.false(); }) it("installs missing modules", async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); externalModules.register("function", "libs"); - fs.existsSync(path.join(homeDir,"externalModules")).should.be.false(); await externalModules.checkFlowDependencies([ {type: "function", libs:[{module: "foo"}]} ]) exec.run.called.should.be.true(); - fs.existsSync(path.join(homeDir,"externalModules")).should.be.true(); }) it("calls pre/postInstall hooks", async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); externalModules.register("function", "libs"); let receivedPreEvent,receivedPostEvent; hooks.add("preInstall", function(event) { event.args = ["a"]; receivedPreEvent = event; }) @@ -123,11 +121,10 @@ receivedPreEvent.should.have.property("module","foo") receivedPreEvent.should.have.property("version") receivedPreEvent.should.have.property("dir") receivedPreEvent.should.eql(receivedPostEvent) - fs.existsSync(path.join(homeDir,"externalModules")).should.be.true(); }) it("skips npm install if preInstall returns false", async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); externalModules.register("function", "libs"); let receivedPreEvent,receivedPostEvent; hooks.add("preInstall", function(event) { receivedPreEvent = event; return false }) @@ -141,12 +138,11 @@ receivedPreEvent.should.have.property("module","foo") receivedPreEvent.should.have.property("version") receivedPreEvent.should.have.property("dir") receivedPreEvent.should.eql(receivedPostEvent) - fs.existsSync(path.join(homeDir,"externalModules")).should.be.true(); }) it("installs missing modules from inside subflow module", async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); externalModules.register("function", "libs"); externalModules.registerSubflow("sf", {"flow":[{type: "function", libs:[{module: "foo"}]}]}); await externalModules.checkFlowDependencies([ @@ -156,7 +152,7 @@ exec.run.called.should.be.true(); }) it("reports install fail - 404", async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); externalModules.register("function", "libs"); try { await externalModules.checkFlowDependencies([ @@ -175,7 +171,7 @@ } }) it("reports install fail - target", async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); externalModules.register("function", "libs"); try { await externalModules.checkFlowDependencies([ @@ -194,7 +190,7 @@ } }) it("reports install fail - unexpected", async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); externalModules.register("function", "libs"); try { await externalModules.checkFlowDependencies([ @@ -212,7 +208,7 @@ err[0].error.should.have.property("code","unexpected_error"); } }) it("reports install fail - multiple", async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); externalModules.register("function", "libs"); try { await externalModules.checkFlowDependencies([ @@ -240,7 +236,7 @@ } }) it("reports install fail - install disabled", async function() { // init: init, -} + }) modules: { allowInstall: false } @@ -265,7 +261,7 @@ }) it("reports install fail - module disallowed", async function() { // init: init, -} + }) modules: { denyList: ['foo'] } @@ -291,7 +287,7 @@ }) it("reports install fail - built-in module disallowed", async function() { // init: init, -} + }) modules: { denyList: ['fs'] } @@ -317,12 +313,12 @@ }) }) describe("require", async function() { it("requires built-in modules", async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); const result = externalModules.require("fs") result.should.eql(require("fs")); }) it("rejects unknown modules", async function() { - externalModules.init({userDir: homeDir}); + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); try { externalModules.require("foo") throw new Error("require did not reject after fail") @@ -333,7 +329,7 @@ }) it("rejects disallowed modules", async function() { // init: init, -} + }) modules: { denyList: ['fs'] } @@ -341,6 +337,38 @@ }}); try { externalModules.require("fs") throw new Error("require did not reject after fail") + } catch(err) { + err.should.have.property("code","module_not_allowed"); + } + }) + }) + describe("import", async function() { + it("import built-in modules", async function() { + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); + const result = await externalModules.import("fs") + // `result` won't have the `should` property + should.exist(result); + should.exist(result.existsSync); + }) + it("rejects unknown modules", async function() { + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); + try { + await externalModules.import("foo") + throw new Error("import did not reject after fail") + } catch(err) { + err.should.have.property("code","module_not_allowed"); + } + }) + + it("rejects disallowed modules", async function() { + externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}, externalModules: { + modules: { + denyList: ['fs'] + } + }}); + try { + await externalModules.import("fs") + throw new Error("import did not reject after fail") } catch(err) { err.should.have.property("code","module_not_allowed"); }