diff --git a/CHANGELOG.md b/CHANGELOG.md index bd7de1465..39079b1fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +#### 4.1.4: Maintenance Releas + + - Update tar dependency @knolleary + - Revert overflow fix in editableList (#5467) @knolleary + - registry: fix importModule base dir for exports subpaths (#5465) @yuan-cloud + - fix: prevent race condition in localfilesystem context store during shutdown (#5462) @Dennis-SEG + - fix: prevent double resolve in node close callback (#5461) @Dennis-SEG + - fix: prevent incorrect array modification in delay node (#5457) @Dennis-SEG + - fix: prevent uncaught exceptions in core node event handlers (#5438) @Dennis-SEG + #### 4.1.3: Maintenance Release Editor diff --git a/package-lock.json b/package-lock.json index 155f837e6..038b2dc8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "node-red", - "version": "4.1.3", + "version": "4.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "node-red", - "version": "4.1.3", + "version": "4.1.4", "license": "Apache-2.0", "dependencies": { "acorn": "8.15.0", @@ -59,7 +59,7 @@ "raw-body": "3.0.0", "rfdc": "^1.3.1", "semver": "7.7.1", - "tar": "7.4.3", + "tar": "7.5.6", "tough-cookie": "5.1.2", "uglify-js": "3.19.3", "uuid": "9.0.1", @@ -10996,16 +10996,15 @@ "dev": true }, "node_modules/tar": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", - "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", - "license": "ISC", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz", + "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", - "minizlib": "^3.0.1", - "mkdirp": "^3.0.1", + "minizlib": "^3.1.0", "yallist": "^5.0.0" }, "engines": { @@ -11066,21 +11065,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/tar/node_modules/mkdirp": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "license": "MIT", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/tar/node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", diff --git a/package.json b/package.json index 9f06f871a..cba7fbfeb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "4.1.3", + "version": "4.1.4", "description": "Low-code programming for event-driven applications", "homepage": "https://nodered.org", "license": "Apache-2.0", @@ -76,7 +76,7 @@ "raw-body": "3.0.0", "rfdc": "^1.3.1", "semver": "7.7.1", - "tar": "7.4.3", + "tar": "7.5.6", "tough-cookie": "5.1.2", "uglify-js": "3.19.3", "uuid": "9.0.1", diff --git a/packages/node_modules/@node-red/editor-api/package.json b/packages/node_modules/@node-red/editor-api/package.json index 587b6e225..28b717bc4 100644 --- a/packages/node_modules/@node-red/editor-api/package.json +++ b/packages/node_modules/@node-red/editor-api/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-api", - "version": "4.1.3", + "version": "4.1.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/util": "4.1.3", - "@node-red/editor-client": "4.1.3", + "@node-red/util": "4.1.4", + "@node-red/editor-client": "4.1.4", "bcryptjs": "3.0.2", "body-parser": "1.20.4", "clone": "2.1.2", diff --git a/packages/node_modules/@node-red/editor-client/package.json b/packages/node_modules/@node-red/editor-client/package.json index 848603ffd..1e4a2d8f8 100644 --- a/packages/node_modules/@node-red/editor-client/package.json +++ b/packages/node_modules/@node-red/editor-client/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/editor-client", - "version": "4.1.3", + "version": "4.1.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss index 4b8873fee..00b79b54a 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/ui/common/editableList.scss @@ -21,7 +21,6 @@ padding: 2px 16px 2px 4px; font-size: 0.9em; } - overflow-x: hidden; } .red-ui-editableList-container { padding: 5px; diff --git a/packages/node_modules/@node-red/nodes/core/function/89-delay.js b/packages/node_modules/@node-red/nodes/core/function/89-delay.js index c44fa06c5..c4adb1f07 100644 --- a/packages/node_modules/@node-red/nodes/core/function/89-delay.js +++ b/packages/node_modules/@node-red/nodes/core/function/89-delay.js @@ -159,7 +159,8 @@ module.exports = function(RED) { if (node.pauseType === "delay") { node.on("input", function(msg, send, done) { var id = ourTimeout(function() { - node.idList.splice(node.idList.indexOf(id),1); + var idx = node.idList.indexOf(id); + if (idx !== -1) { node.idList.splice(idx, 1); } if (node.timeout > 1000) { node.status({fill:"blue",shape:"dot",text:node.idList.length}); } @@ -184,7 +185,8 @@ module.exports = function(RED) { } if (delayvar < 0) { delayvar = 0; } var id = ourTimeout(function() { - node.idList.splice(node.idList.indexOf(id),1); + var idx = node.idList.indexOf(id); + if (idx !== -1) { node.idList.splice(idx, 1); } if (node.idList.length === 0) { node.status({}); } send(msg); if (delayvar >= 0) { @@ -207,7 +209,8 @@ module.exports = function(RED) { node.on("input", function(msg, send, done) { var wait = node.randomFirst + (node.diff * Math.random()); var id = ourTimeout(function() { - node.idList.splice(node.idList.indexOf(id),1); + var idx = node.idList.indexOf(id); + if (idx !== -1) { node.idList.splice(idx, 1); } send(msg); if (node.timeout >= 1000) { node.status({fill:"blue",shape:"dot",text:node.idList.length}); diff --git a/packages/node_modules/@node-red/nodes/core/function/90-exec.js b/packages/node_modules/@node-red/nodes/core/function/90-exec.js index 02a908d4b..beb4d11d4 100644 --- a/packages/node_modules/@node-red/nodes/core/function/90-exec.js +++ b/packages/node_modules/@node-red/nodes/core/function/90-exec.js @@ -105,18 +105,26 @@ module.exports = function(RED) { } node.activeProcesses[child.pid] = child; child.stdout.on('data', function (data) { - if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) { - // console.log('[exec] stdout: ' + data,child.pid); - if (isUtf8(data)) { msg.payload = data.toString(); } - else { msg.payload = data; } - nodeSend([RED.util.cloneMessage(msg),null,null]); + try { + if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) { + // console.log('[exec] stdout: ' + data,child.pid); + if (isUtf8(data)) { msg.payload = data.toString(); } + else { msg.payload = data; } + nodeSend([RED.util.cloneMessage(msg),null,null]); + } + } catch (err) { + node.error(err.toString()); } }); child.stderr.on('data', function (data) { - if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) { - if (isUtf8(data)) { msg.payload = data.toString(); } - else { msg.payload = data; } - nodeSend([null,RED.util.cloneMessage(msg),null]); + try { + if (node.activeProcesses.hasOwnProperty(child.pid) && node.activeProcesses[child.pid] !== null) { + if (isUtf8(data)) { msg.payload = data.toString(); } + else { msg.payload = data; } + nodeSend([null,RED.util.cloneMessage(msg),null]); + } + } catch (err) { + node.error(err.toString()); } }); child.on('close', function (code,signal) { diff --git a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js index 451035a74..3600b016b 100644 --- a/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js +++ b/packages/node_modules/@node-red/nodes/core/network/10-mqtt.js @@ -227,6 +227,7 @@ module.exports = function(RED) { * Handle the payload / packet recieved in MQTT In and MQTT Sub nodes */ function subscriptionHandler(node, datatype ,topic, payload, packet) { + if (!packet) { packet = {}; } const msg = {topic:topic, payload:null, qos:packet.qos, retain:packet.retain}; const v5 = (node && node.brokerConn) ? node.brokerConn.v5() @@ -1074,12 +1075,16 @@ module.exports = function(RED) { if (!subscription.handler) { subscription.handler = function (mtopic, mpayload, mpacket) { - const sops = subscription.options ? subscription.options.properties : {} - const pops = mpacket.properties || {} - if (subIdsAvailable && pops.subscriptionIdentifier && sops.subscriptionIdentifier && (pops.subscriptionIdentifier !== sops.subscriptionIdentifier)) { - //do nothing as subscriptionIdentifier does not match - } else if (matchTopic(topic, mtopic)) { - subscription.callback && subscription.callback(mtopic, mpayload, mpacket) + try { + const sops = subscription.options ? subscription.options.properties : {} + const pops = (mpacket && mpacket.properties) || {} + if (subIdsAvailable && pops.subscriptionIdentifier && sops.subscriptionIdentifier && (pops.subscriptionIdentifier !== sops.subscriptionIdentifier)) { + //do nothing as subscriptionIdentifier does not match + } else if (matchTopic(topic, mtopic)) { + subscription.callback && subscription.callback(mtopic, mpayload, mpacket) + } + } catch (err) { + node.error("MQTT subscription handler error: " + err.toString()); } } } diff --git a/packages/node_modules/@node-red/nodes/core/network/22-websocket.js b/packages/node_modules/@node-red/nodes/core/network/22-websocket.js index f30fd91ce..3c877dc79 100644 --- a/packages/node_modules/@node-red/nodes/core/network/22-websocket.js +++ b/packages/node_modules/@node-red/nodes/core/network/22-websocket.js @@ -297,7 +297,11 @@ module.exports = function(RED) { } msg._session = {type:"websocket",id:id}; for (var i = 0; i < this._inputNodes.length; i++) { - this._inputNodes[i].send(msg); + try { + this._inputNodes[i].send(msg); + } catch (err) { + this.error(RED._("websocket.errors.send-error") + " " + err.toString()); + } } } diff --git a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js index 500bbe2c2..e91a9b7c3 100644 --- a/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js +++ b/packages/node_modules/@node-red/nodes/core/network/31-tcpin.js @@ -127,32 +127,36 @@ module.exports = function(RED) { connectionPool[id] = client; client.on('data', function (data) { - if (node.datatype != 'buffer') { - data = data.toString(node.datatype); - } - if (node.stream) { - var msg; - if ((node.datatype) === "utf8" && node.newline !== "") { - buffer = buffer+data; - var parts = buffer.split(node.newline); - for (var i = 0; i= node.splitc) { - if (clients[connection_id]) { - const msg = clients[connection_id].lastMsg || {}; - msg.payload = Buffer.alloc(i); - buf.copy(msg.payload,0,0,i); - if (node.ret === "string") { - try { msg.payload = msg.payload.toString(); } - catch(e) { node.error("Failed to create string", msg); } + // count bytes into a buffer... + else if (node.out == "count") { + buf[i] = data[j]; + i += 1; + if ( i >= node.splitc) { + if (clients[connection_id]) { + const msg = clients[connection_id].lastMsg || {}; + msg.payload = Buffer.alloc(i); + buf.copy(msg.payload,0,0,i); + if (node.ret === "string") { + try { msg.payload = msg.payload.toString(); } + catch(e) { node.error("Failed to create string", msg); } + } + nodeSend(msg); + if (clients[connection_id].client) { + node.status({}); + clients[connection_id].client.destroy(); + delete clients[connection_id]; + } + i = 0; } - nodeSend(msg); - if (clients[connection_id].client) { - node.status({}); - clients[connection_id].client.destroy(); - delete clients[connection_id]; - } - i = 0; } } - } - // look for a char - else { - buf[i] = data[j]; - i += 1; - if (data[j] == node.splitc) { - if (clients[connection_id]) { - const msg = clients[connection_id].lastMsg || {}; - msg.payload = Buffer.alloc(i); - buf.copy(msg.payload,0,0,i); - if (node.ret === "string") { - try { msg.payload = msg.payload.toString(); } - catch(e) { node.error("Failed to create string", msg); } + // look for a char + else { + buf[i] = data[j]; + i += 1; + if (data[j] == node.splitc) { + if (clients[connection_id]) { + const msg = clients[connection_id].lastMsg || {}; + msg.payload = Buffer.alloc(i); + buf.copy(msg.payload,0,0,i); + if (node.ret === "string") { + try { msg.payload = msg.payload.toString(); } + catch(e) { node.error("Failed to create string", msg); } + } + nodeSend(msg); + if (clients[connection_id].client) { + node.status({}); + clients[connection_id].client.destroy(); + delete clients[connection_id]; + } + i = 0; } - nodeSend(msg); - if (clients[connection_id].client) { - node.status({}); - clients[connection_id].client.destroy(); - delete clients[connection_id]; - } - i = 0; } } } } + } catch (err) { + node.error(RED._("tcpin.errors.error",{error:err.toString()})); } }); diff --git a/packages/node_modules/@node-red/nodes/core/network/32-udp.js b/packages/node_modules/@node-red/nodes/core/network/32-udp.js index d42d3a7c3..27709c2bd 100644 --- a/packages/node_modules/@node-red/nodes/core/network/32-udp.js +++ b/packages/node_modules/@node-red/nodes/core/network/32-udp.js @@ -98,15 +98,19 @@ module.exports = function(RED) { }); server.on('message', function (message, remote) { - var msg; - if (node.datatype =="base64") { - msg = { payload:message.toString('base64'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port }; - } else if (node.datatype =="utf8") { - msg = { payload:message.toString('utf8'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port }; - } else { - msg = { payload:message, fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port }; + try { + var msg; + if (node.datatype =="base64") { + msg = { payload:message.toString('base64'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port }; + } else if (node.datatype =="utf8") { + msg = { payload:message.toString('utf8'), fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port }; + } else { + msg = { payload:message, fromip:remote.address+':'+remote.port, ip:remote.address, port:remote.port }; + } + node.send(msg); + } catch (err) { + node.error(RED._("udp.errors.error",{error:err.toString()})); } - node.send(msg); }); server.on('listening', function () { diff --git a/packages/node_modules/@node-red/nodes/package.json b/packages/node_modules/@node-red/nodes/package.json index 298cddcc8..6fe1b00ab 100644 --- a/packages/node_modules/@node-red/nodes/package.json +++ b/packages/node_modules/@node-red/nodes/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/nodes", - "version": "4.1.3", + "version": "4.1.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/@node-red/registry/lib/externalModules.js b/packages/node_modules/@node-red/registry/lib/externalModules.js index bec93491c..0f4fbf009 100644 --- a/packages/node_modules/@node-red/registry/lib/externalModules.js +++ b/packages/node_modules/@node-red/registry/lib/externalModules.js @@ -122,7 +122,7 @@ function importModule(module) { throw e; } const externalModuleDir = getInstallDir(); - const moduleDir = path.join(externalModuleDir,"node_modules",module); + const moduleDir = path.join(externalModuleDir,"node_modules",parsedModule.module); // To handle both CJS and ESM we need to resolve the module to the // specific file that is loaded when the module is required/imported // As this won't be on the natural module search path, we use createRequire diff --git a/packages/node_modules/@node-red/registry/package.json b/packages/node_modules/@node-red/registry/package.json index 95269f530..869573424 100644 --- a/packages/node_modules/@node-red/registry/package.json +++ b/packages/node_modules/@node-red/registry/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/registry", - "version": "4.1.3", + "version": "4.1.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,11 +16,11 @@ } ], "dependencies": { - "@node-red/util": "4.1.3", + "@node-red/util": "4.1.4", "clone": "2.1.2", "fs-extra": "11.3.0", "semver": "7.7.1", - "tar": "7.4.3", + "tar": "7.5.6", "uglify-js": "3.19.3" } } diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js index 0b1ed349b..f31504214 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/Node.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/Node.js @@ -322,6 +322,7 @@ Node.prototype.close = function(removed) { // The callback takes a 'done' callback and (maybe) the removed flag promises.push( new Promise((resolve) => { + var resolved = false; try { var args = []; if (callback.length === 2) { @@ -329,13 +330,19 @@ Node.prototype.close = function(removed) { args.push(!!removed); } args.push(() => { - resolve(); + if (!resolved) { + resolved = true; + resolve(); + } }); callback.apply(node, args); } catch(err) { // TODO: error thrown in node async close callback // We've never logged this properly. - resolve(); + if (!resolved) { + resolved = true; + resolve(); + } } }) ); diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js b/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js index ef499f0f9..5697f329d 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/context/localfilesystem.js @@ -155,6 +155,7 @@ function LocalFileSystem(config){ } this.pendingWrites = {}; this.knownCircularRefs = {}; + this.closing = false; if (config.hasOwnProperty('flushInterval')) { this.flushInterval = Math.max(0,config.flushInterval) * 1000; @@ -233,16 +234,19 @@ LocalFileSystem.prototype.open = function(){ LocalFileSystem.prototype.close = function(){ var self = this; - if (this.cache && this._pendingWriteTimeout) { - clearTimeout(this._pendingWriteTimeout); - delete this._pendingWriteTimeout; + this.closing = true; + if (this.cache) { + if (this._pendingWriteTimeout) { + clearTimeout(this._pendingWriteTimeout); + delete this._pendingWriteTimeout; + } this.flushInterval = 0; + // Always flush pending writes on close, even if no timeout was pending self.writePromise = self.writePromise.then(function(){ return self._flushPendingWrites.call(self).catch(function(err) { log.error(log._("context.localfilesystem.error-write",{message:err.toString()})); }); }); - } return this.writePromise; } @@ -298,8 +302,9 @@ LocalFileSystem.prototype.set = function(scope, key, value, callback) { if (this.cache) { this.cache.set(scope,key,value,callback); this.pendingWrites[scope] = true; - if (this._pendingWriteTimeout) { - // there's a pending write which will handle this + if (this._pendingWriteTimeout || this.closing) { + // there's a pending write which will handle this, + // or we're closing and the close() flush will handle it return; } else { this._pendingWriteTimeout = setTimeout(function() { diff --git a/packages/node_modules/@node-red/runtime/package.json b/packages/node_modules/@node-red/runtime/package.json index ccf2c55ad..20e07ff1b 100644 --- a/packages/node_modules/@node-red/runtime/package.json +++ b/packages/node_modules/@node-red/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/runtime", - "version": "4.1.3", + "version": "4.1.4", "license": "Apache-2.0", "main": "./lib/index.js", "repository": { @@ -16,8 +16,8 @@ } ], "dependencies": { - "@node-red/registry": "4.1.3", - "@node-red/util": "4.1.3", + "@node-red/registry": "4.1.4", + "@node-red/util": "4.1.4", "async-mutex": "0.5.0", "clone": "2.1.2", "cronosjs": "1.7.1", diff --git a/packages/node_modules/@node-red/util/package.json b/packages/node_modules/@node-red/util/package.json index 13c20261e..322a99054 100644 --- a/packages/node_modules/@node-red/util/package.json +++ b/packages/node_modules/@node-red/util/package.json @@ -1,6 +1,6 @@ { "name": "@node-red/util", - "version": "4.1.3", + "version": "4.1.4", "license": "Apache-2.0", "repository": { "type": "git", diff --git a/packages/node_modules/node-red/package.json b/packages/node_modules/node-red/package.json index 82c944518..027a8609a 100644 --- a/packages/node_modules/node-red/package.json +++ b/packages/node_modules/node-red/package.json @@ -1,6 +1,6 @@ { "name": "node-red", - "version": "4.1.3", + "version": "4.1.4", "description": "Low-code programming for event-driven applications", "homepage": "https://nodered.org", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "flow" ], "dependencies": { - "@node-red/editor-api": "4.1.3", - "@node-red/runtime": "4.1.3", - "@node-red/util": "4.1.3", - "@node-red/nodes": "4.1.3", + "@node-red/editor-api": "4.1.4", + "@node-red/runtime": "4.1.4", + "@node-red/util": "4.1.4", + "@node-red/nodes": "4.1.4", "basic-auth": "2.0.1", "bcryptjs": "3.0.2", "cors": "2.8.5", diff --git a/test/unit/@node-red/registry/lib/externalModules_spec.js b/test/unit/@node-red/registry/lib/externalModules_spec.js index 2158f93f7..3a04d954f 100644 --- a/test/unit/@node-red/registry/lib/externalModules_spec.js +++ b/test/unit/@node-red/registry/lib/externalModules_spec.js @@ -344,6 +344,36 @@ describe("externalModules api", function() { should.exist(result); should.exist(result.existsSync); }) + it("imports external modules using export aliasing", async function() { + const externalModulesDir = path.join(homeDir,"externalModules"); + await fs.ensureDir(externalModulesDir); + externalModules.init({userDir: externalModulesDir, get:()=>{}, set:()=>{}}); + + await fs.writeFile(path.join(externalModulesDir,"package.json"),`{ +"name": "Node-RED-External-Modules", +"description": "These modules are automatically installed by Node-RED to use in Function nodes.", +"version": "1.0.0", +"private": true, +"dependencies": {"fake-pkg":"1.0.0"} +}`) + + const packageDir = path.join(externalModulesDir,"node_modules","fake-pkg"); + await fs.ensureDir(path.join(packageDir,"dist")); + await fs.writeFile(path.join(packageDir,"package.json"),`{ +"name": "fake-pkg", +"version": "1.0.0", +"exports": { + "./M.js": "./dist/M.js" +} +}`) + await fs.writeFile(path.join(packageDir,"dist","M.js"),"module.exports = 42;\n"); + + await externalModules.checkFlowDependencies([]); + + const importedValue = await externalModules.import("fake-pkg/M.js") + const normalizedValue = importedValue && importedValue.default !== undefined ? importedValue.default : importedValue; + normalizedValue.should.equal(42); + }) it("rejects unknown modules", async function() { externalModules.init({userDir: homeDir, get:()=>{}, set:()=>{}}); try {