Comparing version 1.2.1 to 1.2.2
NoFlo ChangeLog | ||
=============== | ||
## 1.2.2 (September 17th 2020) | ||
* Added initial support for components written in TypeScript. Requires the `typescript` module to be installed | ||
* NoFlo ComponentLoader can now tell the supported programming languages with the `getLanguages` method | ||
* Components written with `setSource` now return the original untranspiled source code with `getSource` also on Node.js | ||
## 1.2.1 (September 16th 2020) | ||
@@ -5,0 +11,0 @@ |
@@ -144,2 +144,7 @@ "use strict"; | ||
registerLoader.register(this, function (err) { | ||
_this2.processing = false; | ||
_this2.ready = true; | ||
_this2.emit('ready', true); | ||
if (err) { | ||
@@ -150,7 +155,2 @@ callback(err); | ||
_this2.processing = false; | ||
_this2.ready = true; | ||
_this2.emit('ready', true); | ||
callback(null, _this2.components); | ||
@@ -481,2 +481,14 @@ }); | ||
registerLoader.getSource(this, name, callback); | ||
} // `getLanguages` gets a list of component programming languages supported by the `setSource` | ||
// method on this runtime instance. | ||
}, { | ||
key: "getLanguages", | ||
value: function getLanguages() { | ||
if (!registerLoader.getLanguages) { | ||
// This component loader doesn't support the method, default to normal JS | ||
return ['javascript', 'es2015']; | ||
} | ||
return registerLoader.getLanguages(); | ||
} | ||
@@ -483,0 +495,0 @@ }, { |
@@ -17,13 +17,272 @@ "use strict"; | ||
var fbpGraph = require('fbp-graph'); // We allow components to be un-compiled CoffeeScript | ||
var fbpGraph = require('fbp-graph'); | ||
var utils = require('../Utils'); // Type loading CoffeeScript compiler | ||
var CoffeeScript = require('coffeescript'); | ||
var utils = require('../Utils'); | ||
var CoffeeScript; | ||
if (typeof CoffeeScript.register !== 'undefined') { | ||
CoffeeScript.register(); | ||
try { | ||
// eslint-disable-next-line import/no-unresolved | ||
CoffeeScript = require('coffeescript'); | ||
if (typeof CoffeeScript.register !== 'undefined') { | ||
CoffeeScript.register(); | ||
} | ||
} catch (e) {// If there is no CoffeeScript compiler installed, we simply don't support compiling | ||
} // Try loading TypeScript compiler | ||
var typescript; | ||
try { | ||
// eslint-disable-next-line import/no-unresolved,import/no-extraneous-dependencies | ||
typescript = require('typescript'); | ||
} catch (e) {// If there is no TypeScript compiler installed, we simply don't support compiling | ||
} | ||
function transpileSource(packageId, name, source, language, callback) { | ||
var src; | ||
switch (language) { | ||
case 'coffeescript': | ||
{ | ||
if (!CoffeeScript) { | ||
callback(new Error("Unsupported component source language ".concat(language, " for ").concat(packageId, "/").concat(name, ": no CoffeeScript compiler installed"))); | ||
} | ||
try { | ||
src = CoffeeScript.compile(source, { | ||
bare: true | ||
}); | ||
} catch (err) { | ||
callback(err); | ||
return; | ||
} | ||
break; | ||
} | ||
case 'typescript': | ||
{ | ||
if (!typescript) { | ||
callback(new Error("Unsupported component source language ".concat(language, " for ").concat(packageId, "/").concat(name, ": no TypeScript compiler installed"))); | ||
} | ||
try { | ||
src = typescript.transpile(source, { | ||
compilerOptions: { | ||
module: typescript.ModuleKind.CommonJS | ||
} | ||
}); | ||
} catch (err) { | ||
callback(err); | ||
return; | ||
} | ||
break; | ||
} | ||
case 'es6': | ||
case 'es2015': | ||
case 'js': | ||
case 'javascript': | ||
{ | ||
src = source; | ||
break; | ||
} | ||
default: | ||
{ | ||
callback(new Error("Unsupported component source language ".concat(language, " for ").concat(packageId, "/").concat(name))); | ||
return; | ||
} | ||
} | ||
callback(null, src); | ||
} | ||
function evaluateModule(baseDir, packageId, name, source, callback) { | ||
var Module = require('module'); | ||
var implementation; | ||
try { | ||
// Use the Node.js module API to evaluate in the correct directory context | ||
var modulePath = path.resolve(baseDir, "./components/".concat(name, ".js")); | ||
var moduleImpl = new Module(modulePath, module); | ||
moduleImpl.paths = Module._nodeModulePaths(path.dirname(modulePath)); | ||
moduleImpl.filename = modulePath; | ||
moduleImpl._compile(source, modulePath); | ||
implementation = moduleImpl.exports; | ||
} catch (e) { | ||
callback(e); | ||
return; | ||
} | ||
if (typeof implementation !== 'function' && typeof implementation.getComponent !== 'function') { | ||
callback(new Error("Provided source for ".concat(packageId, "/").concat(name, " failed to create a runnable component"))); | ||
return; | ||
} | ||
callback(null, implementation); | ||
} | ||
function registerSources(loader, packageId, name, source, language) { | ||
if (!loader.sourcesForComponents) { | ||
// eslint-disable-next-line no-param-reassign | ||
loader.sourcesForComponents = {}; | ||
} | ||
var componentName = "".concat(packageId, "/").concat(name); // eslint-disable-next-line no-param-reassign | ||
loader.sourcesForComponents[componentName] = { | ||
language: language, | ||
source: source | ||
}; | ||
} | ||
function transpileAndRegisterForModule(loader, module, component, source, language, callback) { | ||
transpileSource(module.name, component.name, source, language, function (transpileError, src) { | ||
if (transpileError) { | ||
callback(transpileError); | ||
return; | ||
} | ||
var moduleBase = path.resolve(loader.baseDir, module.base); | ||
evaluateModule(moduleBase, module.name, component.name, src, function (evalError, implementation) { | ||
if (evalError) { | ||
callback(evalError); | ||
return; | ||
} | ||
registerSources(loader, module.name, component.name, source, language); | ||
loader.registerComponent(module.name, component.name, implementation, callback); | ||
}); | ||
}); | ||
} | ||
exports.setSource = function setSource(loader, packageId, name, source, language, callback) { | ||
transpileAndRegisterForModule(loader, { | ||
name: packageId, | ||
base: '' | ||
}, { | ||
name: name | ||
}, source, language, callback); | ||
}; | ||
exports.getSource = function getSource(loader, name, callback) { | ||
var componentName = name; | ||
var component = loader.components[name]; | ||
if (!component) { | ||
// Try an alias | ||
var keys = Object.keys(loader.components); | ||
for (var i = 0; i < keys.length; i += 1) { | ||
var key = keys[i]; | ||
if (key.split('/')[1] === name) { | ||
component = loader.components[key]; | ||
componentName = key; | ||
break; | ||
} | ||
} | ||
if (!component) { | ||
callback(new Error("Component ".concat(componentName, " not installed"))); | ||
return; | ||
} | ||
} | ||
var nameParts = componentName.split('/'); | ||
if (nameParts.length === 1) { | ||
nameParts[1] = nameParts[0]; | ||
nameParts[0] = ''; | ||
} | ||
if (loader.isGraph(component)) { | ||
if (_typeof(component) === 'object') { | ||
if (typeof component.toJSON === 'function') { | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
code: JSON.stringify(component.toJSON()), | ||
language: 'json' | ||
}); | ||
return; | ||
} | ||
callback(new Error("Can't provide source for ".concat(componentName, ". Not a file"))); | ||
return; | ||
} | ||
fbpGraph.graph.loadFile(component, function (err, graph) { | ||
if (err) { | ||
callback(err); | ||
return; | ||
} | ||
if (!graph) { | ||
callback(new Error('Unable to load graph')); | ||
return; | ||
} | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
code: JSON.stringify(graph.toJSON()), | ||
language: 'json' | ||
}); | ||
}); | ||
return; | ||
} | ||
if (loader.sourcesForComponents && loader.sourcesForComponents[componentName]) { | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
code: loader.sourcesForComponents[componentName].source, | ||
language: loader.sourcesForComponents[componentName].language | ||
}); | ||
return; | ||
} | ||
if (typeof component !== 'string') { | ||
callback(new Error("Can't provide source for ".concat(componentName, ". Not a file"))); | ||
return; | ||
} | ||
fs.readFile(component, 'utf-8', function (err, code) { | ||
if (err) { | ||
callback(err); | ||
return; | ||
} | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
language: utils.guessLanguageFromFilename(component), | ||
code: code | ||
}); | ||
}); | ||
}; | ||
exports.getLanguages = function getLanguages() { | ||
var languages = ['javascript', 'es2015']; | ||
if (CoffeeScript) { | ||
languages.push('coffeescript'); | ||
} | ||
if (typescript) { | ||
languages.push('typescript'); | ||
} | ||
return languages; | ||
}; | ||
function registerCustomLoaders(loader, componentLoaders, callback) { | ||
@@ -52,3 +311,3 @@ if (!componentLoaders.length) { | ||
var componentLoaders = []; | ||
compatible.forEach(function (m) { | ||
Promise.all(compatible.map(function (m) { | ||
if (m.icon) { | ||
@@ -63,7 +322,39 @@ loader.setLibraryIcon(m.name, m.icon); | ||
m.components.forEach(function (c) { | ||
loader.registerComponent(m.name, c.name, path.resolve(loader.baseDir, c.path)); | ||
}); | ||
}); | ||
registerCustomLoaders(loader, componentLoaders, callback); | ||
return Promise.all(m.components.map(function (c) { | ||
return new Promise(function (resolve, reject) { | ||
var language = utils.guessLanguageFromFilename(c.path); | ||
if (language === 'typescript') { | ||
// We can't require a TypeScript module, go the setSource route | ||
fs.readFile(path.resolve(loader.baseDir, c.path), 'utf-8', function (fsErr, source) { | ||
if (fsErr) { | ||
reject(fsErr); | ||
return; | ||
} | ||
transpileAndRegisterForModule(loader, m, c, source, language, function (err) { | ||
if (err) { | ||
reject(err); | ||
return; | ||
} | ||
resolve(); | ||
}); | ||
}); | ||
return; | ||
} | ||
loader.registerComponent(m.name, c.name, path.resolve(loader.baseDir, c.path), function (err) { | ||
if (err) { | ||
reject(err); | ||
return; | ||
} | ||
resolve(); | ||
}); | ||
}); | ||
})); | ||
})).then(function () { | ||
registerCustomLoaders(loader, componentLoaders, callback); | ||
}, callback); | ||
} | ||
@@ -233,131 +524,2 @@ | ||
callback(null, instance); | ||
}; | ||
exports.setSource = function setSource(loader, packageId, name, source, language, callback) { | ||
var Module = require('module'); | ||
var src = source; | ||
if (language === 'coffeescript') { | ||
try { | ||
src = CoffeeScript.compile(src, { | ||
bare: true | ||
}); | ||
} catch (err) { | ||
callback(err); | ||
return; | ||
} | ||
} | ||
var implementation; | ||
try { | ||
// Use the Node.js module API to evaluate in the correct directory context | ||
var modulePath = path.resolve(loader.baseDir, "./components/".concat(name, ".js")); | ||
var moduleImpl = new Module(modulePath, module); | ||
moduleImpl.paths = Module._nodeModulePaths(path.dirname(modulePath)); | ||
moduleImpl.filename = modulePath; | ||
moduleImpl._compile(src, modulePath); | ||
implementation = moduleImpl.exports; | ||
} catch (err) { | ||
callback(err); | ||
return; | ||
} | ||
if (typeof implementation !== 'function' && typeof implementation.getComponent !== 'function') { | ||
callback(new Error('Provided source failed to create a runnable component')); | ||
return; | ||
} | ||
loader.registerComponent(packageId, name, implementation, callback); | ||
}; | ||
exports.getSource = function getSource(loader, name, callback) { | ||
var componentName = name; | ||
var component = loader.components[name]; | ||
if (!component) { | ||
// Try an alias | ||
var keys = Object.keys(loader.components); | ||
for (var i = 0; i < keys.length; i += 1) { | ||
var key = keys[i]; | ||
if (key.split('/')[1] === name) { | ||
component = loader.components[key]; | ||
componentName = key; | ||
break; | ||
} | ||
} | ||
if (!component) { | ||
callback(new Error("Component ".concat(componentName, " not installed"))); | ||
return; | ||
} | ||
} | ||
var nameParts = componentName.split('/'); | ||
if (nameParts.length === 1) { | ||
nameParts[1] = nameParts[0]; | ||
nameParts[0] = ''; | ||
} | ||
if (loader.isGraph(component)) { | ||
if (_typeof(component) === 'object') { | ||
if (typeof component.toJSON === 'function') { | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
code: JSON.stringify(component.toJSON()), | ||
language: 'json' | ||
}); | ||
return; | ||
} | ||
callback(new Error("Can't provide source for ".concat(componentName, ". Not a file"))); | ||
return; | ||
} | ||
fbpGraph.graph.loadFile(component, function (err, graph) { | ||
if (err) { | ||
callback(err); | ||
return; | ||
} | ||
if (!graph) { | ||
callback(new Error('Unable to load graph')); | ||
return; | ||
} | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
code: JSON.stringify(graph.toJSON()), | ||
language: 'json' | ||
}); | ||
}); | ||
return; | ||
} | ||
if (typeof component !== 'string') { | ||
callback(new Error("Can't provide source for ".concat(componentName, ". Not a file"))); | ||
return; | ||
} | ||
fs.readFile(component, 'utf-8', function (err, code) { | ||
if (err) { | ||
callback(err); | ||
return; | ||
} | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
language: utils.guessLanguageFromFilename(component), | ||
code: code | ||
}); | ||
}); | ||
}; |
@@ -17,2 +17,6 @@ "use strict"; | ||
if (/.*\.ts$/.test(filename)) { | ||
return 'typescript'; | ||
} | ||
return 'javascript'; | ||
@@ -19,0 +23,0 @@ } |
@@ -11,3 +11,3 @@ { | ||
"author": "Henri Bergius <henri.bergius@iki.fi>", | ||
"version": "1.2.1", | ||
"version": "1.2.2", | ||
"license": "MIT", | ||
@@ -22,3 +22,3 @@ "engines": { | ||
"fbp-graph": "^0.4.0", | ||
"fbp-manifest": "^0.2.0", | ||
"fbp-manifest": "^0.2.2", | ||
"get-function-params": "^2.0.6" | ||
@@ -41,4 +41,5 @@ }, | ||
"mocha": "^7.2.0", | ||
"noflo-component-loader": "^0.3.2", | ||
"noflo-component-loader": "^0.3.3", | ||
"nyc": "^15.1.0", | ||
"typescript": "^4.0.2", | ||
"webpack": "^4.44.1", | ||
@@ -45,0 +46,0 @@ "webpack-cli": "^3.3.12" |
@@ -379,8 +379,2 @@ /* eslint-disable | ||
describe('reading sources', () => { | ||
before(function () { | ||
// getSource not implemented in webpack loader yet | ||
if (noflo.isBrowser()) { | ||
this.skip(); | ||
} | ||
}); | ||
it('should be able to provide source code for a component', (done) => { | ||
@@ -409,2 +403,7 @@ l.getSource('Graph', (err, component) => { | ||
it('should return an error for non-file components', (done) => { | ||
if (noflo.isBrowser()) { | ||
// Browser runtime actually supports this via toString() | ||
done(); | ||
return; | ||
} | ||
l.getSource('foo/Split', (err) => { | ||
@@ -465,2 +464,15 @@ chai.expect(err).to.be.an('error'); | ||
}); | ||
describe('getting supported languages', () => { | ||
it('should include the expected ones', () => { | ||
const expectedLanguages = ['es2015', 'javascript']; | ||
if (!noflo.isBrowser()) { | ||
expectedLanguages.push('coffeescript'); | ||
expectedLanguages.push('typescript'); | ||
} | ||
expectedLanguages.sort(); | ||
const supportedLanguages = l.getLanguages(); | ||
supportedLanguages.sort(); | ||
chai.expect(supportedLanguages).to.eql(expectedLanguages); | ||
}); | ||
}); | ||
describe('writing sources', () => { | ||
@@ -520,2 +532,13 @@ let localNofloPath; | ||
}); | ||
it('should return sources in the same format', (done) => { | ||
l.getSource('foo/RepeatData', (err, source) => { | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(source.language).to.equal('javascript'); | ||
chai.expect(source.code).to.equal(workingSource); | ||
done(); | ||
}); | ||
}); | ||
it('should be able to set the source for non-ready ComponentLoader', function (done) { | ||
@@ -529,4 +552,3 @@ this.timeout(10000); | ||
before(function () { | ||
// PhantomJS doesn't work with ES6 | ||
if (noflo.isBrowser()) { | ||
if (l.getLanguages().indexOf('es2015') === -1) { | ||
this.skip(); | ||
@@ -553,3 +575,3 @@ } | ||
} | ||
l.setSource('foo', 'RepeatDataES6', workingSource, 'es6', (err) => { | ||
l.setSource('foo', 'RepeatDataES6', workingSource, 'es2015', (err) => { | ||
if (err) { | ||
@@ -583,8 +605,17 @@ done(err); | ||
}); | ||
it('should return sources in the same format', (done) => { | ||
l.getSource('foo/RepeatDataES6', (err, source) => { | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(source.language).to.equal('es2015'); | ||
chai.expect(source.code).to.equal(workingSource); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('with CoffeeScript', () => { | ||
before(function () { | ||
// CoffeeScript tests work in browser only if we have CoffeeScript | ||
// compiler loaded | ||
if (noflo.isBrowser() && !window.CoffeeScript) { | ||
if (l.getLanguages().indexOf('coffeescript') === -1) { | ||
this.skip(); | ||
@@ -637,3 +668,79 @@ } | ||
}); | ||
it('should return sources in the same format', (done) => { | ||
l.getSource('foo/RepeatDataCoffee', (err, source) => { | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(source.language).to.equal('coffeescript'); | ||
chai.expect(source.code).to.equal(workingSource); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('with TypeScript', () => { | ||
before(function () { | ||
if (l.getLanguages().indexOf('typescript') === -1) { | ||
this.skip(); | ||
} | ||
}); | ||
let workingSource = `\ | ||
import { Component } from 'noflo'; | ||
exports.getComponent = (): Component => { | ||
const c = new noflo.Component(); | ||
c.inPorts.add('in'); | ||
c.outPorts.add('out'); | ||
c.process((input, output): void => { | ||
output.sendDone(input.get('in')); | ||
}); | ||
return c; | ||
}; | ||
`; | ||
it('should be able to set the source', function (done) { | ||
this.timeout(10000); | ||
if (!noflo.isBrowser()) { | ||
workingSource = workingSource.replace("'noflo'", localNofloPath); | ||
} | ||
l.setSource('foo', 'RepeatDataTypeScript', workingSource, 'typescript', (err) => { | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
done(); | ||
}); | ||
}); | ||
it('should be a loadable component', (done) => { | ||
l.load('foo/RepeatDataTypeScript', (err, inst) => { | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(inst).to.be.an('object'); | ||
chai.expect(inst.inPorts).to.contain.keys(['in']); | ||
chai.expect(inst.outPorts).to.contain.keys(['out']); | ||
const ins = new noflo.internalSocket.InternalSocket(); | ||
const out = new noflo.internalSocket.InternalSocket(); | ||
inst.inPorts.in.attach(ins); | ||
inst.outPorts.out.attach(out); | ||
out.on('ip', (ip) => { | ||
chai.expect(ip.type).to.equal('data'); | ||
chai.expect(ip.data).to.equal('TypeScript'); | ||
done(); | ||
}); | ||
ins.send('TypeScript'); | ||
}); | ||
}); | ||
it('should return sources in the same format', (done) => { | ||
l.getSource('foo/RepeatDataTypeScript', (err, source) => { | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(source.language).to.equal('typescript'); | ||
chai.expect(source.code).to.equal(workingSource); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
@@ -793,5 +900,8 @@ describe('with non-working code', () => { | ||
}); | ||
it('should be able to load a local component', (done) => { | ||
it('should be able to load a local JavaScript component', (done) => { | ||
l.load('componentloader/Output', (err, instance) => { | ||
chai.expect(err).to.be.a('null'); | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(instance.description).to.equal('Output stuff'); | ||
@@ -802,5 +912,30 @@ chai.expect(instance.icon).to.equal('cloud'); | ||
}); | ||
it('should be able to load a component from a dependency', (done) => { | ||
it('should be able to load a local CoffeeScript component', (done) => { | ||
l.load('componentloader/RepeatAsync', (err, instance) => { | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(instance.description).to.equal('Repeat stuff async'); | ||
chai.expect(instance.icon).to.equal('forward'); | ||
done(); | ||
}); | ||
}); | ||
it('should be able to load a local TypeScript component', (done) => { | ||
l.load('componentloader/Repeat', (err, instance) => { | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(instance.description).to.equal('Repeat stuff'); | ||
chai.expect(instance.icon).to.equal('cloud'); | ||
done(); | ||
}); | ||
}); | ||
it('should be able to load a JavaScript component from a dependency', (done) => { | ||
l.load('example/Forward', (err, instance) => { | ||
chai.expect(err).to.be.a('null'); | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(instance.description).to.equal('Forward stuff'); | ||
@@ -811,5 +946,30 @@ chai.expect(instance.icon).to.equal('car'); | ||
}); | ||
it('should be able to load a CoffeeScript component from a dependency', (done) => { | ||
l.load('example/RepeatAsync', (err, instance) => { | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(instance.description).to.equal('Repeat stuff async'); | ||
chai.expect(instance.icon).to.equal('forward'); | ||
done(); | ||
}); | ||
}); | ||
it('should be able to load a TypeScript component from a dependency', (done) => { | ||
l.load('example/Repeat', (err, instance) => { | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(instance.description).to.equal('Repeat stuff'); | ||
chai.expect(instance.icon).to.equal('car'); | ||
done(); | ||
}); | ||
}); | ||
it('should be able to load a dynamically registered component from a dependency', (done) => { | ||
l.load('example/Hello', (err, instance) => { | ||
chai.expect(err).to.be.a('null'); | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(instance.description).to.equal('Hello stuff'); | ||
@@ -822,3 +982,6 @@ chai.expect(instance.icon).to.equal('bicycle'); | ||
l.load('Graph', (err, instance) => { | ||
chai.expect(err).to.be.a('null'); | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(instance.icon).to.equal('sitemap'); | ||
@@ -907,3 +1070,6 @@ done(); | ||
l.load('componentloader/Output', (err, instance) => { | ||
chai.expect(err).to.be.a('null'); | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(instance.description).to.equal('Output stuff'); | ||
@@ -916,3 +1082,6 @@ chai.expect(instance.icon).to.equal('cloud'); | ||
l.load('example/Forward', (err, instance) => { | ||
chai.expect(err).to.be.a('null'); | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(instance.description).to.equal('Forward stuff'); | ||
@@ -925,3 +1094,6 @@ chai.expect(instance.icon).to.equal('car'); | ||
l.load('example/Hello', (err, instance) => { | ||
chai.expect(err).to.be.a('null'); | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(instance.description).to.equal('Hello stuff'); | ||
@@ -934,3 +1106,6 @@ chai.expect(instance.icon).to.equal('bicycle'); | ||
l.load('Graph', (err, instance) => { | ||
chai.expect(err).to.be.a('null'); | ||
if (err) { | ||
done(err); | ||
return; | ||
} | ||
chai.expect(instance.icon).to.equal('sitemap'); | ||
@@ -937,0 +1112,0 @@ done(); |
@@ -8,10 +8,10 @@ const noflo = require('../../../../src/lib/NoFlo'); | ||
{ datatype: 'string' }); | ||
c.inPorts.add('out', | ||
c.outPorts.add('out', | ||
{ datatype: 'string' }); | ||
c.process = function (input, output) { | ||
c.process((input, output) => { | ||
const data = input.getData('in'); | ||
console.log(data); | ||
output.sendDone({ out: data }); | ||
}; | ||
}); | ||
return c; | ||
}; |
@@ -75,2 +75,5 @@ // NoFlo - Flow-Based Programming for JavaScript | ||
registerLoader.register(this, (err) => { | ||
this.processing = false; | ||
this.ready = true; | ||
this.emit('ready', true); | ||
if (err) { | ||
@@ -80,5 +83,2 @@ callback(err); | ||
} | ||
this.processing = false; | ||
this.ready = true; | ||
this.emit('ready', true); | ||
callback(null, this.components); | ||
@@ -351,2 +351,12 @@ }); | ||
// `getLanguages` gets a list of component programming languages supported by the `setSource` | ||
// method on this runtime instance. | ||
getLanguages() { | ||
if (!registerLoader.getLanguages) { | ||
// This component loader doesn't support the method, default to normal JS | ||
return ['javascript', 'es2015']; | ||
} | ||
return registerLoader.getLanguages(); | ||
} | ||
clear() { | ||
@@ -353,0 +363,0 @@ this.components = null; |
@@ -12,10 +12,234 @@ /* eslint-disable | ||
// We allow components to be un-compiled CoffeeScript | ||
const CoffeeScript = require('coffeescript'); | ||
const utils = require('../Utils'); | ||
if (typeof CoffeeScript.register !== 'undefined') { | ||
CoffeeScript.register(); | ||
// Type loading CoffeeScript compiler | ||
let CoffeeScript; | ||
try { | ||
// eslint-disable-next-line import/no-unresolved | ||
CoffeeScript = require('coffeescript'); | ||
if (typeof CoffeeScript.register !== 'undefined') { | ||
CoffeeScript.register(); | ||
} | ||
} catch (e) { | ||
// If there is no CoffeeScript compiler installed, we simply don't support compiling | ||
} | ||
// Try loading TypeScript compiler | ||
let typescript; | ||
try { | ||
// eslint-disable-next-line import/no-unresolved,import/no-extraneous-dependencies | ||
typescript = require('typescript'); | ||
} catch (e) { | ||
// If there is no TypeScript compiler installed, we simply don't support compiling | ||
} | ||
function transpileSource(packageId, name, source, language, callback) { | ||
let src; | ||
switch (language) { | ||
case 'coffeescript': { | ||
if (!CoffeeScript) { | ||
callback(new Error(`Unsupported component source language ${language} for ${packageId}/${name}: no CoffeeScript compiler installed`)); | ||
} | ||
try { | ||
src = CoffeeScript.compile(source, { | ||
bare: true, | ||
}); | ||
} catch (err) { | ||
callback(err); | ||
return; | ||
} | ||
break; | ||
} | ||
case 'typescript': { | ||
if (!typescript) { | ||
callback(new Error(`Unsupported component source language ${language} for ${packageId}/${name}: no TypeScript compiler installed`)); | ||
} | ||
try { | ||
src = typescript.transpile(source, { | ||
compilerOptions: { | ||
module: typescript.ModuleKind.CommonJS, | ||
}, | ||
}); | ||
} catch (err) { | ||
callback(err); | ||
return; | ||
} | ||
break; | ||
} | ||
case 'es6': | ||
case 'es2015': | ||
case 'js': | ||
case 'javascript': { | ||
src = source; | ||
break; | ||
} | ||
default: { | ||
callback(new Error(`Unsupported component source language ${language} for ${packageId}/${name}`)); | ||
return; | ||
} | ||
} | ||
callback(null, src); | ||
} | ||
function evaluateModule(baseDir, packageId, name, source, callback) { | ||
const Module = require('module'); | ||
let implementation; | ||
try { | ||
// Use the Node.js module API to evaluate in the correct directory context | ||
const modulePath = path.resolve(baseDir, `./components/${name}.js`); | ||
const moduleImpl = new Module(modulePath, module); | ||
moduleImpl.paths = Module._nodeModulePaths(path.dirname(modulePath)); | ||
moduleImpl.filename = modulePath; | ||
moduleImpl._compile(source, modulePath); | ||
implementation = moduleImpl.exports; | ||
} catch (e) { | ||
callback(e); | ||
return; | ||
} | ||
if ((typeof implementation !== 'function') && (typeof implementation.getComponent !== 'function')) { | ||
callback(new Error(`Provided source for ${packageId}/${name} failed to create a runnable component`)); | ||
return; | ||
} | ||
callback(null, implementation); | ||
} | ||
function registerSources(loader, packageId, name, source, language) { | ||
if (!loader.sourcesForComponents) { | ||
// eslint-disable-next-line no-param-reassign | ||
loader.sourcesForComponents = {}; | ||
} | ||
const componentName = `${packageId}/${name}`; | ||
// eslint-disable-next-line no-param-reassign | ||
loader.sourcesForComponents[componentName] = { | ||
language, | ||
source, | ||
}; | ||
} | ||
function transpileAndRegisterForModule(loader, module, component, source, language, callback) { | ||
transpileSource(module.name, component.name, source, language, (transpileError, src) => { | ||
if (transpileError) { | ||
callback(transpileError); | ||
return; | ||
} | ||
const moduleBase = path.resolve(loader.baseDir, module.base); | ||
evaluateModule(moduleBase, module.name, component.name, src, (evalError, implementation) => { | ||
if (evalError) { | ||
callback(evalError); | ||
return; | ||
} | ||
registerSources(loader, module.name, component.name, source, language); | ||
loader.registerComponent(module.name, component.name, implementation, callback); | ||
}); | ||
}); | ||
} | ||
exports.setSource = function setSource(loader, packageId, name, source, language, callback) { | ||
transpileAndRegisterForModule(loader, { | ||
name: packageId, | ||
base: '', | ||
}, { | ||
name, | ||
}, source, language, callback); | ||
}; | ||
exports.getSource = function getSource(loader, name, callback) { | ||
let componentName = name; | ||
let component = loader.components[name]; | ||
if (!component) { | ||
// Try an alias | ||
const keys = Object.keys(loader.components); | ||
for (let i = 0; i < keys.length; i += 1) { | ||
const key = keys[i]; | ||
if (key.split('/')[1] === name) { | ||
component = loader.components[key]; | ||
componentName = key; | ||
break; | ||
} | ||
} | ||
if (!component) { | ||
callback(new Error(`Component ${componentName} not installed`)); | ||
return; | ||
} | ||
} | ||
const nameParts = componentName.split('/'); | ||
if (nameParts.length === 1) { | ||
nameParts[1] = nameParts[0]; | ||
nameParts[0] = ''; | ||
} | ||
if (loader.isGraph(component)) { | ||
if (typeof component === 'object') { | ||
if (typeof component.toJSON === 'function') { | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
code: JSON.stringify(component.toJSON()), | ||
language: 'json', | ||
}); | ||
return; | ||
} | ||
callback(new Error(`Can't provide source for ${componentName}. Not a file`)); | ||
return; | ||
} | ||
fbpGraph.graph.loadFile(component, (err, graph) => { | ||
if (err) { | ||
callback(err); | ||
return; | ||
} | ||
if (!graph) { | ||
callback(new Error('Unable to load graph')); | ||
return; | ||
} | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
code: JSON.stringify(graph.toJSON()), | ||
language: 'json', | ||
}); | ||
}); | ||
return; | ||
} | ||
if (loader.sourcesForComponents && loader.sourcesForComponents[componentName]) { | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
code: loader.sourcesForComponents[componentName].source, | ||
language: loader.sourcesForComponents[componentName].language, | ||
}); | ||
return; | ||
} | ||
if (typeof component !== 'string') { | ||
callback(new Error(`Can't provide source for ${componentName}. Not a file`)); | ||
return; | ||
} | ||
fs.readFile(component, 'utf-8', (err, code) => { | ||
if (err) { | ||
callback(err); | ||
return; | ||
} | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
language: utils.guessLanguageFromFilename(component), | ||
code, | ||
}); | ||
}); | ||
}; | ||
exports.getLanguages = function getLanguages() { | ||
const languages = ['javascript', 'es2015']; | ||
if (CoffeeScript) { | ||
languages.push('coffeescript'); | ||
} | ||
if (typescript) { | ||
languages.push('typescript'); | ||
} | ||
return languages; | ||
}; | ||
function registerCustomLoaders(loader, componentLoaders, callback) { | ||
@@ -39,4 +263,6 @@ if (!componentLoaders.length) { | ||
const componentLoaders = []; | ||
compatible.forEach((m) => { | ||
if (m.icon) { loader.setLibraryIcon(m.name, m.icon); } | ||
Promise.all(compatible.map((m) => { | ||
if (m.icon) { | ||
loader.setLibraryIcon(m.name, m.icon); | ||
} | ||
@@ -48,8 +274,36 @@ if (m.noflo != null ? m.noflo.loader : undefined) { | ||
m.components.forEach((c) => { | ||
loader.registerComponent(m.name, c.name, path.resolve(loader.baseDir, c.path)); | ||
}); | ||
}); | ||
registerCustomLoaders(loader, componentLoaders, callback); | ||
return Promise.all(m.components.map((c) => new Promise((resolve, reject) => { | ||
const language = utils.guessLanguageFromFilename(c.path); | ||
if (language === 'typescript') { | ||
// We can't require a TypeScript module, go the setSource route | ||
fs.readFile(path.resolve(loader.baseDir, c.path), 'utf-8', (fsErr, source) => { | ||
if (fsErr) { | ||
reject(fsErr); | ||
return; | ||
} | ||
transpileAndRegisterForModule(loader, m, c, source, language, (err) => { | ||
if (err) { | ||
reject(err); | ||
return; | ||
} | ||
resolve(); | ||
}); | ||
}); | ||
return; | ||
} | ||
loader.registerComponent(m.name, c.name, path.resolve(loader.baseDir, c.path), (err) => { | ||
if (err) { | ||
reject(err); | ||
return; | ||
} | ||
resolve(); | ||
}); | ||
}))); | ||
})) | ||
.then( | ||
() => { | ||
registerCustomLoaders(loader, componentLoaders, callback); | ||
}, | ||
callback, | ||
); | ||
} | ||
@@ -200,112 +454,1 @@ | ||
}; | ||
exports.setSource = function setSource(loader, packageId, name, source, language, callback) { | ||
const Module = require('module'); | ||
let src = source; | ||
if (language === 'coffeescript') { | ||
try { | ||
src = CoffeeScript.compile(src, | ||
{ bare: true }); | ||
} catch (err) { | ||
callback(err); | ||
return; | ||
} | ||
} | ||
let implementation; | ||
try { | ||
// Use the Node.js module API to evaluate in the correct directory context | ||
const modulePath = path.resolve(loader.baseDir, `./components/${name}.js`); | ||
const moduleImpl = new Module(modulePath, module); | ||
moduleImpl.paths = Module._nodeModulePaths(path.dirname(modulePath)); | ||
moduleImpl.filename = modulePath; | ||
moduleImpl._compile(src, modulePath); | ||
implementation = moduleImpl.exports; | ||
} catch (err) { | ||
callback(err); | ||
return; | ||
} | ||
if ((typeof implementation !== 'function') && (typeof implementation.getComponent !== 'function')) { | ||
callback(new Error('Provided source failed to create a runnable component')); | ||
return; | ||
} | ||
loader.registerComponent(packageId, name, implementation, callback); | ||
}; | ||
exports.getSource = function getSource(loader, name, callback) { | ||
let componentName = name; | ||
let component = loader.components[name]; | ||
if (!component) { | ||
// Try an alias | ||
const keys = Object.keys(loader.components); | ||
for (let i = 0; i < keys.length; i += 1) { | ||
const key = keys[i]; | ||
if (key.split('/')[1] === name) { | ||
component = loader.components[key]; | ||
componentName = key; | ||
break; | ||
} | ||
} | ||
if (!component) { | ||
callback(new Error(`Component ${componentName} not installed`)); | ||
return; | ||
} | ||
} | ||
const nameParts = componentName.split('/'); | ||
if (nameParts.length === 1) { | ||
nameParts[1] = nameParts[0]; | ||
nameParts[0] = ''; | ||
} | ||
if (loader.isGraph(component)) { | ||
if (typeof component === 'object') { | ||
if (typeof component.toJSON === 'function') { | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
code: JSON.stringify(component.toJSON()), | ||
language: 'json', | ||
}); | ||
return; | ||
} | ||
callback(new Error(`Can't provide source for ${componentName}. Not a file`)); | ||
return; | ||
} | ||
fbpGraph.graph.loadFile(component, (err, graph) => { | ||
if (err) { | ||
callback(err); | ||
return; | ||
} | ||
if (!graph) { | ||
callback(new Error('Unable to load graph')); | ||
return; | ||
} | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
code: JSON.stringify(graph.toJSON()), | ||
language: 'json', | ||
}); | ||
}); | ||
return; | ||
} | ||
if (typeof component !== 'string') { | ||
callback(new Error(`Can't provide source for ${componentName}. Not a file`)); | ||
return; | ||
} | ||
fs.readFile(component, 'utf-8', (err, code) => { | ||
if (err) { | ||
callback(err); | ||
return; | ||
} | ||
callback(null, { | ||
name: nameParts[1], | ||
library: nameParts[0], | ||
language: utils.guessLanguageFromFilename(component), | ||
code, | ||
}); | ||
}); | ||
}; |
@@ -13,2 +13,3 @@ // NoFlo - Flow-Based Programming for JavaScript | ||
if (/.*\.coffee$/.test(filename)) { return 'coffeescript'; } | ||
if (/.*\.ts$/.test(filename)) { return 'typescript'; } | ||
return 'javascript'; | ||
@@ -15,0 +16,0 @@ } |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
773977
96
21324
19
Updatedfbp-manifest@^0.2.2