├── templates ├── webofthings │ ├── icons │ │ └── .gitkeep │ ├── locales │ │ └── .gitkeep │ ├── LICENSE.mustache │ ├── README.md.mustache │ ├── package.json.mustache │ └── node.html.mustache ├── subflow │ ├── LICENSE.mustache │ ├── README.md.mustache │ ├── package.json.mustache │ └── subflow.js.mustache ├── swagger │ ├── LICENSE.mustache │ ├── icons │ │ └── icon.png │ ├── locales │ │ ├── ja │ │ │ └── node.json.mustache │ │ ├── zh-CN │ │ │ └── node.json.mustache │ │ ├── en-US │ │ │ └── node.json.mustache │ │ └── de-DE │ │ │ └── node.json.mustache │ ├── README.md.mustache │ ├── package.json.mustache │ ├── test │ │ └── node_spec.js.mustache │ ├── node.js.mustache │ └── node.html.mustache └── function │ ├── README.md.mustache │ ├── icons │ └── icon.svg │ ├── package.json.mustache │ ├── examples │ └── flow.json.mustache │ ├── test │ └── node_spec.js.mustache │ ├── node.html.mustache │ ├── LICENSE.mustache │ └── node.js.mustache ├── docs ├── library.png └── library_ja.png ├── samples ├── lower-case.js ├── MyLampThing.jsonld ├── qrcode.json └── swagger.json ├── test ├── lib │ ├── nodegen_spec.js │ ├── webofthings_spec.js │ ├── swagger_spec.js │ ├── subflow_spec.js │ └── function_spec.js └── nodegen │ ├── node-red-contrib-lowercase │ └── node_spec.js │ └── node-red-contrib-swagger-petstore │ └── node_spec.js ├── .gitignore ├── .github ├── workflows │ └── test.yml ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── lib ├── nodegen.js ├── subflow │ └── index.js ├── util.js ├── function │ └── index.js ├── webofthings │ ├── index.js │ └── wotutils.js └── swagger │ └── index.js ├── Gruntfile.js ├── package.json ├── README.md ├── bin └── node-red-nodegen.js └── LICENSE /templates/webofthings/icons/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/webofthings/locales/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /templates/subflow/LICENSE.mustache: -------------------------------------------------------------------------------- 1 | {{&licenseName}} 2 | -------------------------------------------------------------------------------- /templates/webofthings/LICENSE.mustache: -------------------------------------------------------------------------------- 1 | {{&licenseName}} 2 | {{&licenseUrl}} 3 | -------------------------------------------------------------------------------- /templates/swagger/LICENSE.mustache: -------------------------------------------------------------------------------- 1 | #### {{&licenseName}} 2 | 3 | {{&licenseUrl}} 4 | -------------------------------------------------------------------------------- /docs/library.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-red/node-red-nodegen/master/docs/library.png -------------------------------------------------------------------------------- /docs/library_ja.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-red/node-red-nodegen/master/docs/library_ja.png -------------------------------------------------------------------------------- /samples/lower-case.js: -------------------------------------------------------------------------------- 1 | // name: lower-case 2 | // outputs: 1 3 | msg.payload = msg.payload.toLowerCase(); 4 | return msg; 5 | -------------------------------------------------------------------------------- /templates/swagger/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node-red/node-red-nodegen/master/templates/swagger/icons/icon.png -------------------------------------------------------------------------------- /test/lib/nodegen_spec.js: -------------------------------------------------------------------------------- 1 | const nodegen = require('../../lib/nodegen'); 2 | 3 | describe('nodegen library', function () { 4 | }); 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated files 2 | nodegen/* 3 | 4 | # Dependency directories 5 | node_modules 6 | node-red 7 | 8 | # Coverage result 9 | coverage 10 | 11 | # npm install log 12 | npm-debug.log.* 13 | package-lock.json 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v2 13 | - run: npm install 14 | - run: npm test 15 | -------------------------------------------------------------------------------- /templates/subflow/README.md.mustache: -------------------------------------------------------------------------------- 1 | {{&projectName}} 2 | ===================== 3 | 4 | {{&description}} 5 | 6 | ## Install 7 | 8 | Run the following command in your Node-RED user directory - typically `~/.node-red` 9 | 10 | npm install {{&projectName}} 11 | 12 | {{#nodeRead}} 13 | ## Information 14 | 15 | {{&nodeRead}} 16 | {{/nodeRead}} 17 | -------------------------------------------------------------------------------- /templates/function/README.md.mustache: -------------------------------------------------------------------------------- 1 | {{&projectName}} 2 | ================ 3 | 4 | Node-RED node for {{&nodeName}} 5 | 6 | {{&description}} 7 | 8 | ## Install 9 | 10 | To install the stable version use the `Menu - Manage palette - Install` 11 | option and search for {{&projectName}}, or run the following 12 | command in your Node-RED user directory, typically `~/.node-red` 13 | 14 | npm install {{&projectName}} 15 | 16 | ## Information 17 | 18 | {{&nodeRead}} -------------------------------------------------------------------------------- /templates/function/icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /templates/subflow/package.json.mustache: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{&projectName}}", 3 | "version": "{{&projectVersion}}", 4 | "description": "Node-RED node for {{&nodeName}}", 5 | "keywords": [ 6 | {{#keywords}} 7 | "{{name}}"{{^last}}, {{/last}} 8 | {{/keywords}} 9 | ], 10 | "engines": { 11 | "node": ">=12.0.0" 12 | }, 13 | "node-red": { 14 | "version": ">=1.3.7", 15 | "nodes": { 16 | "{{&nodeName}}": "subflow.js" 17 | } 18 | }, 19 | {{#encoding}} 20 | "dependencies": { 21 | "crypto-js": "4.1.1" 22 | }, 23 | {{/encoding}} 24 | "license": "{{&licenseName}}" 25 | } 26 | -------------------------------------------------------------------------------- /templates/swagger/locales/ja/node.json.mustache: -------------------------------------------------------------------------------- 1 | { 2 | "{{&className}}": { 3 | "label": { 4 | "service": "サービス", 5 | "method": "メソッド", 6 | "host": "ホスト", 7 | "header": "ヘッダ", 8 | "value": "値", 9 | "isQuery": "クエリ" 10 | }, 11 | "status": { 12 | "requesting": "要求中" 13 | }, 14 | "parameters": { 15 | {{#methods}} 16 | "{{&methodName}}": "{{&method}} {{&path}}", 17 | {{#parameters}} 18 | "{{&camelCaseName}}": "{{&name}}", 19 | {{/parameters}} 20 | {{/methods}} 21 | "optionalParameters": "任意項目" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /templates/swagger/locales/zh-CN/node.json.mustache: -------------------------------------------------------------------------------- 1 | { 2 | "{{&className}}": { 3 | "label": { 4 | "service": "服务", 5 | "method": "方法", 6 | "host": "网络主机", 7 | "header": "字头段", 8 | "value": "值", 9 | "isQuery": "查询" 10 | }, 11 | "status": { 12 | "requesting": "请求中" 13 | }, 14 | "parameters": { 15 | {{#methods}} 16 | "{{&methodName}}": "{{&method}} {{&path}}", 17 | {{#parameters}} 18 | "{{&camelCaseName}}": "{{&name}}", 19 | {{/parameters}} 20 | {{/methods}} 21 | "optionalParameters": "可选项" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /templates/webofthings/README.md.mustache: -------------------------------------------------------------------------------- 1 | {{projectName}} 2 | ================ 3 | 4 | Node-RED node for {{nodeName}} 5 | 6 | {{description}} 7 | 8 | ## Install 9 | 10 | To install the stable version use the `Menu - Manage palette - Install` 11 | option and search for {{projectName}}, or run the following 12 | command in your Node-RED user directory, typically `~/.node-red` 13 | 14 | npm install {{projectName}} 15 | 16 | ## Interactions 17 | 18 | ### Properties 19 | 20 | {{#properties}} 21 | - {{label}}: {{description}} 22 | {{/properties}} 23 | 24 | ### Actions 25 | 26 | {{#actions}} 27 | - {{label}}: {{description}} 28 | {{/actions}} 29 | 30 | 31 | ### Events 32 | 33 | {{#events}} 34 | - {{label}}: {{description}} 35 | {{/events}} 36 | 37 | -------------------------------------------------------------------------------- /templates/swagger/locales/en-US/node.json.mustache: -------------------------------------------------------------------------------- 1 | { 2 | "{{&className}}": { 3 | "label": { 4 | "service": "Service", 5 | "method": "Method", 6 | "host": "Host", 7 | "header": "Header", 8 | "value": "Value", 9 | "isQuery": "isQuery" 10 | }, 11 | "status": { 12 | "requesting": "requesting" 13 | }, 14 | "parameters": { 15 | {{#methods}} 16 | "{{&methodName}}": "{{&method}} {{&path}}", 17 | {{#parameters}} 18 | "{{&camelCaseName}}": "{{&name}}", 19 | {{/parameters}} 20 | {{/methods}} 21 | "optionalParameters": "Options" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /templates/function/package.json.mustache: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{&projectName}}", 3 | "version": "{{&projectVersion}}", 4 | "description": "Node-RED node for {{&nodeName}}", 5 | "main": "node.js", 6 | "scripts": { 7 | "test": "mocha \"test/**/*_spec.js\" --timeout 3000" 8 | }, 9 | "engines": { 10 | "node": ">=12.0.0" 11 | }, 12 | "node-red": { 13 | "version": ">=1.3.7", 14 | "nodes": { 15 | "{{&nodeName}}": "node.js" 16 | } 17 | }, 18 | "keywords": [ 19 | {{#keywords}} 20 | "{{name}}"{{^last}}, {{/last}} 21 | {{/keywords}} 22 | ], 23 | "devDependencies": { 24 | "mocha": "9.2.1", 25 | "node-red": "2.2.2", 26 | "node-red-node-test-helper": "0.2.7" 27 | }, 28 | "license": "Apache-2.0" 29 | } 30 | -------------------------------------------------------------------------------- /templates/swagger/locales/de-DE/node.json.mustache: -------------------------------------------------------------------------------- 1 | { 2 | "{{&className}}": { 3 | "label": { 4 | "service": "Service", 5 | "method": "Methode", 6 | "host": "Host", 7 | "header": "Header", 8 | "value": "Wert", 9 | "isQuery": "Suchanfrage" 10 | }, 11 | "status": { 12 | "requesting": "Anfrage..." 13 | }, 14 | "parameters": { 15 | {{#methods}} 16 | "{{&methodName}}": "{{&method}} {{&path}}", 17 | {{#parameters}} 18 | "{{&camelCaseName}}": "{{&name}}", 19 | {{/parameters}} 20 | {{/methods}} 21 | "optionalParameters": "Optionen" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /templates/swagger/README.md.mustache: -------------------------------------------------------------------------------- 1 | {{&projectName}} 2 | ================ 3 | 4 | Node-RED node for {{&nodeName}} 5 | 6 | {{&description}} 7 | 8 | ## Install 9 | 10 | To install the stable version use the `Menu - Manage palette - Install` 11 | option and search for {{&projectName}}, or run the following 12 | command in your Node-RED user directory, typically `~/.node-red` 13 | 14 | npm install {{&projectName}} 15 | 16 | ## Usage 17 | 18 | ### Methods 19 | 20 | {{#methods}} 21 | #### {{&method}} {{&path}} 22 | 23 | {{&summary}} 24 | 25 | {{#parameters}} 26 | {{&name}} : {{&type}} 27 | {{/parameters}} 28 | 29 | {{#headers}} 30 | {{&name}} : {{&value}} 31 | {{/headers}} 32 | 33 | {{/methods}} 34 | 35 | ## License 36 | 37 | #### {{&licenseName}} 38 | 39 | {{&licenseUrl}} -------------------------------------------------------------------------------- /templates/swagger/package.json.mustache: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{&projectName}}", 3 | "version": "{{&projectVersion}}", 4 | "description": "Node-RED node for {{&nodeName}}", 5 | "main": "node.js", 6 | "scripts": { 7 | "test": "mocha \"test/**/*_spec.js\" --timeout 3000" 8 | }, 9 | "engines": { 10 | "node": ">=12.0.0" 11 | }, 12 | "node-red": { 13 | "version": ">=1.3.7", 14 | "nodes": { 15 | "{{&nodeName}}": "node.js" 16 | } 17 | }, 18 | "keywords": [ 19 | {{#keywords}} 20 | "{{name}}"{{^last}}, {{/last}} 21 | {{/keywords}} 22 | ], 23 | "dependencies": { 24 | "q": "1.5.1", 25 | "request": "2.88.2", 26 | "file-type": "16.5.3" 27 | }, 28 | "devDependencies": { 29 | "mocha": "9.2.1", 30 | "node-red": "2.2.2", 31 | "node-red-node-test-helper": "0.2.7" 32 | }, 33 | "author": "{{&contactName}}", 34 | "license": "{{&licenseName}}" 35 | } 36 | -------------------------------------------------------------------------------- /templates/webofthings/package.json.mustache: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{&projectName}}", 3 | "version": "{{&projectVersion}}", 4 | "description": "Node-RED node for {{&nodeName}}", 5 | "main": "node.js", 6 | "scripts": { 7 | "test": "mocha \"test/**/*_spec.js\"" 8 | }, 9 | "engines": { 10 | "node": ">=12.0.0" 11 | }, 12 | "node-red": { 13 | "version": ">=1.3.7", 14 | "nodes": { 15 | "{{&nodeName}}": "node.js" 16 | } 17 | }, 18 | "keywords": [ 19 | {{#keywords}} 20 | "{{name}}"{{^last}}, {{/last}} 21 | {{/keywords}} 22 | ], 23 | "dependencies": { 24 | "https-proxy-agent": "5.0.0", 25 | "request": "2.88.2", 26 | "ws": "8.5.0", 27 | "url-template": "2.0.8", 28 | "ajv": "8.11.0", 29 | "coap": "1.0.3", 30 | "mqtt": "4.3.7" 31 | }, 32 | "devDependencies": { 33 | "node-red": "2.2.2", 34 | "node-red-node-test-helper": "0.2.7" 35 | }, 36 | "license": "{{&licenseName}}", 37 | "wot": { 38 | {{#wotmeta}} 39 | "{{name}}": "{{value}}"{{^last}}, {{/last}} 40 | {{/wotmeta}} 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /samples/MyLampThing.jsonld: -------------------------------------------------------------------------------- 1 | { 2 | "@context": [ 3 | "https://www.w3.org/2019/wot/td/v1", 4 | { "saref": "https://w3id.org/saref#" } 5 | ], 6 | "id": "urn:dev:ops:32473-WoTLamp-1234", 7 | "title": "MyLampThing", 8 | "@type": "saref:LightSwitch", 9 | "securityDefinitions": {"basic_sc": { 10 | "scheme": "basic", 11 | "in": "header" 12 | }}, 13 | "security": ["basic_sc"], 14 | "properties": { 15 | "status": { 16 | "@type": "saref:OnOffState", 17 | "type": "string", 18 | "forms": [{ 19 | "href": "https://mylamp.example.com/status" 20 | }] 21 | } 22 | }, 23 | "actions": { 24 | "toggle": { 25 | "@type": "saref:ToggleCommand", 26 | "forms": [{ 27 | "href": "https://mylamp.example.com/toggle" 28 | }] 29 | } 30 | }, 31 | "events": { 32 | "overheating": { 33 | "data": {"type": "string"}, 34 | "forms": [{ 35 | "href": "https://mylamp.example.com/oh" 36 | }] 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /lib/nodegen.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright OpenJS Foundation and other contributors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | const FunctionNodeGenerator = require("./function"); 18 | const SwaggerNodeGenerator = require("./swagger"); 19 | const WebOfThingsGenerator = require("./webofthings"); 20 | const SubflowNodeGenerator = require("./subflow"); 21 | 22 | module.exports = { 23 | FunctionNodeGenerator: FunctionNodeGenerator, 24 | SwaggerNodeGenerator: SwaggerNodeGenerator, 25 | WebOfThingsGenerator: WebOfThingsGenerator, 26 | SubflowNodeGenerator: SubflowNodeGenerator 27 | }; 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 21 | 22 | ### What are the steps to reproduce? 23 | 24 | ### What happens? 25 | 26 | ### What do you expect to happen? 27 | 28 | ### Please tell us about your environment: 29 | 30 | - [ ] Node generator version: 31 | - [ ] Node-RED version: 32 | - [ ] Node.js version: 33 | - [ ] npm version: 34 | - [ ] Platform/OS: 35 | - [ ] Browser: 36 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | grunt.initConfig({ 3 | shell: { 4 | generateNode_Function: { 5 | command: 'node bin/node-red-nodegen.js samples/lower-case.js -o ./nodegen' 6 | }, 7 | generateNode_Swagger: { 8 | command: 'node bin/node-red-nodegen.js samples/swagger.json -o ./nodegen' 9 | }, 10 | generateNode_WebOfThings: { 11 | command: 'node bin/node-red-nodegen.js samples/MyLampThing.jsonld -o ./nodegen' 12 | } 13 | }, 14 | simplemocha: { 15 | options: { 16 | timeout: 10000, 17 | retries: 10 18 | }, 19 | all: { 20 | src: [ 'test/**/*_spec.js' ] 21 | } 22 | }, 23 | mocha_istanbul: { 24 | options: { 25 | timeout: 10000 26 | }, 27 | all: { 28 | src: [ 'test/**/*_spec.js' ] 29 | } 30 | } 31 | }); 32 | grunt.file.mkdir('nodegen'); 33 | grunt.loadNpmTasks('grunt-shell'); 34 | grunt.loadNpmTasks('grunt-simple-mocha'); 35 | grunt.registerTask('default', ['shell', 'simplemocha']); 36 | }; 37 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | - [ ] Bugfix (non-breaking change which fixes an issue) 14 | - [ ] New feature (non-breaking change which adds functionality) 15 | 16 | 23 | 24 | ## Proposed changes 25 | 26 | 27 | 28 | ## Checklist 29 | 30 | 31 | - [ ] I have read the [contribution guidelines](https://github.com/node-red/node-red/blob/master/CONTRIBUTING.md) 32 | - [ ] For non-bugfix PRs, I have discussed this change on the mailing list/slack team. 33 | - [ ] I have run `grunt` to verify the unit tests pass 34 | - [ ] I have added suitable unit tests to cover the new/changed functionality 35 | -------------------------------------------------------------------------------- /templates/function/examples/flow.json.mustache: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "786d9ce1.c1c3a4", 4 | "type": "inject", 5 | "z": "7b8f4430.ec912c", 6 | "name": "", 7 | "props": [ 8 | { 9 | "p": "payload" 10 | }, 11 | { 12 | "p": "topic", 13 | "vt": "str" 14 | } 15 | ], 16 | "repeat": "", 17 | "crontab": "", 18 | "once": false, 19 | "onceDelay": 0.1, 20 | "topic": "", 21 | "payload": "", 22 | "payloadType": "date", 23 | "x": 0, 24 | "y": 0, 25 | "wires": [ 26 | [ 27 | "726f08b3.50bfd8" 28 | ] 29 | ] 30 | }, 31 | { 32 | "id": "726f08b3.50bfd8", 33 | "type": "{{&nodeName}}", 34 | "z": "7b8f4430.ec912c", 35 | "name": "", 36 | "x": 200, 37 | "y": 0, 38 | "wires": [ 39 | [ 40 | "82d40348.2d47b" 41 | ] 42 | ] 43 | }, 44 | { 45 | "id": "82d40348.2d47b", 46 | "type": "debug", 47 | "z": "7b8f4430.ec912c", 48 | "name": "", 49 | "active": true, 50 | "tosidebar": true, 51 | "console": false, 52 | "tostatus": false, 53 | "complete": "false", 54 | "statusVal": "", 55 | "statusType": "auto", 56 | "x": 400, 57 | "y": 0, 58 | "wires": [] 59 | } 60 | ] 61 | -------------------------------------------------------------------------------- /templates/function/test/node_spec.js.mustache: -------------------------------------------------------------------------------- 1 | var should = require("should"); 2 | var helper = require("node-red-node-test-helper"); 3 | var node = require("../node.js"); 4 | 5 | helper.init(require.resolve('node-red')); 6 | 7 | describe('{{&nodeName}} node', function () { 8 | 9 | before(function (done) { 10 | helper.startServer(done); 11 | }); 12 | 13 | after(function (done) { 14 | helper.stopServer(done); 15 | }); 16 | 17 | afterEach(function () { 18 | helper.unload(); 19 | }); 20 | 21 | it('should be loaded', function (done) { 22 | var flow = [{ id: "n1", type: "{{&nodeName}}", name: "{{&nodeName}}" }]; 23 | helper.load(node, flow, function () { 24 | var n1 = helper.getNode("n1"); 25 | n1.should.have.property('name', '{{&nodeName}}'); 26 | done(); 27 | }); 28 | }); 29 | 30 | it('should have payload', function (done) { 31 | var flow = [ 32 | { id: "n1", type: "{{&nodeName}}", name: "{{&nodeName}}", wires: [["n2"]] }, 33 | { id: "n2", type: "helper" } 34 | ]; 35 | helper.load(node, flow, function () { 36 | var n2 = helper.getNode("n2"); 37 | var n1 = helper.getNode("n1"); 38 | n2.on("input", function (msg) { 39 | msg.should.have.property('payload', ''); // (2) define output message 40 | done(); 41 | }); 42 | n1.receive({ payload: "" }); // (1) define input message 43 | }); 44 | }); 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /templates/function/node.html.mustache: -------------------------------------------------------------------------------- 1 | 7 | 8 | 28 | 29 | 51 | -------------------------------------------------------------------------------- /test/lib/webofthings_spec.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var should = require('should'); 4 | var del = require('del'); 5 | var wottd2node = require('../../lib/webofthings'); 6 | 7 | describe('Web of Things node', function () { 8 | it('should have node files', function (done) { 9 | const sourcePath = 'samples/MyLampThing.jsonld'; 10 | const data = { 11 | src: JSON.parse(fs.readFileSync(sourcePath)), 12 | dst: '.' 13 | }; 14 | const options = {}; 15 | wottd2node(data, options).then(function (result) { 16 | const packageSourceCode = JSON.parse(fs.readFileSync(result + '/package.json')); 17 | packageSourceCode.name.should.equal('node-red-contrib-wotmylampthing'); 18 | packageSourceCode.version.should.equal('0.0.1'); 19 | fs.statSync(result + '/node.html').size.should.be.above(0); 20 | fs.statSync(result + '/node.js').size.should.be.above(0); 21 | fs.statSync(result + '/README.md').size.should.be.above(0); 22 | fs.statSync(result + '/LICENSE').size.should.be.above(0); 23 | del.sync(result); 24 | done(); 25 | }); 26 | }); 27 | it('should handle options', function (done) { 28 | const sourcePath = 'samples/MyLampThing.jsonld'; 29 | const data = { 30 | src: JSON.parse(fs.readFileSync(sourcePath)), 31 | dst: '.' 32 | }; 33 | const options = { 34 | tgz: true, 35 | obfuscate: true 36 | }; 37 | wottd2node(data, options).then(function (result) { 38 | fs.statSync(result).isFile().should.be.eql(true); 39 | del.sync(result); 40 | var jsfile = result.replace(/-[0-9]+\.[0-9]+\.[0-9]+\.tgz$/, '/node.js'); 41 | fs.readFileSync(jsfile).toString().split('\n').length.should.be.eql(1); 42 | result = result.replace(/-[0-9]+\.[0-9]+\.[0-9]+\.tgz$/, ''); 43 | del.sync(result); 44 | done(); 45 | }); 46 | }); 47 | }); 48 | 49 | -------------------------------------------------------------------------------- /test/nodegen/node-red-contrib-lowercase/node_spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright JS Foundation and other contributors, http://js.foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | var should = require("should"); 18 | var helper = require("node-red-node-test-helper"); 19 | var functionNode = require("../../../nodegen/node-red-contrib-lowercase/node.js"); 20 | 21 | describe('node-red-contrib-lowercase', function () { 22 | 23 | before(function (done) { 24 | helper.startServer(done); 25 | }); 26 | 27 | after(function(done) { 28 | helper.stopServer(done); 29 | }); 30 | 31 | afterEach(function () { 32 | helper.unload(); 33 | }); 34 | 35 | it('should be loaded', function (done) { 36 | var flow = [{id: "n1", type: "lowercase", name: "lowercase" }]; 37 | helper.load(functionNode, flow, function () { 38 | var n1 = helper.getNode('n1'); 39 | n1.should.have.property('name', 'lowercase'); 40 | done(); 41 | }); 42 | }); 43 | it('should handle toLowerCase()', function (done) { 44 | var flow = [{id: "n1", type: "lowercase", wires: [["n2"]]}, 45 | {id: "n2", type: "helper"}]; 46 | helper.load(functionNode, flow, function () { 47 | var n1 = helper.getNode('n1'); 48 | var n2 = helper.getNode('n2'); 49 | n2.on('input', function (msg) { 50 | msg.should.have.property('payload', 'abcde'); 51 | done(); 52 | }); 53 | n1.receive({payload: 'AbCdE'}); 54 | }); 55 | }); 56 | }); 57 | 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-nodegen", 3 | "version": "0.2.4", 4 | "description": "Node generator for Node-RED", 5 | "homepage": "http://nodered.org", 6 | "main": "./lib/nodegen.js", 7 | "license": "Apache-2.0", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/node-red/node-red-nodegen.git" 11 | }, 12 | "engines": { 13 | "node": ">=14" 14 | }, 15 | "contributors": [ 16 | "Kazuhito Yokoi", 17 | "Razvan Bordeanu", 18 | "Hiroki Uchikawa", 19 | "Yuki Naganuma", 20 | "Hiroshi Nasu", 21 | "Jana Backhus", 22 | "Kunihiko Toumura", 23 | "Yu Nakata", 24 | "Yuma Matsuura", 25 | "Katsuya Hoshii", 26 | "Qi Xiu", 27 | "Masae Okada", 28 | "Takaya Ide", 29 | "Kazuki Nakanishi", 30 | "Hideki Nakamura", 31 | "Hiroyasu Nishiyama", 32 | "Nick O'Leary", 33 | "Dave Conway-Jones" 34 | ], 35 | "keywords": [ 36 | "nodegen", 37 | "function", 38 | "swagger", 39 | "codegen", 40 | "swagger-codegen", 41 | "wot", 42 | "web of things" 43 | ], 44 | "dependencies": { 45 | "ajv": "8.11.0", 46 | "ajv-formats": "2.1.1", 47 | "ajv-formats-draft2019": "1.6.1", 48 | "api-spec-converter": "2.12.0", 49 | "axios": "0.26.0", 50 | "colors": "1.4.0", 51 | "crypto-js": "4.1.1", 52 | "csv-string": "4.1.0", 53 | "javascript-obfuscator": "1.12.1", 54 | "jimp": "0.16.1", 55 | "js-string-escape": "1.0.1", 56 | "minimist": "1.2.5", 57 | "mustache": "4.2.0", 58 | "swagger-js-codegen-formdata": "0.15.5", 59 | "yamljs": "0.3.0" 60 | }, 61 | "devDependencies": { 62 | "del": "6.0.0", 63 | "grunt": "1.4.1", 64 | "grunt-shell": "3.0.1", 65 | "grunt-simple-mocha": "0.4.1", 66 | "nyc": "15.1.0", 67 | "node-red": "2.2.2", 68 | "node-red-node-test-helper": "0.2.7", 69 | "q": "1.5.1", 70 | "should": "13.2.3", 71 | "sinon": "13.0.1", 72 | "supertest": "6.2.2" 73 | }, 74 | "bin": { 75 | "node-red-nodegen": "bin/node-red-nodegen.js" 76 | }, 77 | "scripts": { 78 | "test": "grunt" 79 | }, 80 | "bugs": { 81 | "url": "https://github.com/node-red/node-red-nodegen/issues" 82 | }, 83 | "directories": { 84 | "doc": "docs", 85 | "lib": "lib", 86 | "test": "test" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /templates/swagger/test/node_spec.js.mustache: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var helper = require('node-red-node-test-helper'); 3 | var node = require('../node.js'); 4 | 5 | helper.init(require.resolve('node-red')); 6 | 7 | describe('{{&nodeName}} node', function () { 8 | 9 | before(function (done) { 10 | helper.startServer(done); 11 | }); 12 | 13 | after(function (done) { 14 | helper.stopServer(done); 15 | }); 16 | 17 | afterEach(function () { 18 | helper.unload(); 19 | }); 20 | 21 | it('should be loaded', function (done) { 22 | var flow = [{ id: 'n1', type: '{{&nodeName}}', name: '{{&nodeName}}' }]; 23 | helper.load(node, flow, function () { 24 | var n1 = helper.getNode('n1'); 25 | n1.should.have.property('name', '{{&nodeName}}'); 26 | done(); 27 | }); 28 | }); 29 | 30 | {{#methods}} 31 | it('should handle {{&methodName}}()', function (done) { 32 | var flow = [ 33 | { id: 'n1', type: '{{&nodeName}}', name: '{{&nodeName}}', 34 | method: '{{&methodName}}', 35 | {{#parameters}} 36 | {{&methodName}}_{{&camelCaseName}}: '', // (1) define node properties 37 | {{/parameters}} 38 | {{#hasServiceParams}} 39 | wires: [['n3']], 40 | service: 'n2' }, 41 | { id: 'n2', type: '{{&nodeName}}-service', host: 'http://' }, // (4) define host name 42 | {{/hasServiceParams}} 43 | {{^hasServiceParams}} 44 | wires: [['n3']] 45 | }, 46 | {{/hasServiceParams}} 47 | { id: 'n3', type: 'helper' } 48 | ]; 49 | helper.load(node, flow, function () { 50 | var n3 = helper.getNode('n3'); 51 | var n1 = helper.getNode('n1'); 52 | n3.on('input', function (msg) { 53 | try { 54 | msg.should.have.property('payload', ''); // (3) define output message 55 | done(); 56 | } catch (e) { 57 | done(e); 58 | } 59 | }); 60 | n1.receive({ payload: '' }); // (2) define input message 61 | }); 62 | }); 63 | {{/methods}} 64 | }); 65 | -------------------------------------------------------------------------------- /samples/qrcode.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "921a4b1d.07bb68", 4 | "type": "subflow", 5 | "name": "QRcode", 6 | "info": "This node generates a QR code from the text data in the `msg.payload`. The QR code in the outputted `msg.payload` is text data (not image data) described by text which consists of black rectangles and white spaces. Therefore, you can use the debug node to see the QR code.\n\n### Inputs\n- `payload` - _string_\n\n Text data which is converted to QR code\n\n### Outputs\n- `payload` - _string_\n\n Text data which contains QR code\n\n**Note**: This node uses the [node-qrcode](https://www.npmjs.com/package/qrcode) module to generate QR codes.", 7 | "category": "", 8 | "in": [ 9 | { 10 | "x": 140, 11 | "y": 120, 12 | "wires": [ 13 | { 14 | "id": "e563f491.e443f8" 15 | } 16 | ] 17 | } 18 | ], 19 | "out": [ 20 | { 21 | "x": 380, 22 | "y": 120, 23 | "wires": [ 24 | { 25 | "id": "e563f491.e443f8", 26 | "port": 0 27 | } 28 | ] 29 | } 30 | ], 31 | "env": [], 32 | "meta": { 33 | "module": "node-red-contrib-qrcode", 34 | "type": "qrcode", 35 | "version": "0.1.0", 36 | "author": "hiroyasu.nishiyama.uq@hitachi.com", 37 | "desc": "Node-RED node for converting string to QRcode", 38 | "keywords": "Node-RED, subflow, QRcode", 39 | "license": "Apache-2.0" 40 | }, 41 | "color": "#87A980", 42 | "icon": "font-awesome/fa-qrcode" 43 | }, 44 | { 45 | "id": "e563f491.e443f8", 46 | "type": "function", 47 | "z": "921a4b1d.07bb68", 48 | "name": "", 49 | "func": "qrcode.toString(msg.payload, (err,str) => {\n if (err) {\n node.error(err);\n return;\n }\n node.send({payload: str});\n});", 50 | "outputs": 1, 51 | "noerr": 0, 52 | "initialize": "", 53 | "finalize": "", 54 | "libs": [ 55 | { 56 | "var": "qrcode", 57 | "module": "qrcode" 58 | } 59 | ], 60 | "x": 260, 61 | "y": 120, 62 | "wires": [ 63 | [] 64 | ] 65 | } 66 | ] -------------------------------------------------------------------------------- /test/lib/swagger_spec.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var should = require('should'); 4 | var del = require('del'); 5 | var swagger2node = require('../../lib/swagger'); 6 | 7 | describe('swagger node', function () { 8 | var swaggerDoc; 9 | before(function() { 10 | swaggerDoc = JSON.parse(fs.readFileSync(path.join(__dirname,"../../samples/swagger.json"))); 11 | }); 12 | 13 | it('should have node files', function (done) { 14 | var options = {}; 15 | var data = { src: swaggerDoc, dst: '.' }; 16 | swagger2node(data, options).then(function (result) { 17 | var packageSourceCode = JSON.parse(fs.readFileSync(result + '/package.json')); 18 | packageSourceCode.name.should.equal('node-red-contrib-swagger-petstore'); 19 | packageSourceCode.version.should.equal('1.0.5'); 20 | packageSourceCode.license.should.equal('Apache 2.0'); 21 | fs.statSync(result + '/node.html').size.should.be.above(0); 22 | fs.statSync(result + '/node.js').size.should.be.above(0); 23 | fs.statSync(result + '/lib.js').size.should.be.above(0); 24 | fs.statSync(result + '/test/node_spec.js').size.should.be.above(0); 25 | fs.statSync(result + '/icons/icon.png').size.should.be.above(0); 26 | fs.statSync(result + '/locales/en-US/node.json').size.should.be.above(0); 27 | fs.statSync(result + '/locales/ja/node.json').size.should.be.above(0); 28 | fs.statSync(result + '/locales/zh-CN/node.json').size.should.be.above(0); 29 | fs.statSync(result + '/README.md').size.should.be.above(0); 30 | fs.statSync(result + '/LICENSE').size.should.be.above(0); 31 | del.sync(result); 32 | done(); 33 | }); 34 | }); 35 | it('should handle options', function (done) { 36 | var options = { 37 | tgz: true, 38 | obfuscate: true 39 | }; 40 | var data = { src: swaggerDoc, dst: '.' }; 41 | swagger2node(data, options).then(function (result) { 42 | fs.statSync(result).isFile().should.be.eql(true); 43 | del.sync(result); 44 | var jsfile = result.replace(/-[0-9]+\.[0-9]+\.[0-9]+\.tgz$/, '/node.js'); 45 | fs.readFileSync(jsfile).toString().split('\n').length.should.be.eql(1); 46 | result = result.replace(/-[0-9]+\.[0-9]+\.[0-9]+\.tgz$/, ''); 47 | del.sync(result); 48 | done(); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /templates/subflow/subflow.js.mustache: -------------------------------------------------------------------------------- 1 | module.exports = function (RED) { 2 | const fs = require('fs'); 3 | const path = require("path"); 4 | const file = path.join(__dirname, "subflow.json"); 5 | const text = fs.readFileSync(file); 6 | const flow = JSON.parse(text); 7 | {{#encoding}} 8 | const crypt = require("crypto-js"); 9 | 10 | function getDecoder(encoding) { 11 | var settings = RED.settings; 12 | if (settings && 13 | settings.encodeSubflow && 14 | settings.encodeSubflow.methods) { 15 | const methods = settings.encodeSubflow.methods; 16 | const method = methods.find((x) => (x.name === encoding)); 17 | if (method) { 18 | return method.decode; 19 | } 20 | } 21 | if (encoding == "AES") { 22 | return function (enc, key) { 23 | var enc = crypt.AES.decrypt(enc, key) 24 | var data = enc.toString(crypt.enc.Utf8); 25 | return JSON.parse(data); 26 | }; 27 | } 28 | // no decoding 29 | return null; 30 | } 31 | 32 | function convFlow(sf) { 33 | const flow = sf.flow; 34 | if (((typeof flow) === "object") && 35 | !Array.isArray(flow)) { 36 | if (flow.hasOwnProperty("encoding") && 37 | flow.hasOwnProperty("flow")) { 38 | const encoding = flow.encoding; 39 | const body = flow.flow; 40 | const decoder = getDecoder(encoding); 41 | if (decoder) { 42 | const key = process.env["NR_FLOW_DECODE_KEY"]; 43 | if (key && (key !== "")) { 44 | try { 45 | RED.log.info("deocde "+encoding+ " encoded flow"); 46 | const flowData = decoder(body, key); 47 | sf.flow = flowData; 48 | } 49 | catch (e) { 50 | throw new Error("flow decode error:"+e.toString()); 51 | } 52 | } 53 | else { 54 | throw new Error("no flow decode key specified"); 55 | } 56 | } 57 | else { 58 | throw new Error("no decoder found:" +encoding); 59 | } 60 | } 61 | } 62 | return sf; 63 | } 64 | 65 | RED.nodes.registerSubflow(convFlow(flow)); 66 | {{/encoding}} 67 | {{^encoding}} 68 | RED.nodes.registerSubflow(flow); 69 | {{/encoding}} 70 | } 71 | -------------------------------------------------------------------------------- /test/lib/subflow_spec.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const should = require('should'); 4 | const del = require('del'); 5 | const subflow2node = require('../../lib/subflow'); 6 | 7 | describe('subflow node', function () { 8 | it('should have node files', function (done) { 9 | var options = {}; 10 | var data = { dst: '.' }; 11 | data.src = JSON.parse(fs.readFileSync('samples/qrcode.json')); 12 | subflow2node(data, options).then(function (result) { 13 | try { 14 | var packageSourceCode = JSON.parse(fs.readFileSync(result + '/package.json')); 15 | packageSourceCode.name.should.equal('node-red-contrib-qrcode'); 16 | packageSourceCode.version.should.equal('0.1.0'); 17 | fs.statSync(result + '/subflow.json').size.should.be.above(0); 18 | fs.statSync(result + '/subflow.js').size.should.be.above(0); 19 | fs.statSync(result + '/README.md').size.should.be.above(0); 20 | fs.statSync(result + '/LICENSE').size.should.be.above(0); 21 | del.sync(result); 22 | done(); 23 | } 24 | catch (e) { 25 | done(e); 26 | } 27 | }); 28 | }); 29 | it('should handle encoding option', function (done) { 30 | var options = { 31 | encoding: "AES", 32 | encodekey: "Node-RED" 33 | }; 34 | var data = { dst: '.' }; 35 | data.src = JSON.parse(fs.readFileSync('samples/qrcode.json')); 36 | subflow2node(data, options).then(function (result) { 37 | try { 38 | var packageSourceCode = JSON.parse(fs.readFileSync(result + '/package.json')); 39 | packageSourceCode.name.should.equal('node-red-contrib-qrcode'); 40 | packageSourceCode.version.should.equal('0.1.0'); 41 | fs.statSync(result + '/subflow.json').size.should.be.above(0); 42 | fs.statSync(result + '/subflow.js').size.should.be.above(0); 43 | fs.statSync(result + '/README.md').size.should.be.above(0); 44 | fs.statSync(result + '/LICENSE').size.should.be.above(0); 45 | var sf = JSON.parse(fs.readFileSync(result + "/subflow.json")); 46 | sf.should.have.property("flow"); 47 | sf.flow.should.have.property("encoding", "AES"); 48 | del.sync(result); 49 | done(); 50 | } 51 | catch (e) { 52 | done(e); 53 | } 54 | }); 55 | }); 56 | it('should create tgz', function (done) { 57 | var options = { 58 | tgz: true 59 | }; 60 | var data = { dst: '.' }; 61 | data.src = JSON.parse(fs.readFileSync('samples/qrcode.json')); 62 | subflow2node(data, options).then(function (result) { 63 | try { 64 | fs.statSync(result).isFile().should.be.eql(true); 65 | del.sync(result); 66 | del.sync("./node-red-contrib-qrcode"); 67 | done(); 68 | } 69 | catch (e) { 70 | done(e); 71 | } 72 | }); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /test/lib/function_spec.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const should = require('should'); 4 | const del = require('del'); 5 | const function2node = require('../../lib/function'); 6 | 7 | describe('Function node', function () { 8 | it('should have node files', function (done) { 9 | var options = {}; 10 | var data = { dst: '.' }; 11 | data.src = fs.readFileSync('samples/lower-case.js'); 12 | function2node(data, options).then(function (result) { 13 | var packageSourceCode = JSON.parse(fs.readFileSync(result + '/package.json')); 14 | packageSourceCode.name.should.equal('node-red-contrib-lowercase'); 15 | packageSourceCode.version.should.equal('0.0.1'); 16 | fs.statSync(result + '/node.html').size.should.be.above(0); 17 | fs.statSync(result + '/node.js').size.should.be.above(0); 18 | fs.statSync(result + '/test/node_spec.js').size.should.be.above(0); 19 | fs.statSync(result + '/icons/icon.svg').size.should.be.above(0); 20 | fs.statSync(result + '/README.md').size.should.be.above(0); 21 | fs.statSync(result + '/LICENSE').size.should.be.above(0); 22 | del.sync(result); 23 | done(); 24 | }); 25 | }); 26 | it('should handle parameters (node and module name)', function (done) { 27 | var options = {}; 28 | var data = { 29 | name: 'nodename', 30 | module: 'node-red-node-function-node', 31 | dst: '.' 32 | }; 33 | data.src = fs.readFileSync('samples/lower-case.js'); 34 | function2node(data, options).then(function (result) { 35 | var packageSourceCode = JSON.parse(fs.readFileSync(result + '/package.json')); 36 | packageSourceCode.name.should.equal('node-red-node-function-node'); 37 | packageSourceCode.version.should.equal('0.0.1'); 38 | del.sync(result); 39 | done(); 40 | }); 41 | }); 42 | it('should handle parameters (prefix and node name)', function (done) { 43 | var options = {}; 44 | var data = { 45 | prefix: 'node-red-prefix-', 46 | name: 'nodename', 47 | dst: '.' 48 | }; 49 | data.src = fs.readFileSync('samples/lower-case.js'); 50 | function2node(data, options).then(function (result) { 51 | var packageSourceCode = JSON.parse(fs.readFileSync(result + '/package.json')); 52 | packageSourceCode.name.should.equal('node-red-prefix-nodename'); 53 | packageSourceCode.version.should.equal('0.0.1'); 54 | del.sync(result); 55 | done(); 56 | }); 57 | }); 58 | it('should handle parameters (version)', function (done) { 59 | var options = {}; 60 | var data = { 61 | version: '4.5.1', 62 | dst: '.' 63 | }; 64 | data.src = fs.readFileSync('samples/lower-case.js'); 65 | function2node(data, options).then(function (result) { 66 | var packageSourceCode = JSON.parse(fs.readFileSync(result + '/package.json')); 67 | packageSourceCode.name.should.equal('node-red-contrib-lowercase'); 68 | packageSourceCode.version.should.equal('4.5.1'); 69 | del.sync(result); 70 | done(); 71 | }); 72 | }); 73 | it('should handle parameters (keywords)', function (done) { 74 | var options = {}; 75 | var data = { 76 | keywords: 'node-red,function,lowercase', 77 | dst: '.' 78 | }; 79 | data.src = fs.readFileSync('samples/lower-case.js'); 80 | function2node(data, options).then(function (result) { 81 | var packageSourceCode = JSON.parse(fs.readFileSync(result + '/package.json')); 82 | packageSourceCode.name.should.equal('node-red-contrib-lowercase'); 83 | packageSourceCode.keywords.should.eql(['node-red-nodegen', 'node-red', 'function', 'lowercase']); 84 | del.sync(result); 85 | done(); 86 | }); 87 | }); 88 | it('should handle options', function (done) { 89 | var options = { 90 | tgz: true, 91 | obfuscate: true 92 | }; 93 | var data = { dst: '.' }; 94 | data.src = fs.readFileSync('samples/lower-case.js'); 95 | function2node(data, options).then(function (result) { 96 | fs.statSync(result).isFile().should.be.eql(true); 97 | del.sync(result); 98 | var jsfile = result.replace(/-[0-9]+\.[0-9]+\.[0-9]+\.tgz$/, '/node.js'); 99 | fs.readFileSync(jsfile).toString().split('\n').length.should.be.eql(1); 100 | result = result.replace(/-[0-9]+\.[0-9]+\.[0-9]+\.tgz$/, ''); 101 | del.sync(result); 102 | done(); 103 | }); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /lib/subflow/index.js: -------------------------------------------------------------------------------- 1 | const util = require("../util"); 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const mustache = require('mustache'); 6 | const crypt = require("crypto-js"); 7 | 8 | const TEMPLATE_DIR = path.join(__dirname,'../../templates/subflow'); 9 | 10 | // Extract Subflow definition from JSON data 11 | function getSubflowDef(flow) { 12 | const newFlow = []; 13 | let sf = null; 14 | flow.forEach((item) => { 15 | if (item.hasOwnProperty("meta") && 16 | item.meta.hasOwnProperty("module")) { 17 | if (sf !== null) { 18 | throw new Error("unexpected subflow definition"); 19 | } 20 | sf = item; 21 | } 22 | else { 23 | newFlow.push(item); 24 | } 25 | }); 26 | if (sf == null) { 27 | throw new Error("No module properties in subflow"); 28 | } 29 | return [sf, newFlow]; 30 | } 31 | 32 | // get flow encoding method 33 | function getEncoder(encoding) { 34 | if (encoding === "AES") { 35 | return function (flow, key) { 36 | var data = JSON.stringify(flow); 37 | var enc = crypt.AES.encrypt(data, key); 38 | return enc.toString(); 39 | } 40 | } 41 | throw new Error("encoding not defined:" +encoding); 42 | } 43 | 44 | // Create JSON data file 45 | function createJSON(dstPath, flow, encoding, key) { 46 | const [sf, newFlow] = getSubflowDef(flow); 47 | if (encoding && (encoding !== "none")) { 48 | const encode = getEncoder(encoding); 49 | const encStr = encode(newFlow, key); 50 | sf.flow = { 51 | encoding: encoding, 52 | flow: encStr 53 | }; 54 | } 55 | else { 56 | sf.flow = newFlow; 57 | } 58 | const data = JSON.stringify(sf, null, 4); 59 | fs.writeFileSync(dstPath, data); 60 | } 61 | 62 | module.exports = async function(data, options) { 63 | "use strict"; 64 | 65 | const json = data.src; 66 | 67 | // Get subflow & flow definition 68 | const [sf, newFlow] = getSubflowDef(json); 69 | const meta = sf.meta; 70 | 71 | data.name = meta.type; 72 | data.module = meta.module; 73 | data.version = meta.version; 74 | data.desc = meta.desc; 75 | if (!data.desc || (data.desc === "")) { 76 | data.desc = "Node-RED node for " +data.name; 77 | } 78 | data.license = meta.license; 79 | if (!data.license || (data.license === "")) { 80 | data.license = "unknown"; 81 | } 82 | data.keywords = data.keywords || meta.keywords; 83 | data.info = meta.info; 84 | 85 | var params = { 86 | nodeName: data.name, 87 | projectName: data.module, 88 | projectVersion: data.version, 89 | keywords: util.extractKeywords(data.keywords), 90 | category: data.category || "subflow", 91 | description: data.desc, 92 | licenseName: data.license, 93 | nodeRead: sf.info || "", 94 | encoding: options.encoding 95 | }; 96 | 97 | // Make directory 98 | try { 99 | fs.mkdirSync(path.join(data.dst, data.module)); 100 | } catch (error) { 101 | if (error.code !== "EEXIST") { 102 | throw error; 103 | } 104 | } 105 | 106 | // Create subflow.json 107 | createJSON(path.join(data.dst, data.module, "subflow.json"), 108 | json, (options.encoding || "none"), options.encodekey); 109 | 110 | // Create package.json 111 | var packageTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, "package.json.mustache"), "utf-8"); 112 | var packageSourceCode = mustache.render(packageTemplate, params); 113 | fs.writeFileSync(path.join(data.dst, data.module, "package.json"), packageSourceCode); 114 | 115 | // Create subflow.js 116 | var nodeTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, "subflow.js.mustache"), "utf-8"); 117 | var nodeSourceCode = mustache.render(nodeTemplate, params); 118 | fs.writeFileSync(path.join(data.dst, data.module, "subflow.js"), nodeSourceCode); 119 | 120 | // Create README.md 121 | var readmeTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, "README.md.mustache"), "utf-8"); 122 | var readmeSourceCode = mustache.render(readmeTemplate, params); 123 | fs.writeFileSync(path.join(data.dst, data.module, "README.md"), readmeSourceCode); 124 | 125 | // Create LICENSE file 126 | var licenseTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, "LICENSE.mustache"), "utf-8"); 127 | var licenseSourceCode = mustache.render(licenseTemplate, params); 128 | fs.writeFileSync(path.join(data.dst, data.module, "LICENSE"), licenseSourceCode); 129 | 130 | if (options.tgz) { 131 | util.runNpmPack(data); 132 | return(path.join(data.dst, data.module + "-" + data.version + ".tgz")); 133 | } else { 134 | return(path.join(data.dst, data.module)); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const csv = require('csv-string'); 5 | const child_process = require('child_process'); 6 | const jimp = require("jimp"); 7 | 8 | 9 | function createCommonFiles(templateDirectory, data) { 10 | "use strict"; 11 | // Make directories 12 | try { 13 | fs.mkdirSync(path.join(data.dst, data.module)); 14 | } catch (error) { 15 | if (error.code !== 'EEXIST') { 16 | console.error(error); 17 | } 18 | } 19 | 20 | var isStockIcon = data.icon && (data.icon.match(/^(alert|arduino|arrow-in|batch|bluetooth|bridge-dash|bridge|cog|comment|db|debug|envelope|feed|file-in|file-out|file|function|hash|inject|join|leveldb|light|link-out|mongodb|mouse|node-changed|node-error|parser-csv|parser-html|parser-json|parser-xml|parser-yaml|range|redis|rpi|serial|sort|split|subflow|swap|switch|template|timer|trigger|twitter|watch|white-globe)\.png$/) || data.icon.match(/^(node-red|font-awesome)/)); 21 | if (!isStockIcon) { 22 | try { 23 | fs.mkdirSync(path.join(data.dst, data.module, 'icons')); 24 | } catch (error) { 25 | if (error.code !== 'EEXIST') { 26 | console.error(error); 27 | } 28 | } 29 | } 30 | if (data.icon) { 31 | if (!isStockIcon) { 32 | try { 33 | jimp.read(data.icon, function (error2, image) { 34 | if (!error2) { 35 | var outputPath = path.join(data.dst, data.module, 'icons', path.basename(data.icon)); 36 | if (image.bitmap.width === 40 && image.bitmap.height === 60) { 37 | var buf = fs.readFileSync(data.icon); 38 | fs.writeFileSync(outputPath, buf); 39 | } else { 40 | image.background(0xFFFFFFFF).resize(40, 60).write(outputPath); 41 | } 42 | } else { 43 | console.log('error occurs while converting icon file.'); 44 | } 45 | }); 46 | } catch (error) { 47 | console.error(error); 48 | } 49 | } 50 | } else { 51 | var icons = fs.readdirSync(path.join(templateDirectory, 'icons')); 52 | icons.forEach(function (icon) { 53 | try { 54 | var buf = fs.readFileSync(path.join(templateDirectory, 'icons', icon)); 55 | fs.writeFileSync(path.join(data.dst, data.module, 'icons', icon), buf); 56 | } catch (error) { 57 | console.error(error); 58 | } 59 | }); 60 | } 61 | 62 | try { 63 | fs.mkdirSync(path.join(data.dst, data.module, 'locales')); 64 | } catch (error) { 65 | if (error.code !== 'EEXIST') { 66 | console.error(error); 67 | } 68 | } 69 | try { 70 | var languages = fs.readdirSync(path.join(templateDirectory, 'locales')); 71 | languages.forEach(function (language) { 72 | try { 73 | fs.mkdirSync(path.join(data.dst, data.module, 'locales', language)); 74 | } catch (error) { 75 | if (error.code !== 'EEXIST') { 76 | console.error(error); 77 | } 78 | } 79 | }); 80 | } catch (error) { 81 | if (error.code !== 'ENOENT') { 82 | console.error(error); 83 | } 84 | } 85 | try { 86 | fs.mkdirSync(path.join(data.dst, data.module, 'examples')); 87 | } catch (error) { 88 | if (error.code !== 'EEXIST') { 89 | console.error(error); 90 | } 91 | } 92 | try { 93 | fs.mkdirSync(path.join(data.dst, data.module, 'test')); 94 | } catch (error) { 95 | if (error.code !== 'EEXIST') { 96 | console.error(error); 97 | } 98 | } 99 | } 100 | 101 | function runNpmPack(data) { 102 | "use strict"; 103 | var npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; 104 | try { 105 | child_process.execFileSync(npmCommand, ['pack', './' + data.module], { cwd: data.dst }); 106 | } catch (error) { 107 | console.error(error); 108 | } 109 | } 110 | 111 | function extractKeywords(keywordsStr) { 112 | "use strict"; 113 | var keywords = ["node-red-nodegen"]; 114 | keywords = keywordsStr ? keywords.concat(csv.parse(keywordsStr)[0]) : keywords; 115 | keywords = keywords.map(k => ({ name: k })); 116 | keywords[keywords.length - 1].last = true; 117 | return keywords; 118 | } 119 | 120 | function skipBom(body) { 121 | if (body[0]===0xEF && 122 | body[1]===0xBB && 123 | body[2]===0xBF) { 124 | return body.slice(3); 125 | } else { 126 | return body; 127 | } 128 | } 129 | 130 | module.exports = { 131 | createCommonFiles, 132 | runNpmPack, 133 | extractKeywords, 134 | skipBom 135 | } 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node generator for Node-RED 2 | 3 | Node generator is a command line tool to generate Node-RED nodes based on various sources such as an OpenAPI (Swagger) document, a Node-RED Function node, or a Web Of Things Thing description. 4 | It helps developers dramatically reduce the time to implement Node-RED nodes. 5 | 6 | ## Installation 7 | 8 | Install node generator globally to make the `node-red-nodegen` command available on your path: 9 | 10 | npm install -g node-red-nodegen 11 | 12 | You may need to run this with `sudo`, or from within an Administrator command shell. 13 | 14 | ## Usage 15 | 16 | Usage: 17 | node-red-nodegen [-o ] [--prefix ] [--name ] [--module ] [--version ] [--keywords ] [--category ] [--icon ] [--color ] [--tgz] [--help] [--wottd] [--encoding ] [--encodekey ] [--lang ] [-v] 18 | 19 | Description: 20 | Node generator for Node-RED 21 | 22 | Supported source: 23 | - OpenAPI document 24 | - Function node (js file in library, "~/.node-red/lib/function/") 25 | - Subflow node (json file of subflow) 26 | - (Beta) Thing Description of W3C Web of Things (jsonld file or URL that points jsonld file) 27 | 28 | Options: 29 | -o : Destination path to save generated node (default: current directory) 30 | --prefix : Prefix of npm module (default: "node-red-contrib-") 31 | --name : Node name (default: name defined in source) 32 | --module : Module name (default: "node-red-contrib-") 33 | --version : Node version (format: "number.number.number" like "4.5.1") 34 | --keywords : Additional keywords (format: comma separated string, default: "node-red-nodegen") 35 | --category : Node category (default: "function") 36 | --icon : PNG file for node appearance (image size should be 10x20) 37 | --color : Color for node appearance (format: color hexadecimal numbers like "A6BBCF") 38 | --tgz : Save node as tgz file 39 | --help : Show help 40 | --wottd : explicitly instruct source file/URL points a Thing Description 41 | --encoding : Encoding scheme of subflow (none or AES) 42 | --encodekey : Encoding key of subflow 43 | --lang : Language negotiation information when retrieve a Thing Description 44 | -v : Show node generator version 45 | 46 | ### Example 1. Create an original node from OpenAPI document 47 | 48 | - node-red-nodegen http://petstore.swagger.io/v2/swagger.json 49 | - cd ~/.node-red 50 | - npm install \/node-red-contrib-swagger-petstore 51 | - node-red 52 | 53 | -> You can use swagger-petstore node on Node-RED flow editor. 54 | 55 | ### Example 2. Create an original node from function node (JavaScript code) 56 | 57 | - In Node-RED flow editor, edit the function node and to the right of the 'name' option, click on the book icon and select 'Save to library'. Then fill in the 'Export as' with the file name (lower-case.js). 58 | - node-red-nodegen ~/.node-red/lib/functions/lower-case.js 59 | - cd ~/.node-red 60 | - npm install \/node-red-contrib-lower-case 61 | - node-red 62 | 63 | -> You can use lower-case node on Node-RED flow editor. 64 | 65 | ### Example 3. Create original node from Thing Description 66 | 67 | - node-red-nodegen example.jsonld 68 | - cd ~/.node-red 69 | - npm install \/node-red-contrib-example-thing 70 | - node-red 71 | 72 | -> You can use Example Thing node on Node-RED flow editor. 73 | 74 | ### Example 4. Create original node from Thing Description via HTTP 75 | 76 | - node-red-nodegen http://example.com/td.jsonld --wottd --lang "en-US,en;q=0.5" 77 | - cd ~/.node-red 78 | - npm install \/node-red-contrib-example-thing 79 | - node-red 80 | 81 | -> You can use Example Thing node on Node-RED flow editor. 82 | 83 | ### Example 5. Create an original node from SUBFLOW definition 84 | 85 | - In Node-RED flow editor, create the SUBFLOW template with module properties filled. Here, we assume module name for the SUBFLOW template is node-red-contrib-qrcode. 86 | - Export JSON definition of the SUBFLOW template from Export menu. We assume it is saved to a file named qrcode.json. 87 | - node-red-nodegen qrcode.json 88 | - cd ~/.node-red 89 | - npm install \/node-red-contrib-qrcode 90 | - node-red 91 | 92 | -> You can use qrcode node on Node-RED flow editor. 93 | 94 | ## Documentation 95 | 96 | - [Use cases](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#use-cases) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#use-cases)) 97 | - [How to use Node generator](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#how-to-use-node-generator) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#how-to-use-node-generator)) 98 | - [Generated files which node package contains](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#generated-files-which-node-package-contains) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#generated-files-which-node-package-contains)) 99 | - [How to create a node from OpenAPI document](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#how-to-create-a-node-from-openapi-document) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#how-to-create-a-node-from-openapi-document)) 100 | - [How to create a node from function node](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#how-to-create-a-node-from-function-node) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#how-to-create-a-node-from-function-node)) 101 | - [How to create a node from subflow](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#how-to-create-a-node-from-subflow) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#how-to-create-a-node-from-subflow)) 102 | - [How to create a node from WoT Thing Description](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index.md#how-to-create-a-node-from-wot-thing-description) ([Japanese](https://github.com/node-red/node-red-nodegen/blob/0.2.4/docs/index_ja.md#how-to-create-a-node-from-wot-thing-description)) 103 | 104 | Note: Currently node generator supports GET and POST methods using JSON format without authentication. 105 | -------------------------------------------------------------------------------- /bin/node-red-nodegen.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Copyright OpenJS Foundation and other contributors 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | **/ 18 | 19 | var fs = require('fs'); 20 | var path = require('path'); 21 | 22 | var argv = require('minimist')(process.argv.slice(2)); 23 | var colors = require('colors'); 24 | var nodegen = require('../lib/nodegen.js'); 25 | 26 | // Command options 27 | var options = {}; 28 | options.tgz = argv.tgz; 29 | options.obfuscate = argv.obfuscate; 30 | options.encoding = argv.encoding; 31 | options.encodekey = argv.encodekey; 32 | 33 | var data = { 34 | prefix: argv.prefix || argv.p, 35 | name: argv.name || argv.n, 36 | module: argv.module, 37 | version: argv.version, 38 | keywords: argv.keywords || argv.k, 39 | category: argv.category || argv.c, 40 | icon: argv.icon, 41 | color: argv.color, 42 | dst: argv.output || argv.o || '.', 43 | lang: argv.lang 44 | }; 45 | 46 | function help() { 47 | "use strict"; 48 | var helpText = 'Usage:'.bold + '\n' + 49 | ' node-red-nodegen ' + 50 | ' [-o ]' + 51 | ' [--prefix ]' + 52 | ' [--name ]' + 53 | ' [--module ]' + 54 | ' [--version ]' + 55 | ' [--keywords ]' + 56 | ' [--category ]' + 57 | ' [--icon ]' + 58 | ' [--color ]' + 59 | ' [--tgz]' + 60 | ' [--help]' + 61 | ' [--wottd]' + 62 | ' [--encoding ]' + 63 | ' [--encodekey ]' + 64 | ' [--lang ]' + 65 | ' [-v]\n' + 66 | '\n' + 67 | 'Description:'.bold + '\n' + 68 | ' Node generator for Node-RED\n' + 69 | '\n' + 70 | 'Supported source:'.bold + '\n' + 71 | ' - OpenAPI document\n' + 72 | ' - Function node (js file in library, "~/.node-red/lib/function/")\n' + 73 | ' - Subflow node (json file of subflow)\n' + 74 | ' - (Beta) Thing Description of W3C Web of Things (jsonld file or URL that points jsonld file)\n' + 75 | '\n' + 76 | 'Options:\n'.bold + 77 | ' -o : Destination path to save generated node (default: current directory)\n' + 78 | ' --prefix : Prefix of npm module (default: "node-red-contrib-")\n' + 79 | ' --name : Node name (default: name defined in source)\n' + 80 | ' --module : Module name (default: "node-red-contrib-")\n' + 81 | ' --version : Node version (format: "number.number.number" like "4.5.1")\n' + 82 | ' --keywords : Additional keywords (format: comma separated string, default: "node-red-nodegen")\n' + 83 | ' --category : Node category (default: "function")\n' + 84 | ' --icon : PNG file for node appearance (image size should be 10x20)\n' + 85 | ' --color : Color for node appearance (format: color hexadecimal numbers like "A6BBCF")\n' + 86 | ' --tgz : Save node as tgz file\n' + 87 | ' --help : Show help\n' + 88 | ' --wottd : explicitly instruct source file/URL points a Thing Description\n' + 89 | ' --encoding : Encoding scheme of subflow (none or AES)\n' + 90 | ' --encodekey : Encoding key of subflow\n' + 91 | ' --lang : Language negotiation information when retrieve a Thing Description\n' + 92 | ' -v : Show node generator version\n'; 93 | console.log(helpText); 94 | } 95 | 96 | function version() { 97 | var packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'))); 98 | console.log(packageJson.version); 99 | } 100 | 101 | function skipBom(body) { 102 | if (body[0]===0xEF && 103 | body[1]===0xBB && 104 | body[2]===0xBF) { 105 | return body.slice(3); 106 | } else { 107 | return body; 108 | } 109 | } 110 | 111 | function isSubflowDefinition(data) { 112 | return data.find((item) => { 113 | return item.type === "subflow"; 114 | }); 115 | } 116 | 117 | if (argv.help || argv.h) { 118 | help(); 119 | } else if (argv.v) { 120 | version(); 121 | } else { 122 | var promise; 123 | var sourcePath = argv._[0]; 124 | if (sourcePath) { 125 | data.src = sourcePath; 126 | if (argv.wottd || /\.jsonld$/.test(sourcePath)) { 127 | // Explicitly a Web Of Things request 128 | promise = nodegen.WebOfThingsGenerator(data, options); 129 | } else if (/^https?:/.test(sourcePath) || /.yaml$/.test(sourcePath)) { 130 | // URL/yaml -> swagger 131 | promise = nodegen.SwaggerNodeGenerator(data, options); 132 | } else if (/\.json$/.test(sourcePath)) { 133 | // JSON could be a Function node, or Swagger 134 | var content = JSON.parse(fs.readFileSync(sourcePath)); 135 | if (Array.isArray(content)) { 136 | data.src = content; 137 | if (isSubflowDefinition(content)) { 138 | promise = nodegen.SubflowNodeGenerator(data, options); 139 | } 140 | else { 141 | promise = nodegen.FunctionNodeGenerator(data, options); 142 | } 143 | } else { 144 | promise = nodegen.SwaggerNodeGenerator(data, options); 145 | } 146 | } else if (/\.js$/.test(sourcePath)) { 147 | // .js -> Function node 148 | promise = nodegen.FunctionNodeGenerator(data, options); 149 | } else { 150 | console.error('error: Unsupported file type'); 151 | help(); 152 | return; 153 | } 154 | if (promise) { 155 | promise.then(function (result) { 156 | console.log('Success: ' + result); 157 | }).catch(function (error) { 158 | console.log(error.stack); 159 | }); 160 | } 161 | } else { 162 | help(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /lib/function/index.js: -------------------------------------------------------------------------------- 1 | const util = require("../util"); 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const mustache = require('mustache'); 6 | const jsStringEscape = require('js-string-escape'); 7 | const obfuscator = require('javascript-obfuscator'); 8 | 9 | const TEMPLATE_DIR = path.join(__dirname,'../../templates/function'); 10 | 11 | 12 | module.exports = async function(data, options) { 13 | "use strict"; 14 | 15 | if (Array.isArray(data.src)) { 16 | if (data.src.length !== 1) { 17 | throw new Error("Invalid flow json - expected a single Function node"); 18 | } 19 | var f = data.src[0]; 20 | if (!f.name || f.name.length ==0) { 21 | throw new Error('Error: No function name supplied.'); 22 | } 23 | data.name = f.name.toLowerCase(); 24 | data.icon = f.icon; 25 | data.info = f.info; 26 | data.outputs = f.outputs; 27 | data.inputLabels = f.inputLabels; 28 | data.outputLabels = f.outputLabels; 29 | data.src = Buffer.from(f.func); 30 | } else if (typeof data.src === "string" && /\.js$/.test(data.src)) { 31 | data.src = await util.skipBom(await fs.promises.readFile(data.src)); 32 | } 33 | 34 | // Read meta data in js file 35 | var meta = {}; 36 | var parts = new String(data.src).split('\n'); 37 | parts.forEach(function (part) { 38 | var match = /^\/\/ (\w+): (.*)/.exec(part.toString()); 39 | if (match) { 40 | if (match[1] === 'name') { 41 | meta.name = match[2].replace(/([A-Z])/g, ' $1').toLowerCase().replace(/[^ a-z0-9]+/g, '').replace(/^ | $/, '').replace(/ +/g, '-'); 42 | } else { 43 | meta[match[1]] = match[2]; 44 | } 45 | } 46 | }); 47 | 48 | if (!data.name || data.name === '') { 49 | data.name = meta.name; 50 | } 51 | 52 | if (data.module) { 53 | if (data.prefix) { 54 | throw new Error('module name and prefix are conflicted.'); 55 | } 56 | } else { 57 | if (data.prefix) { 58 | data.module = data.prefix + data.name; 59 | } else { 60 | data.module = 'node-red-contrib-' + data.name; 61 | } 62 | } 63 | 64 | if (!data.version || data.version === '') { 65 | data.version = '0.0.1'; 66 | } 67 | 68 | if (data.icon) { 69 | if (!data.icon.match(/\.(png|gif)$/) && !data.icon.match(/^(node-red|font-awesome)/)) { 70 | data.icon = data.icon + '.png'; 71 | } 72 | if (!data.icon.match(/^[a-zA-Z0-9\-\./]+$/)) { 73 | throw new Error('invalid icon file name'); 74 | } 75 | } 76 | 77 | if (data.color) { 78 | if (data.color.match(/^[a-zA-Z0-9]{6}$/)) { 79 | data.color = '#' + data.color; 80 | } else { 81 | throw new Error('invalid color'); 82 | } 83 | } 84 | 85 | if (data.name === 'function') { 86 | throw new Error('\'function\' is duplicated node name. Use another name.'); 87 | } else if (!data.name.match(/^[a-z0-9\-]+$/)) { 88 | throw new Error('invalid node name'); 89 | } else { 90 | var params = { 91 | nodeName: data.name, 92 | projectName: data.module, 93 | projectVersion: data.version, 94 | keywords: util.extractKeywords(data.keywords), 95 | category: data.category || 'function', 96 | icon: function () { 97 | if (data.icon ) { 98 | if (!data.icon.match(/^(node-red|font-awesome)/)) { 99 | return path.basename(data.icon); 100 | } 101 | else { return data.icon; } 102 | } else { 103 | return 'icon.png'; 104 | } 105 | }, 106 | color: data.color || '#C0DEED', 107 | func: jsStringEscape(data.src), 108 | outputs: meta.outputs || data.outputs, 109 | inputLabels: JSON.stringify(data.inputLabels || []), 110 | outputLabels: JSON.stringify(data.outputLabels || []), 111 | nodeInfo: jsStringEscape(data.info || ""), 112 | nodeRead: data.info || "" 113 | }; 114 | 115 | util.createCommonFiles(TEMPLATE_DIR, data); 116 | 117 | // Create package.json 118 | var packageTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, 'package.json.mustache'), 'utf-8'); 119 | var packageSourceCode = mustache.render(packageTemplate, params); 120 | fs.writeFileSync(path.join(data.dst, data.module, 'package.json'), packageSourceCode); 121 | 122 | // Create node.js 123 | var nodeTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, 'node.js.mustache'), 'utf-8'); 124 | var nodeSourceCode = mustache.render(nodeTemplate, params); 125 | if (options.obfuscate) { 126 | nodeSourceCode = obfuscator.obfuscate(nodeSourceCode, { stringArrayEncoding: 'rc4' }).getObfuscatedCode(); 127 | } 128 | fs.writeFileSync(path.join(data.dst, data.module, 'node.js'), nodeSourceCode); 129 | 130 | // Create node.html 131 | var htmlTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, 'node.html.mustache'), 'utf-8'); 132 | var htmlSourceCode = mustache.render(htmlTemplate, params); 133 | fs.writeFileSync(path.join(data.dst, data.module, 'node.html'), htmlSourceCode); 134 | 135 | // Create flow.json 136 | var flowTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, 'examples/flow.json.mustache'), 'utf-8'); 137 | var flowSourceCode = mustache.render(flowTemplate, params); 138 | fs.writeFileSync(path.join(data.dst, data.module, 'examples/flow.json'), flowSourceCode); 139 | 140 | // Create node_spec.js 141 | var nodeSpecTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, 'test/node_spec.js.mustache'), 'utf-8'); 142 | var nodeSpecSourceCode = mustache.render(nodeSpecTemplate, params); 143 | fs.writeFileSync(path.join(data.dst, data.module, 'test/node_spec.js'), nodeSpecSourceCode); 144 | 145 | // Create README.md 146 | var readmeTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, 'README.md.mustache'), 'utf-8'); 147 | var readmeSourceCode = mustache.render(readmeTemplate, params); 148 | fs.writeFileSync(path.join(data.dst, data.module, 'README.md'), readmeSourceCode); 149 | 150 | // Create LICENSE file 151 | var licenseTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, 'LICENSE.mustache'), 'utf-8'); 152 | var licenseSourceCode = mustache.render(licenseTemplate, params); 153 | fs.writeFileSync(path.join(data.dst, data.module, 'LICENSE'), licenseSourceCode); 154 | 155 | if (options.tgz) { 156 | util.runNpmPack(data); 157 | return path.join(data.dst, data.module + '-' + data.version + '.tgz'); 158 | } else { 159 | return path.join(data.dst, data.module); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /lib/webofthings/index.js: -------------------------------------------------------------------------------- 1 | const util = require("../util"); 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const mustache = require('mustache'); 6 | const obfuscator = require('javascript-obfuscator'); 7 | const axios = require('axios').default; 8 | 9 | const wotutils = require('./wotutils'); 10 | 11 | const TEMPLATE_DIR = path.join(__dirname,'../../templates/webofthings'); 12 | 13 | async function getSpec(src) { 14 | let spec; 15 | if (typeof src === "string") { 16 | if (/^https?:/.test(src)) { 17 | const response = await axios.get(src); 18 | spec = response.data; 19 | } else { 20 | spec = JSON.parse(util.skipBom(await fs.promises.readFile(src))); 21 | } 22 | } else { 23 | spec = src; 24 | } 25 | return spec; 26 | } 27 | 28 | module.exports = async function(data, options) { 29 | 30 | let td = await getSpec(data.src); 31 | 32 | // validate TD 33 | const validateResult = wotutils.validateTd(td); 34 | if (validateResult.result === false) { 35 | console.warn(`Invalid Thing Description:\n${validateResult.errorText}`); 36 | } else { 37 | console.info(`Schema validation succeeded.`); 38 | } 39 | 40 | // if there is no title but titles, put one of titles to title. 41 | if (!td.title) { 42 | if (td.titles) { 43 | if (td.titles.en) { 44 | td.title = td.titles.en 45 | } else { 46 | td.title = Object.values(td.titles)[0]; 47 | } 48 | } else { 49 | td.title = "notitle"; 50 | } 51 | } 52 | 53 | // if name is not specified, use td.title for module name. 54 | if (!data.name || data.name === '') { 55 | // filtering out special characters 56 | data.name = 'wot' + td.title.replace(/[^A-Za-z0-9]/g, '').toLowerCase(); 57 | } 58 | 59 | if (data.module) { 60 | if (data.prefix) { 61 | throw new Error('module name and prefix are conflicted'); 62 | } 63 | } else { 64 | if (data.prefix) { 65 | data.module = data.prefix + data.name; 66 | } else { 67 | data.module = 'node-red-contrib-' + data.name; 68 | } 69 | } 70 | 71 | if (!data.version || data.version === '') { 72 | if (td.version && td.version.instance) { 73 | data.version = td.version.instance; 74 | } else { 75 | data.version = '0.0.1'; 76 | } 77 | } 78 | 79 | data.tdstr = JSON.stringify(td); 80 | td = wotutils.normalizeTd(td); 81 | td = wotutils.filterFormTd(td); 82 | data.normtd = JSON.stringify(td); 83 | data.properties = []; 84 | const rwo = {}; 85 | for (const p in td.properties) { // convert to array 86 | if (td.properties.hasOwnProperty(p)) { 87 | const q = td.properties[p]; 88 | q.name = p; 89 | if (!q.title || q.title === '') { 90 | q.title = q.name; 91 | } 92 | if (q.forms) { 93 | for (var i = 0; i < q.forms.length; i++) { 94 | q.forms[i].index = i; 95 | } 96 | } 97 | data.properties.push(q); 98 | rwo[p] = { 99 | readable: !q.writeOnly, 100 | writable: !q.readOnly, 101 | observable: q.observable 102 | }; 103 | 104 | } 105 | } 106 | data.actions = []; 107 | for (const a in td.actions) { 108 | if (td.actions.hasOwnProperty(a)) { 109 | const q = td.actions[a]; 110 | q.name = a; 111 | if (!q.title || q.title === '') { 112 | q.title = q.name; 113 | } 114 | if (q.forms) { 115 | for (var i = 0; i < q.forms.length; i++) { 116 | q.forms[i].index = i; 117 | } 118 | } 119 | data.actions.push(q); 120 | } 121 | } 122 | data.events = []; 123 | for (const e in td.events) { 124 | if (td.events.hasOwnProperty(e)) { 125 | const q = td.events[e]; 126 | q.name = e; 127 | if (!q.title || q.title === '') { 128 | q.title = q.name; 129 | } 130 | if (q.forms) { 131 | for (var i = 0; i < q.forms.length; i++) { 132 | q.forms[i].index = i; 133 | } 134 | } 135 | data.events.push(q); 136 | } 137 | } 138 | 139 | const wotmeta = []; 140 | if (td.hasOwnProperty('lastModified')) { 141 | wotmeta.push({name: "lastModified", value: td.lastModified}); 142 | } 143 | if (td.hasOwnProperty('created')) { 144 | wotmeta.push({name: "created", value: td.created}); 145 | } 146 | if (td.hasOwnProperty('support')) { 147 | wotmeta.push({name: "support", value: JSON.stringify(td.support)}); 148 | } 149 | if (td.hasOwnProperty("id")) { 150 | wotmeta.push({name: "id", value: td.id, last: true}); 151 | } 152 | 153 | const formsel = wotutils.makeformsel(td); 154 | 155 | data.genformsel = JSON.stringify(formsel); 156 | data.genproprwo = JSON.stringify(rwo); 157 | 158 | data.ufName = td.title.replace(/[^A-Za-z0-9]/g, ' ').trim(); 159 | data.nodeName = data.name; 160 | data.projectName = data.module; 161 | data.projectVersion = data.version; 162 | data.keywords = util.extractKeywords(data.keywords); 163 | data.category = data.category || 'Web of Things'; 164 | data.description = td.description; 165 | data.licenseName = 'Apache-2.0'; 166 | data.licenseUrl = ''; 167 | data.links = td.links; 168 | data.support = td.support; 169 | data.iconpath = wotutils.woticon(td); 170 | data.wotmeta = wotmeta; 171 | 172 | let lang = null; 173 | if (td.hasOwnProperty('@context') && Array.isArray(td['@context'])) { 174 | td['@context'].forEach(e => { 175 | if (e.hasOwnProperty("@language")) { 176 | lang = e['@language']; 177 | } 178 | }); 179 | } 180 | if (lang === null) { 181 | data.textdir = "auto"; 182 | } else { 183 | data.textdir = wotutils.textDirection(lang); 184 | } 185 | 186 | util.createCommonFiles(TEMPLATE_DIR, data); 187 | 188 | // Create package.json 189 | const packageTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, 'package.json.mustache'), 'utf-8'); 190 | const packageSourceCode = mustache.render(packageTemplate, data); 191 | fs.writeFileSync(path.join(data.dst, data.module, 'package.json'), packageSourceCode); 192 | 193 | // Create node.js 194 | const nodeTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, 'node.js.mustache'), 'utf-8'); 195 | let nodeSourceCode = mustache.render(nodeTemplate, data); 196 | if (options.obfuscate) { 197 | nodeSourceCode = obfuscator.obfuscate(nodeSourceCode, { stringArrayEncoding: 'rc4' }).getObfuscatedCode(); 198 | } 199 | fs.writeFileSync(path.join(data.dst, data.module, 'node.js'), nodeSourceCode); 200 | 201 | // Create node.html 202 | const htmlTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, 'node.html.mustache'), 'utf-8'); 203 | const htmlSourceCode = mustache.render(htmlTemplate, data); 204 | fs.writeFileSync(path.join(data.dst, data.module, 'node.html'), htmlSourceCode); 205 | 206 | // Create README.html 207 | const readmeTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, 'README.md.mustache'), 'utf-8'); 208 | const readmeSourceCode = mustache.render(readmeTemplate, data); 209 | fs.writeFileSync(path.join(data.dst, data.module, 'README.md'), readmeSourceCode); 210 | 211 | // Create LICENSE 212 | const licenseTemplate = fs.readFileSync(path.join(TEMPLATE_DIR, 'LICENSE.mustache'), 'utf-8'); 213 | const licenseSourceCode = mustache.render(licenseTemplate, data); 214 | fs.writeFileSync(path.join(data.dst, data.module, 'LICENSE'), licenseSourceCode); 215 | 216 | if (options.tgz) { 217 | util.runNpmPack(data); 218 | return path.join(data.dst, data.module + '-' + data.version + '.tgz'); 219 | } else { 220 | return path.join(data.dst, data.module); 221 | } 222 | } -------------------------------------------------------------------------------- /templates/swagger/node.js.mustache: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var lib = require('./lib.js'); 3 | 4 | module.exports = function (RED) { 5 | function {{&className}}Node(config) { 6 | RED.nodes.createNode(this, config); 7 | {{#hasServiceParams}} 8 | this.service = RED.nodes.getNode(config.service); 9 | {{/hasServiceParams}} 10 | this.method = config.method; 11 | {{#methods}} 12 | {{#parameters}} 13 | this.{{&methodName}}_{{&camelCaseName}} = config.{{&methodName}}_{{&camelCaseName}}; 14 | this.{{&methodName}}_{{&camelCaseName}}Type = config.{{&methodName}}_{{&camelCaseName}}Type || 'str'; 15 | {{/parameters}} 16 | {{/methods}} 17 | var node = this; 18 | 19 | node.on('input', function (msg) { 20 | var errorFlag = false; 21 | {{#domain}} 22 | var client = new lib.{{&className}}(); 23 | {{/domain}} 24 | {{^domain}} 25 | var client; 26 | if (this.service && this.service.host) { 27 | client = new lib.{{&className}}({ domain: this.service.host }); 28 | } else { 29 | node.error('Host in configuration node is not specified.', msg); 30 | errorFlag = true; 31 | } 32 | {{/domain}} 33 | {{#isSecure}} 34 | {{#isSecureToken}} 35 | if (!errorFlag && this.service && this.service.credentials && this.service.credentials.secureTokenValue) { 36 | if (this.service.secureTokenIsQuery) { 37 | client.setToken(this.service.credentials.secureTokenValue, 38 | this.service.secureTokenHeaderOrQueryName, true); 39 | } else { 40 | client.setToken(this.service.credentials.secureTokenValue, 41 | this.service.secureTokenHeaderOrQueryName, false); 42 | } 43 | } 44 | {{/isSecureToken}} 45 | {{#isSecureApiKey}} 46 | if (!errorFlag && this.service && this.service.credentials && this.service.credentials.secureApiKeyValue) { 47 | if (this.service.secureApiKeyIsQuery) { 48 | client.setApiKey(this.service.credentials.secureApiKeyValue, 49 | this.service.secureApiKeyHeaderOrQueryName, true); 50 | } else { 51 | client.setApiKey(this.service.credentials.secureApiKeyValue, 52 | this.service.secureApiKeyHeaderOrQueryName, false); 53 | } 54 | } 55 | {{/isSecureApiKey}} 56 | {{#isSecureBasic}} 57 | if (!errorFlag && this.service && this.service.credentials) { 58 | client.setBasicAuth(this.service.credentials.username, this.service.credentials.password); 59 | } 60 | {{/isSecureBasic}} 61 | {{/isSecure}} 62 | if (!errorFlag) { 63 | client.body = msg.payload; 64 | } 65 | 66 | var result; 67 | {{#methods}} 68 | if (!errorFlag && node.method === '{{&methodName}}') { 69 | var {{&methodName}}_parameters = []; 70 | var {{&methodName}}_nodeParam; 71 | var {{&methodName}}_nodeParamType; 72 | {{#parameters}} 73 | {{#isBodyParam}} 74 | if (typeof msg.payload === 'object') { 75 | {{&methodName}}_parameters.{{&camelCaseName}} = msg.payload; 76 | } else { 77 | node.error('Unsupported type: \'' + (typeof msg.payload) + '\', ' + 'msg.payload must be JSON object or buffer.', msg); 78 | errorFlag = true; 79 | } 80 | {{/isBodyParam}} 81 | {{#isNotBodyParam}} 82 | {{&methodName}}_nodeParam = node.{{&methodName}}_{{&camelCaseName}}; 83 | {{&methodName}}_nodeParamType = node.{{&methodName}}_{{&camelCaseName}}Type; 84 | if ({{&methodName}}_nodeParamType === 'str') { 85 | {{&methodName}}_parameters.{{&camelCaseName}} = {{&methodName}}_nodeParam || ''; 86 | } else { 87 | {{&methodName}}_parameters.{{&camelCaseName}} = RED.util.getMessageProperty(msg, {{&methodName}}_nodeParam); 88 | } 89 | {{&methodName}}_parameters.{{&camelCaseName}} = !!{{&methodName}}_parameters.{{&camelCaseName}} ? {{&methodName}}_parameters.{{&camelCaseName}} : msg.payload; 90 | {{/isNotBodyParam}} 91 | {{/parameters}} 92 | result = client.{{&methodName}}({{&methodName}}_parameters); 93 | } 94 | {{/methods}} 95 | if (!errorFlag && result === undefined) { 96 | node.error('Method is not specified.', msg); 97 | errorFlag = true; 98 | } 99 | var setData = function (msg, data) { 100 | if (data) { 101 | if (data.response) { 102 | if (data.response.statusCode) { 103 | msg.statusCode = data.response.statusCode; 104 | } 105 | if (data.response.headers) { 106 | msg.headers = data.response.headers; 107 | } 108 | if (data.response.request && data.response.request.uri && data.response.request.uri.href) { 109 | msg.responseUrl = data.response.request.uri.href; 110 | } 111 | } 112 | if (data.body) { 113 | msg.payload = data.body; 114 | } 115 | } 116 | return msg; 117 | }; 118 | if (!errorFlag) { 119 | node.status({ fill: 'blue', shape: 'dot', text: '{{&className}}.status.requesting' }); 120 | result.then(function (data) { 121 | node.send(setData(msg, data)); 122 | node.status({}); 123 | }).catch(function (error) { 124 | var message = null; 125 | if (error && error.body && error.body.message) { 126 | message = error.body.message; 127 | } 128 | node.error(message, setData(msg, error)); 129 | node.status({ fill: 'red', shape: 'ring', text: 'node-red:common.status.error' }); 130 | }); 131 | } 132 | }); 133 | } 134 | 135 | RED.nodes.registerType('{{&nodeName}}', {{&className}}Node); 136 | {{#hasServiceParams}} 137 | function {{&className}}ServiceNode(n) { 138 | RED.nodes.createNode(this, n); 139 | {{^domain}} 140 | this.host = n.host; 141 | {{/domain}} 142 | 143 | {{#isSecure}} 144 | {{#isSecureToken}} 145 | this.secureTokenValue = n.secureTokenValue; 146 | this.secureTokenHeaderOrQueryName = n.secureTokenHeaderOrQueryName; 147 | this.secureTokenIsQuery = n.secureTokenIsQuery; 148 | {{/isSecureToken}} 149 | {{#isSecureApiKey}} 150 | this.secureApiKeyValue = n.secureApiKeyValue; 151 | this.secureApiKeyHeaderOrQueryName = n.secureApiKeyHeaderOrQueryName; 152 | this.secureApiKeyIsQuery = n.secureApiKeyIsQuery; 153 | {{/isSecureApiKey}} 154 | {{#isSecureBasic}} 155 | this.username = n.username; 156 | this.password = n.password; 157 | {{/isSecureBasic}} 158 | {{/isSecure}} 159 | } 160 | 161 | RED.nodes.registerType('{{&nodeName}}-service', {{&className}}ServiceNode, { 162 | credentials: { 163 | {{#isSecure}} 164 | {{#isSecureToken}} 165 | secureTokenValue: { type: 'password' }, 166 | {{/isSecureToken}} 167 | {{#isSecureApiKey}} 168 | secureApiKeyValue: { type: 'password' }, 169 | {{/isSecureApiKey}} 170 | {{#isSecureBasic}} 171 | username: { type: 'text' }, 172 | password: { type: 'password' }, 173 | {{/isSecureBasic}} 174 | {{/isSecure}} 175 | temp: { type: 'text' } 176 | } 177 | }); 178 | {{/hasServiceParams}} 179 | }; 180 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /templates/function/LICENSE.mustache: -------------------------------------------------------------------------------- 1 | This module was generated by node-red-nodegen: https://github.com/node-red/node-red-nodegen 2 | using code templates made available under the Apache-2.0 licence. 3 | 4 | --------------------------------------------------------------------------- 5 | 6 | Apache License 7 | Version 2.0, January 2004 8 | http://www.apache.org/licenses/ 9 | 10 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 11 | 12 | 1. Definitions. 13 | 14 | "License" shall mean the terms and conditions for use, reproduction, 15 | and distribution as defined by Sections 1 through 9 of this document. 16 | 17 | "Licensor" shall mean the copyright owner or entity authorized by 18 | the copyright owner that is granting the License. 19 | 20 | "Legal Entity" shall mean the union of the acting entity and all 21 | other entities that control, are controlled by, or are under common 22 | control with that entity. For the purposes of this definition, 23 | "control" means (i) the power, direct or indirect, to cause the 24 | direction or management of such entity, whether by contract or 25 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 26 | outstanding shares, or (iii) beneficial ownership of such entity. 27 | 28 | "You" (or "Your") shall mean an individual or Legal Entity 29 | exercising permissions granted by this License. 30 | 31 | "Source" form shall mean the preferred form for making modifications, 32 | including but not limited to software source code, documentation 33 | source, and configuration files. 34 | 35 | "Object" form shall mean any form resulting from mechanical 36 | transformation or translation of a Source form, including but 37 | not limited to compiled object code, generated documentation, 38 | and conversions to other media types. 39 | 40 | "Work" shall mean the work of authorship, whether in Source or 41 | Object form, made available under the License, as indicated by a 42 | copyright notice that is included in or attached to the work 43 | (an example is provided in the Appendix below). 44 | 45 | "Derivative Works" shall mean any work, whether in Source or Object 46 | form, that is based on (or derived from) the Work and for which the 47 | editorial revisions, annotations, elaborations, or other modifications 48 | represent, as a whole, an original work of authorship. For the purposes 49 | of this License, Derivative Works shall not include works that remain 50 | separable from, or merely link (or bind by name) to the interfaces of, 51 | the Work and Derivative Works thereof. 52 | 53 | "Contribution" shall mean any work of authorship, including 54 | the original version of the Work and any modifications or additions 55 | to that Work or Derivative Works thereof, that is intentionally 56 | submitted to Licensor for inclusion in the Work by the copyright owner 57 | or by an individual or Legal Entity authorized to submit on behalf of 58 | the copyright owner. For the purposes of this definition, "submitted" 59 | means any form of electronic, verbal, or written communication sent 60 | to the Licensor or its representatives, including but not limited to 61 | communication on electronic mailing lists, source code control systems, 62 | and issue tracking systems that are managed by, or on behalf of, the 63 | Licensor for the purpose of discussing and improving the Work, but 64 | excluding communication that is conspicuously marked or otherwise 65 | designated in writing by the copyright owner as "Not a Contribution." 66 | 67 | "Contributor" shall mean Licensor and any individual or Legal Entity 68 | on behalf of whom a Contribution has been received by Licensor and 69 | subsequently incorporated within the Work. 70 | 71 | 2. Grant of Copyright License. Subject to the terms and conditions of 72 | this License, each Contributor hereby grants to You a perpetual, 73 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 74 | copyright license to reproduce, prepare Derivative Works of, 75 | publicly display, publicly perform, sublicense, and distribute the 76 | Work and such Derivative Works in Source or Object form. 77 | 78 | 3. Grant of Patent License. Subject to the terms and conditions of 79 | this License, each Contributor hereby grants to You a perpetual, 80 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 81 | (except as stated in this section) patent license to make, have made, 82 | use, offer to sell, sell, import, and otherwise transfer the Work, 83 | where such license applies only to those patent claims licensable 84 | by such Contributor that are necessarily infringed by their 85 | Contribution(s) alone or by combination of their Contribution(s) 86 | with the Work to which such Contribution(s) was submitted. If You 87 | institute patent litigation against any entity (including a 88 | cross-claim or counterclaim in a lawsuit) alleging that the Work 89 | or a Contribution incorporated within the Work constitutes direct 90 | or contributory patent infringement, then any patent licenses 91 | granted to You under this License for that Work shall terminate 92 | as of the date such litigation is filed. 93 | 94 | 4. Redistribution. You may reproduce and distribute copies of the 95 | Work or Derivative Works thereof in any medium, with or without 96 | modifications, and in Source or Object form, provided that You 97 | meet the following conditions: 98 | 99 | (a) You must give any other recipients of the Work or 100 | Derivative Works a copy of this License; and 101 | 102 | (b) You must cause any modified files to carry prominent notices 103 | stating that You changed the files; and 104 | 105 | (c) You must retain, in the Source form of any Derivative Works 106 | that You distribute, all copyright, patent, trademark, and 107 | attribution notices from the Source form of the Work, 108 | excluding those notices that do not pertain to any part of 109 | the Derivative Works; and 110 | 111 | (d) If the Work includes a "NOTICE" text file as part of its 112 | distribution, then any Derivative Works that You distribute must 113 | include a readable copy of the attribution notices contained 114 | within such NOTICE file, excluding those notices that do not 115 | pertain to any part of the Derivative Works, in at least one 116 | of the following places: within a NOTICE text file distributed 117 | as part of the Derivative Works; within the Source form or 118 | documentation, if provided along with the Derivative Works; or, 119 | within a display generated by the Derivative Works, if and 120 | wherever such third-party notices normally appear. The contents 121 | of the NOTICE file are for informational purposes only and 122 | do not modify the License. You may add Your own attribution 123 | notices within Derivative Works that You distribute, alongside 124 | or as an addendum to the NOTICE text from the Work, provided 125 | that such additional attribution notices cannot be construed 126 | as modifying the License. 127 | 128 | You may add Your own copyright statement to Your modifications and 129 | may provide additional or different license terms and conditions 130 | for use, reproduction, or distribution of Your modifications, or 131 | for any such Derivative Works as a whole, provided Your use, 132 | reproduction, and distribution of the Work otherwise complies with 133 | the conditions stated in this License. 134 | 135 | 5. Submission of Contributions. Unless You explicitly state otherwise, 136 | any Contribution intentionally submitted for inclusion in the Work 137 | by You to the Licensor shall be under the terms and conditions of 138 | this License, without any additional terms or conditions. 139 | Notwithstanding the above, nothing herein shall supersede or modify 140 | the terms of any separate license agreement you may have executed 141 | with Licensor regarding such Contributions. 142 | 143 | 6. Trademarks. This License does not grant permission to use the trade 144 | names, trademarks, service marks, or product names of the Licensor, 145 | except as required for reasonable and customary use in describing the 146 | origin of the Work and reproducing the content of the NOTICE file. 147 | 148 | 7. Disclaimer of Warranty. Unless required by applicable law or 149 | agreed to in writing, Licensor provides the Work (and each 150 | Contributor provides its Contributions) on an "AS IS" BASIS, 151 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 152 | implied, including, without limitation, any warranties or conditions 153 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 154 | PARTICULAR PURPOSE. You are solely responsible for determining the 155 | appropriateness of using or redistributing the Work and assume any 156 | risks associated with Your exercise of permissions under this License. 157 | 158 | 8. Limitation of Liability. In no event and under no legal theory, 159 | whether in tort (including negligence), contract, or otherwise, 160 | unless required by applicable law (such as deliberate and grossly 161 | negligent acts) or agreed to in writing, shall any Contributor be 162 | liable to You for damages, including any direct, indirect, special, 163 | incidental, or consequential damages of any character arising as a 164 | result of this License or out of the use or inability to use the 165 | Work (including but not limited to damages for loss of goodwill, 166 | work stoppage, computer failure or malfunction, or any and all 167 | other commercial damages or losses), even if such Contributor 168 | has been advised of the possibility of such damages. 169 | 170 | 9. Accepting Warranty or Additional Liability. While redistributing 171 | the Work or Derivative Works thereof, You may choose to offer, 172 | and charge a fee for, acceptance of support, warranty, indemnity, 173 | or other liability obligations and/or rights consistent with this 174 | License. However, in accepting such obligations, You may act only 175 | on Your own behalf and on Your sole responsibility, not on behalf 176 | of any other Contributor, and only if You agree to indemnify, 177 | defend, and hold each Contributor harmless for any liability 178 | incurred by, or claims asserted against, such Contributor by reason 179 | of your accepting any such warranty or additional liability. 180 | 181 | END OF TERMS AND CONDITIONS 182 | 183 | APPENDIX: How to apply the Apache License to your work. 184 | 185 | To apply the Apache License to your work, attach the following 186 | boilerplate notice, with the fields enclosed by brackets "[]" 187 | replaced with your own identifying information. (Don't include 188 | the brackets!) The text should be enclosed in the appropriate 189 | comment syntax for the file format. We also recommend that a 190 | file or class name and description of purpose be included on the 191 | same "printed page" as the copyright notice for easier 192 | identification within third-party archives. 193 | 194 | Copyright [yyyy] [name of copyright owner] 195 | 196 | Licensed under the Apache License, Version 2.0 (the "License"); 197 | you may not use this file except in compliance with the License. 198 | You may obtain a copy of the License at 199 | 200 | http://www.apache.org/licenses/LICENSE-2.0 201 | 202 | Unless required by applicable law or agreed to in writing, software 203 | distributed under the License is distributed on an "AS IS" BASIS, 204 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 205 | See the License for the specific language governing permissions and 206 | limitations under the License. 207 | -------------------------------------------------------------------------------- /samples/swagger.json: -------------------------------------------------------------------------------- 1 | {"swagger":"2.0","info":{"description":"This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.","version":"1.0.5","title":"Swagger Petstore","termsOfService":"http://swagger.io/terms/","contact":{"email":"apiteam@swagger.io"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"}},"host":"petstore.swagger.io","basePath":"/v2","tags":[{"name":"pet","description":"Everything about your Pets","externalDocs":{"description":"Find out more","url":"http://swagger.io"}},{"name":"store","description":"Access to Petstore orders"},{"name":"user","description":"Operations about user","externalDocs":{"description":"Find out more about our store","url":"http://swagger.io"}}],"schemes":["https","http"],"paths":{"/pet/{petId}/uploadImage":{"post":{"tags":["pet"],"summary":"uploads an image","description":"","operationId":"uploadFile","consumes":["multipart/form-data"],"produces":["application/json"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to update","required":true,"type":"integer","format":"int64"},{"name":"additionalMetadata","in":"formData","description":"Additional data to pass to server","required":false,"type":"string"},{"name":"file","in":"formData","description":"file to upload","required":false,"type":"file"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/ApiResponse"}}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet":{"post":{"tags":["pet"],"summary":"Add a new pet to the store","description":"","operationId":"addPet","consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"put":{"tags":["pet"],"summary":"Update an existing pet","description":"","operationId":"updatePet","consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"Pet object that needs to be added to the store","required":true,"schema":{"$ref":"#/definitions/Pet"}}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"},"405":{"description":"Validation exception"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByStatus":{"get":{"tags":["pet"],"summary":"Finds Pets by status","description":"Multiple status values can be provided with comma separated strings","operationId":"findPetsByStatus","produces":["application/json","application/xml"],"parameters":[{"name":"status","in":"query","description":"Status values that need to be considered for filter","required":true,"type":"array","items":{"type":"string","enum":["available","pending","sold"],"default":"available"},"collectionFormat":"multi"}],"responses":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400":{"description":"Invalid status value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/pet/findByTags":{"get":{"tags":["pet"],"summary":"Finds Pets by tags","description":"Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.","operationId":"findPetsByTags","produces":["application/json","application/xml"],"parameters":[{"name":"tags","in":"query","description":"Tags to filter by","required":true,"type":"array","items":{"type":"string"},"collectionFormat":"multi"}],"responses":{"200":{"description":"successful operation","schema":{"type":"array","items":{"$ref":"#/definitions/Pet"}}},"400":{"description":"Invalid tag value"}},"security":[{"petstore_auth":["write:pets","read:pets"]}],"deprecated":true}},"/pet/{petId}":{"get":{"tags":["pet"],"summary":"Find pet by ID","description":"Returns a single pet","operationId":"getPetById","produces":["application/json","application/xml"],"parameters":[{"name":"petId","in":"path","description":"ID of pet to return","required":true,"type":"integer","format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Pet"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"security":[{"api_key":[]}]},"post":{"tags":["pet"],"summary":"Updates a pet in the store with form data","description":"","operationId":"updatePetWithForm","consumes":["application/x-www-form-urlencoded"],"produces":["application/json","application/xml"],"parameters":[{"name":"petId","in":"path","description":"ID of pet that needs to be updated","required":true,"type":"integer","format":"int64"},{"name":"name","in":"formData","description":"Updated name of the pet","required":false,"type":"string"},{"name":"status","in":"formData","description":"Updated status of the pet","required":false,"type":"string"}],"responses":{"405":{"description":"Invalid input"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]},"delete":{"tags":["pet"],"summary":"Deletes a pet","description":"","operationId":"deletePet","produces":["application/json","application/xml"],"parameters":[{"name":"api_key","in":"header","required":false,"type":"string"},{"name":"petId","in":"path","description":"Pet id to delete","required":true,"type":"integer","format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Pet not found"}},"security":[{"petstore_auth":["write:pets","read:pets"]}]}},"/store/order":{"post":{"tags":["store"],"summary":"Place an order for a pet","description":"","operationId":"placeOrder","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"order placed for purchasing the pet","required":true,"schema":{"$ref":"#/definitions/Order"}}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid Order"}}}},"/store/order/{orderId}":{"get":{"tags":["store"],"summary":"Find purchase order by ID","description":"For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions","operationId":"getOrderById","produces":["application/json","application/xml"],"parameters":[{"name":"orderId","in":"path","description":"ID of pet that needs to be fetched","required":true,"type":"integer","maximum":10,"minimum":1,"format":"int64"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/Order"}},"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}},"delete":{"tags":["store"],"summary":"Delete purchase order by ID","description":"For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors","operationId":"deleteOrder","produces":["application/json","application/xml"],"parameters":[{"name":"orderId","in":"path","description":"ID of the order that needs to be deleted","required":true,"type":"integer","minimum":1,"format":"int64"}],"responses":{"400":{"description":"Invalid ID supplied"},"404":{"description":"Order not found"}}}},"/store/inventory":{"get":{"tags":["store"],"summary":"Returns pet inventories by status","description":"Returns a map of status codes to quantities","operationId":"getInventory","produces":["application/json"],"parameters":[],"responses":{"200":{"description":"successful operation","schema":{"type":"object","additionalProperties":{"type":"integer","format":"int32"}}}},"security":[{"api_key":[]}]}},"/user/createWithArray":{"post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"","operationId":"createUsersWithArrayInput","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/User"}}}],"responses":{"default":{"description":"successful operation"}}}},"/user/createWithList":{"post":{"tags":["user"],"summary":"Creates list of users with given input array","description":"","operationId":"createUsersWithListInput","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"List of user object","required":true,"schema":{"type":"array","items":{"$ref":"#/definitions/User"}}}],"responses":{"default":{"description":"successful operation"}}}},"/user/{username}":{"get":{"tags":["user"],"summary":"Get user by user name","description":"","operationId":"getUserByName","produces":["application/json","application/xml"],"parameters":[{"name":"username","in":"path","description":"The name that needs to be fetched. Use user1 for testing. ","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","schema":{"$ref":"#/definitions/User"}},"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}},"put":{"tags":["user"],"summary":"Updated user","description":"This can only be done by the logged in user.","operationId":"updateUser","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"name":"username","in":"path","description":"name that need to be updated","required":true,"type":"string"},{"in":"body","name":"body","description":"Updated user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"400":{"description":"Invalid user supplied"},"404":{"description":"User not found"}}},"delete":{"tags":["user"],"summary":"Delete user","description":"This can only be done by the logged in user.","operationId":"deleteUser","produces":["application/json","application/xml"],"parameters":[{"name":"username","in":"path","description":"The name that needs to be deleted","required":true,"type":"string"}],"responses":{"400":{"description":"Invalid username supplied"},"404":{"description":"User not found"}}}},"/user/login":{"get":{"tags":["user"],"summary":"Logs user into the system","description":"","operationId":"loginUser","produces":["application/json","application/xml"],"parameters":[{"name":"username","in":"query","description":"The user name for login","required":true,"type":"string"},{"name":"password","in":"query","description":"The password for login in clear text","required":true,"type":"string"}],"responses":{"200":{"description":"successful operation","headers":{"X-Expires-After":{"type":"string","format":"date-time","description":"date in UTC when token expires"},"X-Rate-Limit":{"type":"integer","format":"int32","description":"calls per hour allowed by the user"}},"schema":{"type":"string"}},"400":{"description":"Invalid username/password supplied"}}}},"/user/logout":{"get":{"tags":["user"],"summary":"Logs out current logged in user session","description":"","operationId":"logoutUser","produces":["application/json","application/xml"],"parameters":[],"responses":{"default":{"description":"successful operation"}}}},"/user":{"post":{"tags":["user"],"summary":"Create user","description":"This can only be done by the logged in user.","operationId":"createUser","consumes":["application/json"],"produces":["application/json","application/xml"],"parameters":[{"in":"body","name":"body","description":"Created user object","required":true,"schema":{"$ref":"#/definitions/User"}}],"responses":{"default":{"description":"successful operation"}}}}},"securityDefinitions":{"api_key":{"type":"apiKey","name":"api_key","in":"header"},"petstore_auth":{"type":"oauth2","authorizationUrl":"https://petstore.swagger.io/oauth/authorize","flow":"implicit","scopes":{"read:pets":"read your pets","write:pets":"modify pets in your account"}}},"definitions":{"ApiResponse":{"type":"object","properties":{"code":{"type":"integer","format":"int32"},"type":{"type":"string"},"message":{"type":"string"}}},"Category":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Category"}},"Pet":{"type":"object","required":["name","photoUrls"],"properties":{"id":{"type":"integer","format":"int64"},"category":{"$ref":"#/definitions/Category"},"name":{"type":"string","example":"doggie"},"photoUrls":{"type":"array","xml":{"wrapped":true},"items":{"type":"string","xml":{"name":"photoUrl"}}},"tags":{"type":"array","xml":{"wrapped":true},"items":{"xml":{"name":"tag"},"$ref":"#/definitions/Tag"}},"status":{"type":"string","description":"pet status in the store","enum":["available","pending","sold"]}},"xml":{"name":"Pet"}},"Tag":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"}},"xml":{"name":"Tag"}},"Order":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"petId":{"type":"integer","format":"int64"},"quantity":{"type":"integer","format":"int32"},"shipDate":{"type":"string","format":"date-time"},"status":{"type":"string","description":"Order Status","enum":["placed","approved","delivered"]},"complete":{"type":"boolean"}},"xml":{"name":"Order"}},"User":{"type":"object","properties":{"id":{"type":"integer","format":"int64"},"username":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"},"phone":{"type":"string"},"userStatus":{"type":"integer","format":"int32","description":"User Status"}},"xml":{"name":"User"}}},"externalDocs":{"description":"Find out more about Swagger","url":"http://swagger.io"}} -------------------------------------------------------------------------------- /templates/function/node.js.mustache: -------------------------------------------------------------------------------- 1 | module.exports = function(RED) { 2 | "use strict"; 3 | var util = require("util"); 4 | var vm = require("vm"); 5 | 6 | function sendResults(node,send,_msgid,msgs,cloneFirstMessage) { 7 | if (msgs == null) { 8 | return; 9 | } else if (!util.isArray(msgs)) { 10 | msgs = [msgs]; 11 | } 12 | var msgCount = 0; 13 | for (var m=0; m0) { 40 | send(msgs); 41 | } 42 | } 43 | 44 | function FunctionNode(n) { 45 | RED.nodes.createNode(this,n); 46 | var node = this; 47 | this.name = n.name; 48 | this.func = n.func; 49 | 50 | var handleNodeDoneCall = true; 51 | // Check to see if the Function appears to call `node.done()`. If so, 52 | // we will assume it is well written and does actually call node.done(). 53 | // Otherwise, we will call node.done() after the function returns regardless. 54 | if (/node\.done\s*\(\s*\)/.test(this.func)) { 55 | handleNodeDoneCall = false; 56 | } 57 | 58 | var functionText = "var results = null;"+ 59 | "results = (function(msg,__send__,__done__){ "+ 60 | "var __msgid__ = msg._msgid;"+ 61 | "var node = {"+ 62 | "id:__node__.id,"+ 63 | "name:__node__.name,"+ 64 | "log:__node__.log,"+ 65 | "error:__node__.error,"+ 66 | "warn:__node__.warn,"+ 67 | "debug:__node__.debug,"+ 68 | "trace:__node__.trace,"+ 69 | "on:__node__.on,"+ 70 | "status:__node__.status,"+ 71 | "send:function(msgs,cloneMsg){ __node__.send(__send__,__msgid__,msgs,cloneMsg);},"+ 72 | "done:__done__"+ 73 | "};\n"+ 74 | "{{&func}}"+"\n"+ 75 | "})(msg,send,done);"; 76 | this.topic = n.topic; 77 | this.outstandingTimers = []; 78 | this.outstandingIntervals = []; 79 | var sandbox = { 80 | console:console, 81 | util:util, 82 | Buffer:Buffer, 83 | Date: Date, 84 | RED: { 85 | util: RED.util 86 | }, 87 | __node__: { 88 | id: node.id, 89 | name: node.name, 90 | log: function() { 91 | node.log.apply(node, arguments); 92 | }, 93 | error: function() { 94 | node.error.apply(node, arguments); 95 | }, 96 | warn: function() { 97 | node.warn.apply(node, arguments); 98 | }, 99 | debug: function() { 100 | node.debug.apply(node, arguments); 101 | }, 102 | trace: function() { 103 | node.trace.apply(node, arguments); 104 | }, 105 | send: function(send, id, msgs, cloneMsg) { 106 | sendResults(node, send, id, msgs, cloneMsg); 107 | }, 108 | on: function() { 109 | if (arguments[0] === "input") { 110 | throw new Error(RED._("function.error.inputListener")); 111 | } 112 | node.on.apply(node, arguments); 113 | }, 114 | status: function() { 115 | node.status.apply(node, arguments); 116 | } 117 | }, 118 | context: { 119 | set: function() { 120 | node.context().set.apply(node,arguments); 121 | }, 122 | get: function() { 123 | return node.context().get.apply(node,arguments); 124 | }, 125 | keys: function() { 126 | return node.context().keys.apply(node,arguments); 127 | }, 128 | get global() { 129 | return node.context().global; 130 | }, 131 | get flow() { 132 | return node.context().flow; 133 | } 134 | }, 135 | flow: { 136 | set: function() { 137 | node.context().flow.set.apply(node,arguments); 138 | }, 139 | get: function() { 140 | return node.context().flow.get.apply(node,arguments); 141 | }, 142 | keys: function() { 143 | return node.context().flow.keys.apply(node,arguments); 144 | } 145 | }, 146 | global: { 147 | set: function() { 148 | node.context().global.set.apply(node,arguments); 149 | }, 150 | get: function() { 151 | return node.context().global.get.apply(node,arguments); 152 | }, 153 | keys: function() { 154 | return node.context().global.keys.apply(node,arguments); 155 | } 156 | }, 157 | env: { 158 | get: function(envVar) { 159 | var flow = node._flow; 160 | return flow.getSetting(envVar); 161 | } 162 | }, 163 | setTimeout: function () { 164 | var func = arguments[0]; 165 | var timerId; 166 | arguments[0] = function() { 167 | sandbox.clearTimeout(timerId); 168 | try { 169 | func.apply(this,arguments); 170 | } catch(err) { 171 | node.error(err,{}); 172 | } 173 | }; 174 | timerId = setTimeout.apply(this,arguments); 175 | node.outstandingTimers.push(timerId); 176 | return timerId; 177 | }, 178 | clearTimeout: function(id) { 179 | clearTimeout(id); 180 | var index = node.outstandingTimers.indexOf(id); 181 | if (index > -1) { 182 | node.outstandingTimers.splice(index,1); 183 | } 184 | }, 185 | setInterval: function() { 186 | var func = arguments[0]; 187 | var timerId; 188 | arguments[0] = function() { 189 | try { 190 | func.apply(this,arguments); 191 | } catch(err) { 192 | node.error(err,{}); 193 | } 194 | }; 195 | timerId = setInterval.apply(this,arguments); 196 | node.outstandingIntervals.push(timerId); 197 | return timerId; 198 | }, 199 | clearInterval: function(id) { 200 | clearInterval(id); 201 | var index = node.outstandingIntervals.indexOf(id); 202 | if (index > -1) { 203 | node.outstandingIntervals.splice(index,1); 204 | } 205 | } 206 | }; 207 | if (util.hasOwnProperty('promisify')) { 208 | sandbox.setTimeout[util.promisify.custom] = function(after, value) { 209 | return new Promise(function(resolve, reject) { 210 | sandbox.setTimeout(function(){ resolve(value); }, after); 211 | }); 212 | }; 213 | } 214 | var context = vm.createContext(sandbox); 215 | try { 216 | this.script = vm.createScript(functionText, { 217 | filename: 'Function node:'+this.id+(this.name?' ['+this.name+']':''), // filename for stack traces 218 | displayErrors: true 219 | // Using the following options causes node 4/6 to not include the line number 220 | // in the stack output. So don't use them. 221 | // lineOffset: -11, // line number offset to be used for stack traces 222 | // columnOffset: 0, // column number offset to be used for stack traces 223 | }); 224 | this.on("input", function(msg,send,done) { 225 | try { 226 | var start = process.hrtime(); 227 | context.msg = msg; 228 | context.send = send; 229 | context.done = done; 230 | 231 | this.script.runInContext(context); 232 | sendResults(this,send,msg._msgid,context.results,false); 233 | if (handleNodeDoneCall) { 234 | done(); 235 | } 236 | 237 | var duration = process.hrtime(start); 238 | var converted = Math.floor((duration[0] * 1e9 + duration[1])/10000)/100; 239 | this.metric("duration", msg, converted); 240 | if (process.env.NODE_RED_FUNCTION_TIME) { 241 | this.status({fill:"yellow",shape:"dot",text:""+converted}); 242 | } 243 | } catch(err) { 244 | if ((typeof err === "object") && err.hasOwnProperty("stack")) { 245 | //remove unwanted part 246 | var index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/); 247 | err.stack = err.stack.slice(0, index).split('\n').slice(0,-1).join('\n'); 248 | var stack = err.stack.split(/\r?\n/); 249 | 250 | //store the error in msg to be used in flows 251 | msg.error = err; 252 | 253 | var line = 0; 254 | var errorMessage; 255 | if (stack.length > 0) { 256 | while (line < stack.length && stack[line].indexOf("ReferenceError") !== 0) { 257 | line++; 258 | } 259 | 260 | if (line < stack.length) { 261 | errorMessage = stack[line]; 262 | var m = /:(\d+):(\d+)$/.exec(stack[line+1]); 263 | if (m) { 264 | var lineno = Number(m[1])-1; 265 | var cha = m[2]; 266 | errorMessage += " (line "+lineno+", col "+cha+")"; 267 | } 268 | } 269 | } 270 | if (!errorMessage) { 271 | errorMessage = err.toString(); 272 | } 273 | done(errorMessage); 274 | } 275 | else if (typeof err === "string") { 276 | done(err); 277 | } 278 | else { 279 | done(JSON.stringify(err)); 280 | } 281 | } 282 | }); 283 | this.on("close", function() { 284 | while (node.outstandingTimers.length > 0) { 285 | clearTimeout(node.outstandingTimers.pop()); 286 | } 287 | while (node.outstandingIntervals.length > 0) { 288 | clearInterval(node.outstandingIntervals.pop()); 289 | } 290 | this.status({}); 291 | }); 292 | } catch(err) { 293 | // eg SyntaxError - which v8 doesn't include line number information 294 | // so we can't do better than this 295 | this.error(err); 296 | } 297 | } 298 | RED.nodes.registerType("{{&nodeName}}", FunctionNode); 299 | RED.library.register("functions"); 300 | }; 301 | -------------------------------------------------------------------------------- /lib/swagger/index.js: -------------------------------------------------------------------------------- 1 | const util = require("../util"); 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const mustache = require('mustache'); 6 | const obfuscator = require('javascript-obfuscator'); 7 | const CodeGen = require('swagger-js-codegen-formdata').CodeGen; 8 | const axios = require('axios'); 9 | const yamljs = require('yamljs'); 10 | const Converter = require('api-spec-converter'); 11 | 12 | const TEMPLATE_DIR = path.join(__dirname,'../../templates/swagger'); 13 | 14 | async function getSpec(src, data) { 15 | let spec; 16 | if (typeof src === "string") { 17 | if (/^https?:/.test(src)) { 18 | let requestOptions = {}; 19 | if (data.lang) { 20 | requestOptions.headers = { 21 | 'Accept-Language': data.lang 22 | } 23 | } 24 | let response = await axios.get(src, requestOptions); 25 | spec = response.data; 26 | } else if (/\.yaml$/.test(src)) { 27 | spec = yamljs.load(src); 28 | } else if (/\.json/.test(src)) { 29 | spec = JSON.parse(util.skipBom(await fs.promises.readFile(src))); 30 | } else { 31 | throw new Error("Unsupported file type: "+src) 32 | } 33 | } else { 34 | spec = src; 35 | } 36 | return Converter.convert({ 37 | from: spec.openapi && spec.openapi.startsWith('3.0') ? 'openapi_3' : 'swagger_2', 38 | to: 'swagger_2', 39 | source: spec, 40 | }).then(res => res.spec) 41 | } 42 | 43 | /* 44 | * data.src : String : url or path to json/yaml file 45 | * : Spec JSON 46 | * Object : Spec Object 47 | */ 48 | module.exports = async function(data, options) { 49 | "use strict"; 50 | 51 | let spec = await getSpec(data.src, data); 52 | // Modify swagger data 53 | var swagger = JSON.parse(JSON.stringify(spec), function (key, value) { 54 | if (key === 'consumes' || key === 'produces') { // Filter type of 'Content-Type' and 'Accept' in request header 55 | if (value.indexOf('application/json') >= 0) { 56 | return ['application/json']; 57 | } else if (value.indexOf('application/xml') >= 0) { 58 | return ['application/xml']; 59 | } else if (value.indexOf('text/csv') >= 0) { 60 | return ['text/csv']; 61 | } else if (value.indexOf('text/plain') >= 0) { 62 | return ['text/plain']; 63 | } else if (value.indexOf('multipart/form-data') >= 0) { 64 | return ['multipart/form-data']; 65 | } else if (value.indexOf('application/octet-stream') >= 0) { 66 | return ['application/octet-stream']; 67 | } else { 68 | return undefined; 69 | } 70 | } else if (key === 'default') { // Handle swagger-js-codegen bug 71 | return undefined; 72 | } else if (key === 'operationId') { // Sanitize 'operationId' (remove special chars) 73 | return value.replace(/(?!\w|\s)./g, ''); 74 | } else { 75 | return value; 76 | } 77 | }); 78 | 79 | if (swagger.swagger !== '2.0') { 80 | throw new Error('unsupported version of OpenAPI document'); 81 | } 82 | 83 | if (!swagger.info.title) { 84 | throw new Error('No title is specified in OpenAPI document'); 85 | } 86 | 87 | var className = swagger.info.title.toLowerCase().replace(/(^|[^a-z0-9]+)[a-z0-9]/g, 88 | function (str) { 89 | return str.replace(/^[^a-z0-9]+/, '').toUpperCase(); 90 | }); 91 | 92 | if (!data.name || data.name === '') { 93 | data.name = className.replace(/([A-Z])/g, '-$1').slice(1).toLowerCase(); 94 | } 95 | 96 | if (data.module) { 97 | if (data.prefix) { 98 | throw new Error('module name and prefix are conflicted.'); 99 | } 100 | } else { 101 | if (data.prefix) { 102 | data.module = data.prefix + data.name; 103 | } else { 104 | data.module = 'node-red-contrib-' + data.name; 105 | } 106 | } 107 | 108 | if (!data.version || data.version === '') { 109 | if (swagger.info.version) { 110 | var version = swagger.info.version.replace(/[^0-9\.]/g, ''); 111 | if (version.match(/^[0-9]+$/)) { 112 | data.version = version + '.0.0'; 113 | } else if (version.match(/^[0-9]+\.[0-9]+$/)) { 114 | data.version = version + '.0'; 115 | } else if (version.match(/^[0-9]+\.[0-9]+\.[0-9]+$/)) { 116 | data.version = version; 117 | } else { 118 | data.version = '0.0.1'; 119 | } 120 | } else { 121 | data.version = '0.0.1'; 122 | } 123 | } 124 | 125 | if (data.icon) { 126 | if (!data.icon.match(/\.(png|gif)$/)) { 127 | data.icon = data.icon + '.png'; 128 | } 129 | if (!data.icon.match(/^[a-zA-Z0-9\-\./]+$/)) { 130 | throw new Error('invalid icon file name'); 131 | } 132 | } 133 | 134 | if (data.color) { 135 | if (data.color.match(/^[a-zA-Z0-9]{6}$/)) { 136 | data.color = '#' + data.color; 137 | } else { 138 | throw new Error('invalid color'); 139 | } 140 | } 141 | 142 | util.createCommonFiles(TEMPLATE_DIR, data); 143 | 144 | // Create Node.js SDK 145 | var nodejsSourceCode = CodeGen.getNodeCode({ 146 | className: className, 147 | swagger: swagger, 148 | lint: false, 149 | beautify: false 150 | }); 151 | if (options.obfuscate) { 152 | nodejsSourceCode = obfuscator.obfuscate(nodejsSourceCode, { stringArrayEncoding: 'rc4' }).getObfuscatedCode(); 153 | } 154 | fs.writeFileSync(path.join(data.dst, data.module, 'lib.js'), nodejsSourceCode); 155 | 156 | // Create package.json 157 | var packageSourceCode = CodeGen.getCustomCode({ 158 | className: className, 159 | swagger: swagger, 160 | template: { 161 | class: fs.readFileSync(path.join(TEMPLATE_DIR, 'package.json.mustache'), 'utf-8'), 162 | method: '', 163 | type: '' 164 | }, 165 | mustache: { 166 | nodeName: data.name, 167 | projectName: data.module, 168 | projectVersion: data.version, 169 | keywords: util.extractKeywords(data.keywords), 170 | licenseName: function () { 171 | if (swagger.info.license && swagger.info.license.name) { 172 | return swagger.info.license.name; 173 | } else { 174 | return 'Apache-2.0'; 175 | } 176 | }, 177 | projectAuthor: swagger.info.contact && swagger.info.contact.name ? swagger.info.contact.name : '' 178 | }, 179 | lint: false, 180 | beautify: false 181 | }); 182 | fs.writeFileSync(path.join(data.dst, data.module, 'package.json'), packageSourceCode); 183 | 184 | // Mustache helpers 185 | var isNotBodyParam = function () { 186 | return function (content, render) { 187 | return render('{{camelCaseName}}') !== 'body' ? render(content) : ''; 188 | }; 189 | }; 190 | var isBodyParam = function () { 191 | return function (content, render) { 192 | return render('{{camelCaseName}}') === 'body' ? render(content) : ''; 193 | }; 194 | }; 195 | var hasOptionalParams = function () { 196 | return function (content, render) { 197 | var params = render('{{#parameters}}{{^required}}{{camelCaseName}},{{/required}}{{/parameters}}'); 198 | return params.split(',').filter(p => p).some(p => p !== 'body') ? render(content) : ''; 199 | }; 200 | }; 201 | var hasServiceParams = swagger.host === undefined || swagger.securityDefinitions !== undefined; 202 | 203 | // Create node.js 204 | var nodeSourceCode = CodeGen.getCustomCode({ 205 | className: className, 206 | swagger: swagger, 207 | template: { 208 | class: fs.readFileSync(path.join(TEMPLATE_DIR, 'node.js.mustache'), 'utf-8'), 209 | method: '', 210 | type: '' 211 | }, 212 | mustache: { 213 | nodeName: data.name, 214 | isBodyParam: isBodyParam, 215 | isNotBodyParam: isNotBodyParam, 216 | hasServiceParams: hasServiceParams 217 | }, 218 | lint: false, 219 | beautify: false 220 | }); 221 | if (options.obfuscate) { 222 | nodeSourceCode = obfuscator.obfuscate(nodeSourceCode, { stringArrayEncoding: 'rc4' }).getObfuscatedCode(); 223 | } 224 | fs.writeFileSync(path.join(data.dst, data.module, 'node.js'), nodeSourceCode); 225 | 226 | // Create node.html 227 | var htmlSourceCode = CodeGen.getCustomCode({ 228 | className: className, 229 | swagger: swagger, 230 | template: { 231 | class: fs.readFileSync(path.join(TEMPLATE_DIR, 'node.html.mustache'), 'utf-8'), 232 | method: '', 233 | type: '' 234 | }, 235 | mustache: { 236 | nodeName: data.name, 237 | category: data.category || 'function', 238 | icon: function () { 239 | if (data.icon) { 240 | return path.basename(data.icon); 241 | } else { 242 | return 'icon.png'; 243 | } 244 | }, 245 | color: data.color || '#89bf04', 246 | isNotBodyParam: isNotBodyParam, 247 | hasOptionalParams: hasOptionalParams, 248 | hasServiceParams: hasServiceParams 249 | }, 250 | lint: false, 251 | beautify: false 252 | }); 253 | fs.writeFileSync(path.join(data.dst, data.module, 'node.html'), htmlSourceCode); 254 | 255 | // Create language files 256 | var languages = fs.readdirSync(path.join(TEMPLATE_DIR, 'locales')); 257 | languages.forEach(function (language) { 258 | var languageFileSourceCode = CodeGen.getCustomCode({ 259 | className: className, 260 | swagger: swagger, 261 | template: { 262 | class: fs.readFileSync(path.join(TEMPLATE_DIR, 'locales', language, 'node.json.mustache'), 'utf-8'), 263 | method: '', 264 | type: '' 265 | }, 266 | mustache: { 267 | nodeName: data.name, 268 | }, 269 | lint: false, 270 | beautify: false 271 | }); 272 | fs.writeFileSync(path.join(data.dst, data.module, 'locales', language, 'node.json'), 273 | JSON.stringify(JSON.parse(languageFileSourceCode), null, 4)); 274 | }); 275 | 276 | // Create node_spec.js 277 | var nodeSpecSourceCode = CodeGen.getCustomCode({ 278 | className: className, 279 | swagger: swagger, 280 | template: { 281 | class: fs.readFileSync(path.join(TEMPLATE_DIR, 'test/node_spec.js.mustache'), 'utf-8'), 282 | method: '', 283 | type: '' 284 | }, 285 | mustache: { 286 | nodeName: data.name, 287 | projectName: data.module, 288 | hasServiceParams: hasServiceParams 289 | }, 290 | lint: false, 291 | beautify: false 292 | }); 293 | fs.writeFileSync(path.join(data.dst, data.module, 'test/node_spec.js'), nodeSpecSourceCode); 294 | 295 | // Create README.md 296 | var readmeSourceCode = CodeGen.getCustomCode({ 297 | className: className, 298 | swagger: swagger, 299 | template: { 300 | class: fs.readFileSync(path.join(TEMPLATE_DIR, 'README.md.mustache'), 'utf-8'), 301 | method: '', 302 | type: '' 303 | }, 304 | mustache: { 305 | nodeName: data.name, 306 | projectName: data.module, 307 | licenseName: function () { 308 | if (swagger.info.license && swagger.info.license.name) { 309 | return swagger.info.license.name; 310 | } else { 311 | return 'Apache-2.0'; 312 | } 313 | }, 314 | licenseUrl: function () { 315 | if (swagger.info.license && swagger.info.license.url) { 316 | return swagger.info.license.url; 317 | } else { 318 | return ''; 319 | } 320 | } 321 | }, 322 | lint: false, 323 | beautify: false 324 | }); 325 | fs.writeFileSync(path.join(data.dst, data.module, 'README.md'), readmeSourceCode); 326 | 327 | // Create LICENSE file 328 | var licenseSourceCode = CodeGen.getCustomCode({ 329 | projectName: data.module, 330 | className: className, 331 | swagger: swagger, 332 | template: { 333 | class: fs.readFileSync(path.join(TEMPLATE_DIR, 'LICENSE.mustache'), 'utf-8'), 334 | method: '', 335 | type: '' 336 | }, 337 | mustache: { 338 | licenseName: function () { 339 | if (swagger.info.license && swagger.info.license.name) { 340 | return swagger.info.license.name; 341 | } else { 342 | return 'Apache-2.0'; 343 | } 344 | }, 345 | licenseUrl: function () { 346 | if (swagger.info.license && swagger.info.license.url) { 347 | return swagger.info.license.url; 348 | } else { 349 | return ''; 350 | } 351 | } 352 | }, 353 | lint: false, 354 | beautify: false 355 | }); 356 | fs.writeFileSync(path.join(data.dst, data.module, 'LICENSE'), licenseSourceCode); 357 | 358 | if (options.tgz) { 359 | util.runNpmPack(data); 360 | return path.join(data.dst, data.module + '-' + data.version + '.tgz'); 361 | } else { 362 | return path.join(data.dst, data.module); 363 | } 364 | } 365 | 366 | -------------------------------------------------------------------------------- /templates/swagger/node.html.mustache: -------------------------------------------------------------------------------- 1 | 117 | 118 | 172 | 173 | 187 | {{#hasServiceParams}} 188 | 257 | 258 | 301 | 302 | 322 | {{/hasServiceParams}} 323 | -------------------------------------------------------------------------------- /lib/webofthings/wotutils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright OpenJS Foundation and other contributors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | const Ajv = require('ajv'); 18 | const addFormats = require('ajv-formats'); 19 | const addFormatsDraft2019 = require('ajv-formats-draft2019'); 20 | const url = require('url'); 21 | const fs = require('fs'); 22 | const path = require('path'); 23 | 24 | function validateTd(td) { 25 | const TDSchema = 26 | JSON.parse(fs.readFileSync(path.join(__dirname, './td-json-schema-validation.json'), 'utf-8')); 27 | const ajv = new Ajv({allErrors: true, strict: false, strictSchema: false}); 28 | addFormats(ajv); 29 | addFormatsDraft2019(ajv); 30 | const valid = ajv.validate(TDSchema, td); 31 | 32 | return { result: valid, errorText: valid?"":ajv.errorsText()}; 33 | } 34 | 35 | function setdefault(obj, propname, dflt) { 36 | return obj.hasOwnProperty(propname) ? obj[propname] : dflt; 37 | } 38 | 39 | function isOpInForms(op, forms) { 40 | return forms && forms 41 | .map(e=>(e.op && typeof e.op === 'string') ? [e.op] : e.op) 42 | .reduce((a,v)=>a.concat(v),[]) 43 | .some(e=>e === op); 44 | } 45 | 46 | function textDirection(langid) { 47 | // based on CLDR and other sources. 48 | const rtlLangs = [ 49 | "ar-AE","ar-BH","ar-DJ","ar-DZ","ar-EG","ar-EH","ar-ER","ar-IL","ar-IQ", 50 | "ar-JO","ar-KM","ar-KW","ar-LB","ar-LY","ar-MA","ar-MR","ar-OM","ar-PS", 51 | "ar-QA","ar-SA","ar-SD","ar-SO","ar-SS","ar-SY","ar-TD","ar-TN","ar-YE","ar", 52 | "az-Arab", 53 | "ckb-IR","ckb", 54 | "fa-AF","fa", 55 | "he", 56 | "ks", 57 | "lrc-IQ","lrc", 58 | "mzn", 59 | "pa-Arab", 60 | "ps-PK","ps", 61 | "sd", 62 | "ug", 63 | "ur-IN","ur", 64 | "uz-Arab", 65 | "yi", 66 | ]; 67 | if (langid === undefined || langid === "") { 68 | return 'auto'; 69 | } else { 70 | return (rtlLangs.includes(langid) ? 'rtl' : 'ltr'); 71 | } 72 | } 73 | 74 | function normalizeTd(td) { 75 | 76 | const baseUrl = td.base || ""; 77 | 78 | function formconv(intr, f, affordance) { 79 | if (f.hasOwnProperty("href")) { 80 | if (td.base) { 81 | f.href = new URL(f.href, td.base).toString() 82 | } else { 83 | f.href = new URL(f.href).toString() 84 | } 85 | f.href = f.href.replace(/%7B/gi,'{').replace(/%7D/gi,'}'); 86 | } 87 | if (f.hasOwnProperty("security") && typeof f.security === 'string') { 88 | f.security = [f.security]; 89 | } 90 | if (!f.hasOwnProperty("security")) { 91 | if (intr.hasOwnProperty("security")) { 92 | f.security = intr.security; 93 | } else if (td.hasOwnProperty("security")) { 94 | f.security = td.security; 95 | } 96 | } 97 | f.contentType = setdefault(f, "contentType", "application/json"); 98 | switch (affordance) { 99 | case "PropertyAffordance": 100 | f.op = setdefault(f, "op", ["readproperty", "writeproperty"]); 101 | break; 102 | case "ActionAffordance": 103 | f.op = setdefault(f, "op", "invokeaction"); 104 | break; 105 | case "EventAffordance": 106 | f.op = setdefault(f, "op", "subscribeevent"); 107 | break; 108 | } 109 | 110 | return f; 111 | } 112 | 113 | // normalize 'security' as Array of String. 114 | if (td.hasOwnProperty("security") && typeof td.security === "string") { 115 | td.security = [td.security]; 116 | } 117 | 118 | // Set default values in security definition 119 | for (const sd in td.securityDefinitions) { 120 | const sdef = td.securityDefinitions[sd]; 121 | switch (sdef.scheme) { 122 | case "basic": 123 | sdef.in = setdefault(sdef, "in", "header"); 124 | break; 125 | case "digest": 126 | sdef.in = setdefault(sdef, "in", "header"); 127 | sdef.qop = setdefault(sdef, "qop", "auth"); 128 | break; 129 | case "bearer": 130 | sdef.in = setdefault(sdef, "in", "header"); 131 | sdef.alg = setdefault(sdef, "alg", "ES256"); 132 | sdef.format = setdefault(sdef, "format", "jwt"); 133 | break; 134 | case "pop": 135 | sdef.in = setdefault(sdef, "in", "header"); 136 | sdef.alg = setdefault(sdef, "alg", "ES256"); 137 | sdef.format = setdefault(sdef, "format", "jwt"); 138 | break; 139 | case "oauth2": 140 | sdef.flow = setdefault(sdef, "flow", "implicit"); 141 | break; 142 | case "apikey": 143 | sdef.in = setdefault(sdef, "in", "query"); 144 | break; 145 | default: 146 | break; 147 | } 148 | } 149 | // Set default values in properties 150 | for (const p in td.properties) { 151 | const pdef = td.properties[p]; 152 | if (pdef.hasOwnProperty("security") && typeof pdef.security === 'string') { 153 | pdef.security = [pdef.security]; 154 | } 155 | if (pdef.forms) { 156 | pdef.forms = pdef.forms.map((f) => formconv(pdef, f, "PropertyAffordance")); 157 | // no filtering based on protocol 158 | } 159 | 160 | // if there is forms which have readproperty in op, writeOnly is false, otherwise true 161 | pdef.writeOnly = setdefault(pdef, "writeOnly", !isOpInForms("readproperty", pdef.forms)); 162 | // if there is forms which have writeproperty in op, readOnly is false, otherwise true 163 | pdef.readOnly = setdefault(pdef, "readOnly", !isOpInForms("writeproperty", pdef.forms)); 164 | // if there is forms which have observeproperty in op, observable is true, otherwise false 165 | pdef.observable = setdefault(pdef, "observable", isOpInForms("observeproperty", pdef.forms)); 166 | // in any cases, if it explicitly stated by writeOnly/readOnly/observable, use it. 167 | 168 | } 169 | 170 | // Set default values in actions 171 | for (const a in td.actions) { 172 | const adef = td.actions[a]; 173 | adef.safe = setdefault(adef, "safe", false); 174 | adef.idempotent = setdefault(adef, "idempotent", false); 175 | if (adef.hasOwnProperty("security") && typeof adef.security === 'string') { 176 | adef.security = [adef.security]; 177 | } 178 | if (adef.forms) { 179 | adef.forms = adef.forms.map((f) => formconv(adef, f, "ActionAffordance")); 180 | // no filtering based on protocol 181 | } 182 | } 183 | 184 | // Set default values in events 185 | for (const e in td.events) { 186 | const edef = td.events[e]; 187 | if (edef.hasOwnProperty("security") && typeof edef.security === 'string') { 188 | edef.security = [edef.security]; 189 | } 190 | if (edef.forms) { 191 | edef.forms = edef.forms.map((f) => formconv(edef, f, "EventAffordance")); 192 | // no filtering based on protocol 193 | } 194 | } 195 | 196 | // Set default values in toplevel forms --- TODO: make this work 197 | if (td.forms) { 198 | const pdef = td; 199 | td.forms = td.forms.map((f) => formconv(pdef, f, "TopPropertyAffordance")); 200 | } 201 | 202 | // Set default value in toplevel context 203 | td["@context"] = setdefault(td, "@context", "https://www.w3.org/2019/wot/td/v1"); 204 | 205 | // Convert top level forms ({read/write}allproperties) to "ALLPROPERTIES" property. 206 | if (td.forms) { 207 | const convforms = td.forms 208 | .map(f => { 209 | if (f.op && typeof f.op === 'string') { 210 | f.op = [f.op]; 211 | } 212 | f.op = f.op.map(o => { 213 | let res = o; 214 | switch (o) { 215 | case 'readallproperties': 216 | res = 'readproperty'; 217 | break; 218 | case 'writeallproperties': 219 | res = 'writeproperty'; 220 | } 221 | return res; 222 | }); 223 | return f; 224 | }); 225 | td.properties['__ALLPROPERTIES'] = { 226 | title: "All Properties", 227 | description: "all properties of this Thing", 228 | forms: convforms, 229 | type: "object", 230 | writeOnly: !isOpInForms("readproperty", convforms), 231 | readOnly: !isOpInForms("writeproperty", convforms), 232 | observable: false 233 | }; 234 | } 235 | 236 | return td; 237 | } 238 | 239 | function filterFormTd(td) { 240 | for (const p in td.properties) { 241 | let forms = td.properties[p].forms; 242 | if (forms) { 243 | forms = forms.filter((f) => (f.hasOwnProperty("href") && 244 | (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/) || f.href.match(/^mqtt:/)))); 245 | } 246 | td.properties[p].forms = forms; 247 | } 248 | for (const a in td.actions) { 249 | let forms = td.actions[a].forms; 250 | if (forms) { 251 | forms = forms.filter((f) => (f.hasOwnProperty("href") && 252 | (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/) || f.href.match(/^mqtt:/)))); 253 | } 254 | td.actions[a].forms = forms; 255 | } 256 | for (const e in td.events) { 257 | let forms = td.events[e].forms; 258 | if (forms) { 259 | forms = forms.filter((f) => (f.hasOwnProperty("href") && 260 | (f.href.match(/^https?:/) || f.href.match(/^wss?:/) || f.href.match(/^coaps?:/) || f.href.match(/^mqtt:/)))); 261 | } 262 | td.events[e].forms = forms; 263 | } 264 | return td; 265 | } 266 | 267 | function makeformsel(td) { 268 | const formsel = { 269 | property: {}, 270 | action: {}, 271 | event: {} 272 | }; 273 | 274 | for (const p in td.properties) { 275 | const forms = td.properties[p].forms; 276 | const readforms = []; 277 | const writeforms = []; 278 | const observeforms = []; 279 | for (let i = 0; i < forms.length; i++) { 280 | const secscheme = td.securityDefinitions[forms[i].security[0]].scheme; 281 | if (!forms[i].hasOwnProperty('op') || forms[i].op.includes("readproperty")) { 282 | readforms.push({index:i,secscheme:secscheme,title:forms[i].href}); 283 | } 284 | if (!forms[i].hasOwnProperty('op') || forms[i].op.includes("writeproperty")) { 285 | writeforms.push({index:i,secscheme:secscheme,title:forms[i].href}); 286 | } 287 | if (forms[i].hasOwnProperty('op') && forms[i].op.includes("observeproperty")) { 288 | observeforms.push({index:i,secscheme:secscheme,title:forms[i].href}); 289 | } 290 | } 291 | formsel.property[p] = {read: readforms, write: writeforms, observe: observeforms}; 292 | } 293 | for (const a in td.actions) { 294 | const forms = td.actions[a].forms; 295 | formsel.action[a] = []; 296 | for (let i = 0; i < forms.length; i++) { 297 | const secscheme = td.securityDefinitions[forms[i].security[0]].scheme; 298 | if (!forms[i].hasOwnProperty('op') || forms[i].op.includes("invokeaction")) { 299 | formsel.action[a].push({index:i,secscheme:secscheme,title:forms[i].href}); 300 | } 301 | } 302 | } 303 | for (const e in td.events) { 304 | const forms = td.events[e].forms; 305 | formsel.event[e] = []; 306 | for (let i = 0; i < forms.length; i++) { 307 | const secscheme = td.securityDefinitions[forms[i].security[0]].scheme; 308 | if (!forms[i].hasOwnProperty('op') || forms[i].op.includes("subscribeevent")) { 309 | formsel.event[e].push({index:i,secscheme:secscheme,title:forms[i].href}); 310 | } 311 | } 312 | } 313 | 314 | return formsel; 315 | } 316 | 317 | function woticon(td) { 318 | const iotschemaToIcon = { 319 | "Sensor": "font-awesome/fa-microchip", 320 | "BinarySwitch": "font-awesome/fa-toggle-on", 321 | "SwitchStatus": "font-awesome/fa-toggle-on", 322 | "Toggle": "font-awesome/fa-toggle-on", 323 | "Light": "font-awesome/fa-lightbulb-o",//"light.png", 324 | "Actuator": "font-awesome/fa-bolt", 325 | "CurrentColour": "font-awesome/fa-paint-brush", 326 | "ColourData": "font-awesome/fa-paint-brush", 327 | "LightControl": "font-awesome/fa-cogs", 328 | "Illuminance": "font-awesome/fa-sun-o", 329 | "IlluminanceSensing": "font-awesome/fa-sun-o", 330 | "MotionControl": "font-awesome/fa-arrows-alt", 331 | "Temperature": "font-awesome/fa-thermometer-half", 332 | "TemperatureSensing": "font-awesome/fa-thermometer-half", 333 | "TemperatureData": "font-awesome/fa-thermometer-half", 334 | "Thermostat": "font-awesome/fa-thermometer-half", 335 | "Pump": "font-awesome/fa-tint", 336 | "AirConditioner": "font-awesome/fa-snowflake-o", 337 | "UltrasonicSensing": "font-awesome/fa-rss", 338 | "HumiditySensing": "font-awesome/fa-umbrella", 339 | "SoundPressure": "font-awesome/fa-volume-up", 340 | "Valve": "font-awesome/fa-wrench", 341 | "ProximitySensing": "font-awesome/fa-crosshairs" 342 | }; 343 | 344 | const iotschemaPrefix = 'iot'; 345 | 346 | if (Array.isArray(td['@type'])) { 347 | const candidates = td['@type'].map((e) => { 348 | for (f in iotschemaToIcon) { 349 | if (`${iotschemaPrefix}:${f}` === e) { 350 | return iotschemaToIcon[f]; 351 | }; 352 | }; 353 | }).filter((e) => e); 354 | 355 | if (candidates.length > 0) { 356 | return candidates[0]; 357 | } 358 | } 359 | return "white-globe.png"; 360 | } 361 | 362 | module.exports = { 363 | validateTd: validateTd, 364 | normalizeTd: normalizeTd, 365 | filterFormTd: filterFormTd, 366 | makeformsel: makeformsel, 367 | woticon: woticon, 368 | textDirection: textDirection 369 | } -------------------------------------------------------------------------------- /templates/webofthings/node.html.mustache: -------------------------------------------------------------------------------- 1 | 203 | 204 | 265 | 266 | 344 | -------------------------------------------------------------------------------- /test/nodegen/node-red-contrib-swagger-petstore/node_spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright JS Foundation and other contributors, http://js.foundation 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | **/ 16 | 17 | var should = require("should"); 18 | var helper = require("node-red-node-test-helper"); 19 | var swaggerNode = require("../../../nodegen/node-red-contrib-swagger-petstore/node.js"); 20 | 21 | describe('node-red-contrib-swagger-petstore', function () { 22 | 23 | before(function (done) { 24 | helper.startServer(done); 25 | }); 26 | 27 | after(function (done) { 28 | helper.stopServer(done); 29 | }); 30 | 31 | afterEach(function () { 32 | helper.unload(); 33 | }); 34 | 35 | it('should be loaded', function (done) { 36 | var flow = [{id: "n1", type: "swagger-petstore", name: "swagger-petstore" }]; 37 | helper.load(swaggerNode, flow, function () { 38 | var n1 = helper.getNode('n1'); 39 | n1.should.have.property('name', 'swagger-petstore'); 40 | done(); 41 | }); 42 | }); 43 | it('should handle addPet()', function (done) { 44 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "addPet"}, 45 | {id: "n2", type: "helper"}]; 46 | helper.load(swaggerNode, flow, function () { 47 | var n1 = helper.getNode('n1'); 48 | var n2 = helper.getNode('n2'); 49 | n2.on('input', function (msg) { 50 | try { 51 | msg.payload.should.eql({ 52 | "id": 4513, 53 | "category": { 54 | "id": 4649, 55 | "name": "string" 56 | }, 57 | "name": "doggie", 58 | "photoUrls": [ 59 | "string" 60 | ], 61 | "tags": [ 62 | { 63 | "id": 2525, 64 | "name": "string" 65 | } 66 | ], 67 | "status": "available" 68 | }); 69 | done(); 70 | } catch (e) { 71 | done(e); 72 | } 73 | }); 74 | n1.receive({ 75 | payload: { 76 | "id": 4513, 77 | "category": { 78 | "id": 4649, 79 | "name": "string" 80 | }, 81 | "name": "doggie", 82 | "photoUrls": [ 83 | "string" 84 | ], 85 | "tags": [ 86 | { 87 | "id": 2525, 88 | "name": "string" 89 | } 90 | ], 91 | "status": "available" 92 | } 93 | }); 94 | }); 95 | }); 96 | it('should handle updatePet()', function (done) { 97 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "updatePet"}, 98 | {id: "n2", type: "helper"}]; 99 | helper.load(swaggerNode, flow, function () { 100 | var n1 = helper.getNode('n1'); 101 | var n2 = helper.getNode('n2'); 102 | n2.on('input', function (msg) { 103 | try { 104 | msg.payload.should.eql({ 105 | "id": 4513, 106 | "category": { 107 | "id": 5963, 108 | "name": "string" 109 | }, 110 | "name": "doggie", 111 | "photoUrls": [ 112 | "string" 113 | ], 114 | "tags": [ 115 | { 116 | "id": 3341, 117 | "name": "string" 118 | } 119 | ], 120 | "status": "available" 121 | }); 122 | done(); 123 | } catch (e) { 124 | done(e); 125 | } 126 | }); 127 | n1.receive({ 128 | payload: { 129 | "id": 4513, 130 | "category": { 131 | "id": 5963, 132 | "name": "string" 133 | }, 134 | "name": "doggie", 135 | "photoUrls": [ 136 | "string" 137 | ], 138 | "tags": [ 139 | { 140 | "id": 3341, 141 | "name": "string" 142 | } 143 | ], 144 | "status": "available" 145 | } 146 | }); 147 | }); 148 | }); 149 | it('should handle findPetsByStatus()', function (done) { 150 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "findPetsByStatus", findPetsByStatus_status: "available"}, 151 | {id: "n2", type: "helper"}]; 152 | helper.load(swaggerNode, flow, function () { 153 | var n1 = helper.getNode('n1'); 154 | var n2 = helper.getNode('n2'); 155 | n2.on('input', function (msg) { 156 | try { 157 | msg.payload.should.containEql({ 158 | "id": 4513, 159 | "category": { 160 | "id": 5963, 161 | "name": "string" 162 | }, 163 | "name": "doggie", 164 | "photoUrls": [ 165 | "string" 166 | ], 167 | "tags": [ 168 | { 169 | "id": 3341, 170 | "name": "string" 171 | } 172 | ], 173 | "status": "available" 174 | }); 175 | done(); 176 | } catch (e) { 177 | done(e); 178 | } 179 | }); 180 | n1.receive({}); 181 | }); 182 | }); 183 | it('should handle getPetById()', function (done) { 184 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "getPetById", getPetById_petId: "4513"}, 185 | {id: "n2", type: "helper"}]; 186 | helper.load(swaggerNode, flow, function () { 187 | var n1 = helper.getNode('n1'); 188 | var n2 = helper.getNode('n2'); 189 | n2.on('input', function (msg) { 190 | try { 191 | msg.payload.should.containEql({ 192 | "id": 4513, 193 | "category": { 194 | "id": 5963, 195 | "name": "string" 196 | }, 197 | "name": "doggie", 198 | "photoUrls": [ 199 | "string" 200 | ], 201 | "tags": [ 202 | { 203 | "id": 3341, 204 | "name": "string" 205 | } 206 | ], 207 | "status": "available" 208 | }); 209 | done(); 210 | } catch (e) { 211 | done(e); 212 | } 213 | }); 214 | n1.receive({}); 215 | }); 216 | }); 217 | it('should handle updatePetWithForm()', function (done) { 218 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "updatePetWithForm", updatePetWithForm_petId: "4513", updatePetWithForm_name: "pending doggie", updatePetWithForm_status: "pending"}, 219 | {id: "n2", type: "helper"}]; 220 | helper.load(swaggerNode, flow, function () { 221 | var n1 = helper.getNode('n1'); 222 | var n2 = helper.getNode('n2'); 223 | n2.on('input', function (msg) { 224 | try { 225 | msg.payload.should.eql({ code: 200, type: 'unknown', message: '4513' }); 226 | msg.should.have.property('topic', 'bar'); 227 | done(); 228 | } catch (e) { 229 | done(e); 230 | } 231 | }); 232 | n1.receive({ payload: "foo", topic: "bar" }); 233 | }); 234 | }); 235 | it('should handle deletePet()', function (done) { 236 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "deletePet", deletePet_petId: "4513"}, 237 | {id: "n2", type: "helper"}]; 238 | helper.load(swaggerNode, flow, function () { 239 | var n1 = helper.getNode('n1'); 240 | var n2 = helper.getNode('n2'); 241 | n2.on('input', function (msg) { 242 | try { 243 | msg.payload.should.eql({ code: 200, type: 'unknown', message: '4513' }); 244 | msg.should.have.property('topic', 'bar'); 245 | done(); 246 | } catch (e) { 247 | done(e); 248 | } 249 | }); 250 | n1.receive({ payload: "foo", topic: "bar" }); 251 | }); 252 | }); 253 | it('should handle getInventory()', function (done) { 254 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "getInventory"}, 255 | {id: "n2", type: "helper"}]; 256 | helper.load(swaggerNode, flow, function () { 257 | var n1 = helper.getNode('n1'); 258 | var n2 = helper.getNode('n2'); 259 | n2.on('input', function (msg) { 260 | try { 261 | msg.payload.should.have.property('available'); 262 | done(); 263 | } catch (e) { 264 | done(e); 265 | } 266 | }); 267 | n1.receive({}); 268 | }); 269 | }); 270 | it('should handle placeOrder()', function (done) { 271 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "placeOrder"}, 272 | {id: "n2", type: "helper"}]; 273 | helper.load(swaggerNode, flow, function () { 274 | var n1 = helper.getNode('n1'); 275 | var n2 = helper.getNode('n2'); 276 | n2.on('input', function (msg) { 277 | try { 278 | msg.payload.should.eql({ 279 | "id": 4147, 280 | "petId": 4513, 281 | "quantity": 1, 282 | "shipDate": "1983-09-12T09:03:00.000+0000", 283 | "status": "placed", 284 | "complete": false 285 | }); 286 | done(); 287 | } catch (e) { 288 | done(e); 289 | } 290 | }); 291 | n1.receive({ 292 | payload: { 293 | "id": 4147, 294 | "petId": 4513, 295 | "quantity": 1, 296 | "shipDate": "1983-09-12T09:03:00.000Z", 297 | "status": "placed", 298 | "complete": false 299 | } 300 | }); 301 | }); 302 | }); 303 | it('should handle getOrderById()', function (done) { 304 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "getOrderById", getOrderById_orderId: "4147"}, 305 | {id: "n2", type: "helper"}]; 306 | helper.load(swaggerNode, flow, function () { 307 | var n1 = helper.getNode('n1'); 308 | var n2 = helper.getNode('n2'); 309 | n2.on('input', function (msg) { 310 | try { 311 | msg.payload.should.eql({ 312 | "id": 4147, 313 | "petId": 4513, 314 | "quantity": 1, 315 | "shipDate": "1983-09-12T09:03:00.000+0000", 316 | "status": "placed", 317 | "complete": false 318 | }); 319 | done(); 320 | } catch (e) { 321 | done(e); 322 | } 323 | }); 324 | n1.receive({}); 325 | }); 326 | }); 327 | it('should handle deleteOrder()', function (done) { 328 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "deleteOrder", deleteOrder_orderId: "4147"}, 329 | {id: "n2", type: "helper"}]; 330 | helper.load(swaggerNode, flow, function () { 331 | var n1 = helper.getNode('n1'); 332 | var n2 = helper.getNode('n2'); 333 | n2.on('input', function (msg) { 334 | try { 335 | msg.payload.should.eql({ code: 200, type: 'unknown', message: '4147' }); 336 | msg.should.have.property('topic', 'bar'); 337 | done(); 338 | } catch (e) { 339 | done(e); 340 | } 341 | }); 342 | n1.receive({ payload: "foo", topic: "bar" }); 343 | }); 344 | }); 345 | it('should handle createUser()', function (done) { 346 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "createUser"}, 347 | {id: "n2", type: "helper"}]; 348 | helper.load(swaggerNode, flow, function () { 349 | var n1 = helper.getNode('n1'); 350 | var n2 = helper.getNode('n2'); 351 | n2.on('input', function (msg) { 352 | try { 353 | msg.payload.should.eql({ code: 200, type: 'unknown', message: '8110' }); 354 | done(); 355 | } catch (e) { 356 | done(e); 357 | } 358 | }); 359 | n1.receive({ 360 | payload: { 361 | "id": 8110, 362 | "username": "My user name", 363 | "firstName": "My first name", 364 | "lastName": "My last name", 365 | "email": "My e-mail address", 366 | "password": "My password", 367 | "phone": "My phone number", 368 | "userStatus": 0 369 | } 370 | }); 371 | }); 372 | }); 373 | it('should handle createUsersWithArrayInput()', function (done) { 374 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "createUsersWithArrayInput"}, 375 | {id: "n2", type: "helper"}]; 376 | helper.load(swaggerNode, flow, function () { 377 | var n1 = helper.getNode('n1'); 378 | var n2 = helper.getNode('n2'); 379 | n2.on('input', function (msg) { 380 | try { 381 | msg.payload.should.eql({ code: 200, type: 'unknown', message: 'ok' }); 382 | done(); 383 | } catch (e) { 384 | done(e); 385 | } 386 | }); 387 | n1.receive({ 388 | payload: [ 389 | { 390 | "id": 8948, 391 | "username": "My user name", 392 | "firstName": "My first name", 393 | "lastName": "My last name", 394 | "email": "My e-mail address", 395 | "password": "My password", 396 | "phone": "My phone number", 397 | "userStatus": 0 398 | } 399 | ] 400 | }); 401 | }); 402 | }); 403 | it('should handle createUsersWithListInput()', function (done) { 404 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "createUsersWithListInput"}, 405 | {id: "n2", type: "helper"}]; 406 | helper.load(swaggerNode, flow, function () { 407 | var n1 = helper.getNode('n1'); 408 | var n2 = helper.getNode('n2'); 409 | n2.on('input', function (msg) { 410 | try { 411 | msg.payload.should.eql({ code: 200, type: 'unknown', message: 'ok' }); 412 | done(); 413 | } catch (e) { 414 | done(e); 415 | } 416 | }); 417 | n1.receive({ 418 | payload: [ 419 | { 420 | "id": 8808, 421 | "username": "My user name", 422 | "firstName": "My first name", 423 | "lastName": "My last name", 424 | "email": "My e-mail address", 425 | "password": "My password", 426 | "phone": "My phone number", 427 | "userStatus": 0 428 | } 429 | ] 430 | }); 431 | }); 432 | }); 433 | it('should handle loginUser()', function (done) { 434 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "loginUser", loginUser_username: "My user name", loginUser_password: "My password"}, 435 | {id: "n2", type: "helper"}]; 436 | helper.load(swaggerNode, flow, function () { 437 | var n1 = helper.getNode('n1'); 438 | var n2 = helper.getNode('n2'); 439 | n2.on('input', function (msg) { 440 | try { 441 | msg.payload.should.containEql({ code: 200, type: 'unknown' }); 442 | msg.payload.message.should.startWith('logged in user session:'); 443 | done(); 444 | } catch (e) { 445 | done(e); 446 | } 447 | }); 448 | n1.receive({}); 449 | }); 450 | }); 451 | it('should handle logoutUser()', function (done) { 452 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "logoutUser"}, 453 | {id: "n2", type: "helper"}]; 454 | helper.load(swaggerNode, flow, function () { 455 | var n1 = helper.getNode('n1'); 456 | var n2 = helper.getNode('n2'); 457 | n2.on('input', function (msg) { 458 | try { 459 | msg.payload.should.eql({ code: 200, type: 'unknown', message: 'ok' }); 460 | msg.should.have.property('topic', 'bar'); 461 | done(); 462 | } catch (e) { 463 | done(e); 464 | } 465 | }); 466 | n1.receive({ payload: "foo", topic: "bar" }); 467 | }); 468 | }); 469 | it('should handle getUserByName()', function (done) { 470 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "getUserByName", getUserByName_username: "My user name"}, 471 | {id: "n2", type: "helper"}]; 472 | helper.load(swaggerNode, flow, function () { 473 | var n1 = helper.getNode('n1'); 474 | var n2 = helper.getNode('n2'); 475 | n2.on('input', function (msg) { 476 | try { 477 | msg.payload.should.eql({ 478 | "id": 8110, 479 | "username": "My user name", 480 | "firstName": "My first name", 481 | "lastName": "My last name", 482 | "email": "My e-mail address", 483 | "password": "My password", 484 | "phone": "My phone number", 485 | "userStatus": 0 486 | }); 487 | done(); 488 | } catch (e) { 489 | done(e); 490 | } 491 | }); 492 | n1.receive({}); 493 | }); 494 | }); 495 | it('should handle updateUser()', function (done) { 496 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "updateUser", updateUser_username: "My user name"}, 497 | {id: "n2", type: "helper"}]; 498 | helper.load(swaggerNode, flow, function () { 499 | var n1 = helper.getNode('n1'); 500 | var n2 = helper.getNode('n2'); 501 | n2.on('input', function (msg) { 502 | try { 503 | msg.payload.should.eql({ code: 200, type: 'unknown', message: '8808' }); 504 | done(); 505 | } catch (e) { 506 | done(e); 507 | } 508 | }); 509 | n1.receive({ 510 | payload: { 511 | "id": 8808, 512 | "username": "My user name2", 513 | "firstName": "My first name2", 514 | "lastName": "My last name2", 515 | "email": "My e-mail address2", 516 | "password": "My password2", 517 | "phone": "My phone number2", 518 | "userStatus": 0 519 | } 520 | }); 521 | }); 522 | }); 523 | it('should handle deleteUser()', function (done) { 524 | var flow = [{id: "n1", type: "swagger-petstore", wires: [["n2"]], method: "deleteUser", deleteUser_username: "My user name2"}, 525 | {id: "n2", type: "helper"}]; 526 | helper.load(swaggerNode, flow, function () { 527 | var n1 = helper.getNode('n1'); 528 | var n2 = helper.getNode('n2'); 529 | n2.on('input', function (msg) { 530 | try { 531 | msg.payload.should.eql({ code: 200, type: 'unknown', message: 'My user name2' }); 532 | msg.should.have.property('topic', 'bar'); 533 | done(); 534 | } catch (e) { 535 | done(e); 536 | } 537 | }); 538 | n1.receive({ payload: "foo", topic: "bar" }); 539 | }); 540 | }); 541 | }); 542 | --------------------------------------------------------------------------------