@gregoriusrippenstein/node-red-contrib-nodedev 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +15 -0
- package/README.md +50 -0
- package/examples/convert-existing-tgz.json +130 -0
- package/examples/factory-generate-example-node.json +380 -0
- package/nodes/10-node-factory.html +311 -0
- package/nodes/10-node-factory.js +375 -0
- package/nodes/20-pkg-file.html +135 -0
- package/nodes/20-pkg-file.js +209 -0
- package/nodes/30-npm-tarball.html +33 -0
- package/nodes/30-npm-tarball.js +53 -0
- package/nodes/40-node-red-install.html +141 -0
- package/nodes/40-node-red-install.js +48 -0
- package/nodes/50-npm-publish.html +97 -0
- package/nodes/50-npm-publish.js +76 -0
- package/nodes/templates/tmpl.html +70 -0
- package/nodes/templates/tmpl.js +18 -0
- package/package.json +42 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
<script type="text/html" data-template-name="NodeFactory">
|
|
2
|
+
<div class="form-row">
|
|
3
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
4
|
+
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-name" placeholder="Name"></div>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div class="form-row">
|
|
8
|
+
<label for="node-input-autoimport">
|
|
9
|
+
<span>Auto Import of Nodes?</span>
|
|
10
|
+
</label>
|
|
11
|
+
<input type="checkbox" id="node-input-autoimport" style="display:inline-block; width:15px; vertical-align:baseline;">
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="form-row">
|
|
15
|
+
<label for="node-input-nodename"><i class="fa fa-tag"></i> Node Name</label>
|
|
16
|
+
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-nodename" placeholder="Node Name">
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="form-row">
|
|
21
|
+
<label for="node-input-category"><i class="fa fa-tag"></i> Node Category</label>
|
|
22
|
+
<div style="display: inline-block; width: calc(100% - 105px)"><input type="text" id="node-input-category" placeholder="Node Category">
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div id="node-input-row-style-colour" class="form-row">
|
|
27
|
+
<label>Color</label>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div id="node-input-row-icon-picker" class="form-row">
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="form-row">
|
|
34
|
+
<label for="node-input-hasbutton">
|
|
35
|
+
<span>Has button?</span>
|
|
36
|
+
</label>
|
|
37
|
+
<input type="checkbox" id="node-input-hasbutton" style="display:inline-block; width:15px; vertical-align:baseline;">
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="form-row">
|
|
41
|
+
<label for="node-input-hasinput">
|
|
42
|
+
<span>Has input port?</span>
|
|
43
|
+
</label>
|
|
44
|
+
<input type="checkbox" id="node-input-hasinput" style="display:inline-block; width:15px; vertical-align:baseline;">
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="form-row">
|
|
48
|
+
<label for="node-input-outputcount">
|
|
49
|
+
<i class="fa fa-tag"></i> Output ports
|
|
50
|
+
</label>
|
|
51
|
+
<select id="node-input-outputcount"></select>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="form-row">
|
|
55
|
+
<label for="node-input-summary">
|
|
56
|
+
<i class="fa fa-tag"></i>
|
|
57
|
+
<span>Summary</span>
|
|
58
|
+
</label>
|
|
59
|
+
<div style="height: 150px; min-height:150px; max-height: 150px;" class="node-text-editor" id="node-input-summary">
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="form-row">
|
|
64
|
+
<label for="node-input-description">
|
|
65
|
+
<i class="fa fa-tag"></i>
|
|
66
|
+
<span>Description</span>
|
|
67
|
+
</label>
|
|
68
|
+
<div style="height: 350px; min-height:350px; max-height: 350px;" class="node-text-editor" id="node-input-description">
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<script type="text/javascript">
|
|
75
|
+
(function () {
|
|
76
|
+
|
|
77
|
+
RED.comms.subscribe("nodedev:perform-autoimport-nodes", (event,data) => {
|
|
78
|
+
if ( data.msg == "autoimport" ) {
|
|
79
|
+
RED.clipboard.import();
|
|
80
|
+
|
|
81
|
+
setTimeout(() => {
|
|
82
|
+
$('#red-ui-clipboard-dialog-import-text').val(
|
|
83
|
+
data.payload
|
|
84
|
+
).trigger("paste");
|
|
85
|
+
}, 300);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
function doSubmission(node) {
|
|
90
|
+
|
|
91
|
+
var data = {};
|
|
92
|
+
Object.keys(node._def.defaults).forEach( (nam) => {
|
|
93
|
+
data[nam] = node[nam]
|
|
94
|
+
})
|
|
95
|
+
data["name"] = node.nodename;
|
|
96
|
+
data["__task"] = "generate_from_templates";
|
|
97
|
+
|
|
98
|
+
$.ajax({
|
|
99
|
+
url: "NodeFactory/" + node.id,
|
|
100
|
+
type: "POST",
|
|
101
|
+
contentType: "application/json; charset=utf-8",
|
|
102
|
+
|
|
103
|
+
data: JSON.stringify({
|
|
104
|
+
node: data,
|
|
105
|
+
}),
|
|
106
|
+
|
|
107
|
+
success: function (resp) {
|
|
108
|
+
RED.notify("Data sent", {
|
|
109
|
+
type: "warning",
|
|
110
|
+
id: "FlowHubPush",
|
|
111
|
+
timeout: 2000
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
error: function (jqXHR, textStatus, errorThrown) {
|
|
116
|
+
if (jqXHR.status == 404) {
|
|
117
|
+
RED.notify(node._("common.notification.error", {
|
|
118
|
+
message: node._("common.notification.errors.not-deployed")
|
|
119
|
+
}), "error");
|
|
120
|
+
} else if (jqXHR.status == 405) {
|
|
121
|
+
RED.notify(node._("common.notification.not_allowed", {
|
|
122
|
+
message: node._("inject.errors.not_allowed")
|
|
123
|
+
}), "error");
|
|
124
|
+
} else if (jqXHR.status == 500) {
|
|
125
|
+
RED.notify(node._("common.notification.error", {
|
|
126
|
+
message: node._("inject.errors.failed")
|
|
127
|
+
}), "error");
|
|
128
|
+
} else if (jqXHR.status == 0) {
|
|
129
|
+
RED.notify(node._("common.notification.error", {
|
|
130
|
+
message: node._("common.notification.errors.no-response")
|
|
131
|
+
}), "error");
|
|
132
|
+
} else {
|
|
133
|
+
RED.notify(node._("common.notification.error", {
|
|
134
|
+
message: node._("common.notification.errors.unexpected", {
|
|
135
|
+
status: jqXHR.status, message: textStatus
|
|
136
|
+
})
|
|
137
|
+
}), "error");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
RED.nodes.registerType('NodeFactory', {
|
|
144
|
+
color: "#e5e4ef",
|
|
145
|
+
category: 'nodedev',
|
|
146
|
+
defaults: {
|
|
147
|
+
name: { value: "" },
|
|
148
|
+
nodename: { value: "" },
|
|
149
|
+
color: { value: "#e5e4ef" },
|
|
150
|
+
hasbutton: { value: false },
|
|
151
|
+
hasinput: { value: true },
|
|
152
|
+
outputcount: { value: 1 },
|
|
153
|
+
autoimport: { value: true },
|
|
154
|
+
category: { value: "" },
|
|
155
|
+
summary: { value: ""},
|
|
156
|
+
description: { value: ""},
|
|
157
|
+
icon: { value: "font-awesome/fa-industry" }
|
|
158
|
+
},
|
|
159
|
+
inputs: 1,
|
|
160
|
+
outputs: 1,
|
|
161
|
+
icon: "font-awesome/fa-industry",
|
|
162
|
+
label: function () {
|
|
163
|
+
return this.name || this._def.paletteLabel;
|
|
164
|
+
},
|
|
165
|
+
labelStyle: function () {
|
|
166
|
+
return this.name ? "node_label_italic" : "";
|
|
167
|
+
},
|
|
168
|
+
oneditprepare: function () {
|
|
169
|
+
const that = this;
|
|
170
|
+
const stateId = RED.editor.generateViewStateId("node", this, "");
|
|
171
|
+
|
|
172
|
+
var sltObj = $('#node-input-outputcount');
|
|
173
|
+
sltObj.html("");
|
|
174
|
+
|
|
175
|
+
[0,1,2,3,4,5,6,7,8,9,10].forEach( function(v) {
|
|
176
|
+
sltObj.append($('<option></option>').val(v).html(v));
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
sltObj.val(this.outputcount || "1");
|
|
180
|
+
|
|
181
|
+
var colorPalette = [
|
|
182
|
+
"#DDAA99",
|
|
183
|
+
"#3FADB5", "#87A980", "#A6BBCF",
|
|
184
|
+
"#AAAA66", "#C0C0C0", "#C0DEED",
|
|
185
|
+
"#C7E9C0", "#D7D7A0", "#D8BFD8",
|
|
186
|
+
"#DAC4B4", "#DEB887", "#DEBD5C",
|
|
187
|
+
"#E2D96E", "#E6E0F8", "#E7E7AE",
|
|
188
|
+
"#E9967A", "#F3B567", "#FDD0A2",
|
|
189
|
+
"#FDF0C2", "#FFAAAA", "#FFCC66",
|
|
190
|
+
"#FFF0F0", "#FFFFFF"
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
RED.editor.colorPicker.create({
|
|
194
|
+
id: "node-input-colour",
|
|
195
|
+
value: this.color || "#a4a4a4",
|
|
196
|
+
defaultValue: "#a4a4a4",
|
|
197
|
+
palette: colorPalette,
|
|
198
|
+
cellWidth: 16,
|
|
199
|
+
cellHeight: 16,
|
|
200
|
+
cellMargin: 3,
|
|
201
|
+
none: false,
|
|
202
|
+
opacity: 1.0,
|
|
203
|
+
sortPalette: function (a, b) { return a.l - b.l; }
|
|
204
|
+
}).appendTo("#node-input-row-style-colour");
|
|
205
|
+
|
|
206
|
+
$("#node-input-colour").on('change', function(ev) {
|
|
207
|
+
var colour = $(this).val();
|
|
208
|
+
var nodeDiv = $('.red-ui-search-result-node')
|
|
209
|
+
nodeDiv.css('backgroundColor',colour);
|
|
210
|
+
var borderColor = RED.utils.getDarkerColor(colour);
|
|
211
|
+
if (borderColor !== colour) {
|
|
212
|
+
nodeDiv.css('border-color',borderColor);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
this.editorSummary = RED.editor.createEditor({
|
|
217
|
+
id: 'node-input-summary',
|
|
218
|
+
mode: 'ace/mode/markdown',
|
|
219
|
+
value: $("#node-input-summary").val()
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
this.editorDesc = RED.editor.createEditor({
|
|
223
|
+
id: 'node-input-description',
|
|
224
|
+
mode: 'ace/mode/markdown',
|
|
225
|
+
value: $("#node-input-description").val()
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
var node = this;
|
|
229
|
+
var iconRow = $('#node-input-row-icon-picker');
|
|
230
|
+
$('<label data-i18n="editor.settingIcon">').appendTo(iconRow);
|
|
231
|
+
|
|
232
|
+
var iconButton = $('<button type="button" class="red-ui-button red-ui-editor-node-appearance-button">').appendTo(iconRow);
|
|
233
|
+
$('<i class="fa fa-caret-down"></i>').appendTo(iconButton);
|
|
234
|
+
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(iconButton);
|
|
235
|
+
var colour = this.color || RED.utils.getNodeColor(node.type, node._def);
|
|
236
|
+
var icon_url = RED.utils.getNodeIcon(node._def,node);
|
|
237
|
+
nodeDiv.css('backgroundColor',colour);
|
|
238
|
+
var borderColor = RED.utils.getDarkerColor(colour);
|
|
239
|
+
if (borderColor !== colour) {
|
|
240
|
+
nodeDiv.css('border-color',borderColor);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
|
|
244
|
+
RED.utils.createIconElement(icon_url, iconContainer, true);
|
|
245
|
+
|
|
246
|
+
iconButton.on("click", function(e) {
|
|
247
|
+
e.preventDefault();
|
|
248
|
+
var iconPath;
|
|
249
|
+
var icon = $("#red-ui-editor-node-icon").val()||"";
|
|
250
|
+
if (icon) {
|
|
251
|
+
iconPath = RED.utils.separateIconPath(icon);
|
|
252
|
+
} else {
|
|
253
|
+
iconPath = RED.utils.getDefaultNodeIcon(node._def, node);
|
|
254
|
+
}
|
|
255
|
+
var backgroundColor = RED.utils.getNodeColor(node.type, node._def);
|
|
256
|
+
if (node.type === "subflow") {
|
|
257
|
+
backgroundColor = $("#red-ui-editor-node-color").val();
|
|
258
|
+
}
|
|
259
|
+
RED.editor.iconPicker.show(iconButton,backgroundColor,iconPath,false,function(newIcon) {
|
|
260
|
+
$("#red-ui-editor-node-icon").val(newIcon||"");
|
|
261
|
+
var icon_url = RED.utils.getNodeIcon(node._def,{type:node.type,icon:newIcon});
|
|
262
|
+
RED.utils.createIconElement(icon_url, iconContainer, true);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
RED.popover.tooltip(iconButton, function() {
|
|
267
|
+
return $("#red-ui-editor-node-icon").val() || RED._("editor.default");
|
|
268
|
+
});
|
|
269
|
+
$('<input type="hidden" id="red-ui-editor-node-icon">').val(node.icon).appendTo(iconRow);
|
|
270
|
+
},
|
|
271
|
+
|
|
272
|
+
oneditsave: function () {
|
|
273
|
+
this.color = $('#node-input-colour').val();
|
|
274
|
+
$("#node-input-summary").val(this.editorSummary.getValue());
|
|
275
|
+
$("#node-input-description").val(this.editorDesc.getValue());
|
|
276
|
+
this.icon = $('#red-ui-editor-node-icon').val();
|
|
277
|
+
|
|
278
|
+
this.editorSummary.destroy();
|
|
279
|
+
this.editorDesc.destroy();
|
|
280
|
+
delete this.editorSummary;
|
|
281
|
+
delete this.editorDesc;
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
oneditcancel: function () {
|
|
285
|
+
this.editorSummary.destroy();
|
|
286
|
+
this.editorDesc.destroy();
|
|
287
|
+
delete this.editorSummary;
|
|
288
|
+
delete this.editorDesc;
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
oneditresize: function (size) {
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
button: {
|
|
295
|
+
enabled: function () {
|
|
296
|
+
return !this.changed
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
onclick: function () {
|
|
300
|
+
if (this.changed) {
|
|
301
|
+
return RED.notify(RED._("notification.warning", {
|
|
302
|
+
message: RED._("notification.warnings.undeployedChanges")
|
|
303
|
+
}), "warning");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
doSubmission(this)
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
})();
|
|
311
|
+
</script>
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
module.exports = function (RED) {
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
|
|
7
|
+
var mustache = require("mustache");
|
|
8
|
+
var tarStream = require('tar-stream');
|
|
9
|
+
var streamx = require('streamx');
|
|
10
|
+
var pakoGzip = require('pako');
|
|
11
|
+
|
|
12
|
+
function extractTokens(tokens, set) {
|
|
13
|
+
set = set || new Set();
|
|
14
|
+
tokens.forEach(function (token) {
|
|
15
|
+
if (token[0] !== 'text') {
|
|
16
|
+
set.add(token[1]);
|
|
17
|
+
if (token.length > 4) {
|
|
18
|
+
extractTokens(token[4], set);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return set;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseContext(key) {
|
|
26
|
+
var match = /^(flow|global)(\[(\w+)\])?\.(.+)/.exec(key);
|
|
27
|
+
if (match) {
|
|
28
|
+
var parts = {};
|
|
29
|
+
parts.type = match[1];
|
|
30
|
+
parts.store = (match[3] === '') ? "default" : match[3];
|
|
31
|
+
parts.field = match[4];
|
|
32
|
+
return parts;
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseEnv(key) {
|
|
38
|
+
var match = /^env\.(.+)/.exec(key);
|
|
39
|
+
if (match) {
|
|
40
|
+
return match[1];
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Custom Mustache Context capable to collect message property and node
|
|
47
|
+
* flow and global context
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
function NodeContext(msg, nodeContext, parent, escapeStrings, cachedContextTokens) {
|
|
51
|
+
this.msgContext = new mustache.Context(msg, parent);
|
|
52
|
+
this.nodeContext = nodeContext;
|
|
53
|
+
this.escapeStrings = escapeStrings;
|
|
54
|
+
this.cachedContextTokens = cachedContextTokens;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
NodeContext.prototype = new mustache.Context();
|
|
58
|
+
|
|
59
|
+
NodeContext.prototype.lookup = function (name) {
|
|
60
|
+
// try message first:
|
|
61
|
+
try {
|
|
62
|
+
var value = this.msgContext.lookup(name);
|
|
63
|
+
if (value !== undefined) {
|
|
64
|
+
if (this.escapeStrings && typeof value === "string") {
|
|
65
|
+
value = value.replace(/\\/g, "\\\\");
|
|
66
|
+
value = value.replace(/\n/g, "\\n");
|
|
67
|
+
value = value.replace(/\t/g, "\\t");
|
|
68
|
+
value = value.replace(/\r/g, "\\r");
|
|
69
|
+
value = value.replace(/\f/g, "\\f");
|
|
70
|
+
value = value.replace(/[\b]/g, "\\b");
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// try env
|
|
76
|
+
if (parseEnv(name)) {
|
|
77
|
+
return this.cachedContextTokens[name];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// try flow/global context:
|
|
81
|
+
var context = parseContext(name);
|
|
82
|
+
if (context) {
|
|
83
|
+
var type = context.type;
|
|
84
|
+
var store = context.store;
|
|
85
|
+
var field = context.field;
|
|
86
|
+
var target = this.nodeContext[type];
|
|
87
|
+
if (target) {
|
|
88
|
+
return this.cachedContextTokens[name];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return '';
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
NodeContext.prototype.push = function push(view) {
|
|
99
|
+
return new NodeContext(view, this.nodeContext, this.msgContext, undefined, this.cachedContextTokens);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
function handleTemplate(msg, node, template) {
|
|
103
|
+
var promises = [];
|
|
104
|
+
var tokens = extractTokens(mustache.parse(template));
|
|
105
|
+
var resolvedTokens = {};
|
|
106
|
+
|
|
107
|
+
tokens.forEach(function (name) {
|
|
108
|
+
var env_name = parseEnv(name);
|
|
109
|
+
if (env_name) {
|
|
110
|
+
var promise = new Promise((resolve, reject) => {
|
|
111
|
+
var val = RED.util.evaluateNodeProperty(env_name, 'env', node)
|
|
112
|
+
resolvedTokens[name] = val;
|
|
113
|
+
resolve();
|
|
114
|
+
});
|
|
115
|
+
promises.push(promise);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
var context = parseContext(name);
|
|
120
|
+
if (context) {
|
|
121
|
+
var type = context.type;
|
|
122
|
+
var store = context.store;
|
|
123
|
+
var field = context.field;
|
|
124
|
+
var target = node.context()[type];
|
|
125
|
+
if (target) {
|
|
126
|
+
var promise = new Promise((resolve, reject) => {
|
|
127
|
+
target.get(field, store, (err, val) => {
|
|
128
|
+
if (err) {
|
|
129
|
+
reject(err);
|
|
130
|
+
} else {
|
|
131
|
+
resolvedTokens[name] = val;
|
|
132
|
+
resolve();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
promises.push(promise);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
return Promise.all(promises).then(function () {
|
|
143
|
+
return mustache.render(template, new NodeContext(msg, node.context(), null, false, resolvedTokens));
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function convertToPkgFileNodes(msg, jsCtnt, htmlCtnt) {
|
|
148
|
+
var allNodes = [];
|
|
149
|
+
|
|
150
|
+
var secondId = RED.util.generateId();
|
|
151
|
+
|
|
152
|
+
allNodes.push({
|
|
153
|
+
id: RED.util.generateId(),
|
|
154
|
+
type: "PkgFile",
|
|
155
|
+
name: msg.node.name + ".js",
|
|
156
|
+
filename: "nodes/" + msg.node.name.toLowerCase() + ".js",
|
|
157
|
+
template: jsCtnt,
|
|
158
|
+
syntax: "mustache",
|
|
159
|
+
format: "javascript",
|
|
160
|
+
output: "str",
|
|
161
|
+
x: 100,
|
|
162
|
+
y: 50,
|
|
163
|
+
wires: [
|
|
164
|
+
[
|
|
165
|
+
secondId
|
|
166
|
+
]
|
|
167
|
+
]
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
allNodes.push({
|
|
171
|
+
id: secondId,
|
|
172
|
+
type: "PkgFile",
|
|
173
|
+
name: msg.node.name + ".html",
|
|
174
|
+
filename: "nodes/" + msg.node.name.toLowerCase() + ".html",
|
|
175
|
+
template: htmlCtnt,
|
|
176
|
+
syntax: "mustache",
|
|
177
|
+
format: "html",
|
|
178
|
+
output: "str",
|
|
179
|
+
x: 100,
|
|
180
|
+
y: 100,
|
|
181
|
+
wires: [
|
|
182
|
+
[
|
|
183
|
+
]
|
|
184
|
+
]
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
return allNodes;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function NodeFactoryFunctionality(config) {
|
|
191
|
+
RED.nodes.createNode(this, config);
|
|
192
|
+
|
|
193
|
+
var node = this;
|
|
194
|
+
var cfg = config;
|
|
195
|
+
|
|
196
|
+
node.on('close', function () {
|
|
197
|
+
node.status({});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
node.on("input", function (msg, send, done) {
|
|
201
|
+
if (msg.node && msg.node.__task == "generate_from_templates") {
|
|
202
|
+
try {
|
|
203
|
+
var htmlPath = path.join(__dirname, 'templates', 'tmpl.html');
|
|
204
|
+
var jsPath = path.join(__dirname, 'templates', 'tmpl.js');
|
|
205
|
+
|
|
206
|
+
var jsonTmpl = fs.readFileSync(jsPath, 'utf8');
|
|
207
|
+
var htmlTmpl = fs.readFileSync(htmlPath, 'utf8');
|
|
208
|
+
|
|
209
|
+
handleTemplate(msg, node, jsonTmpl).then(function (data) {
|
|
210
|
+
var jsData = data;
|
|
211
|
+
handleTemplate(msg, node, htmlTmpl).then(function (data) {
|
|
212
|
+
var nodeImpStr = JSON.stringify(convertToPkgFileNodes(msg, jsData, data));
|
|
213
|
+
|
|
214
|
+
send({ payload: nodeImpStr })
|
|
215
|
+
|
|
216
|
+
if (cfg.autoimport) {
|
|
217
|
+
RED.comms.publish(
|
|
218
|
+
"nodedev:perform-autoimport-nodes",
|
|
219
|
+
RED.util.encodeObject({
|
|
220
|
+
msg: "autoimport",
|
|
221
|
+
payload: nodeImpStr,
|
|
222
|
+
topic: msg.topic,
|
|
223
|
+
nodeid: node.id,
|
|
224
|
+
_msg: msg
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
done()
|
|
230
|
+
}).catch((err) => {
|
|
231
|
+
msg.error = err
|
|
232
|
+
done(err.message, msg)
|
|
233
|
+
})
|
|
234
|
+
}).catch((err) => {
|
|
235
|
+
msg.error = err
|
|
236
|
+
done(err.message, msg)
|
|
237
|
+
})
|
|
238
|
+
//send({ payload: jsonString })
|
|
239
|
+
} catch (err) {
|
|
240
|
+
msg.error = err
|
|
241
|
+
done(err.message, msg)
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
/* assume that payload is a buffer with a .tgz if not, error out */
|
|
245
|
+
try {
|
|
246
|
+
const extract = tarStream.extract()
|
|
247
|
+
|
|
248
|
+
var allFiles = [];
|
|
249
|
+
|
|
250
|
+
/*
|
|
251
|
+
* there is no indication in a tar file of whether a file is binary or textual.
|
|
252
|
+
* we can only make a guess by the extension of the filename.
|
|
253
|
+
***/
|
|
254
|
+
var computeFormat = (filename) => {
|
|
255
|
+
var ext = filename.split(".").at(-1);
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
"html": "html",
|
|
259
|
+
"js": "javascript",
|
|
260
|
+
"md": "markdown",
|
|
261
|
+
"json": "json",
|
|
262
|
+
/* binary formats are encoded in base64 */
|
|
263
|
+
"png": "base64",
|
|
264
|
+
"tiff": "base64",
|
|
265
|
+
"tif": "base64",
|
|
266
|
+
"jpg": "base64",
|
|
267
|
+
"jpeg": "base64",
|
|
268
|
+
"bin": "base64",
|
|
269
|
+
"bmp": "base64",
|
|
270
|
+
}[ext.toLowerCase()] || "text";
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
extract.on('entry', function (header, stream, next) {
|
|
274
|
+
// header is the tar header
|
|
275
|
+
// stream is the content body (might be an empty stream)
|
|
276
|
+
// call next when you are done with this entry
|
|
277
|
+
|
|
278
|
+
var buffer = [];
|
|
279
|
+
|
|
280
|
+
stream.on('data', function (data) {
|
|
281
|
+
buffer.push(data)
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
stream.on('end', function () {
|
|
285
|
+
var frmt = computeFormat(header.name.split("/").at(-1));
|
|
286
|
+
|
|
287
|
+
allFiles.push({
|
|
288
|
+
id: RED.util.generateId(),
|
|
289
|
+
type: "PkgFile",
|
|
290
|
+
name: header.name.split("/").at(-1),
|
|
291
|
+
filename: header.name.replace(/^package\//, ''),
|
|
292
|
+
template: Buffer.concat(buffer).toString(frmt == "base64" ? 'base64' : 'utf8'),
|
|
293
|
+
syntax: "mustache",
|
|
294
|
+
format: frmt,
|
|
295
|
+
output: "str",
|
|
296
|
+
x: 100,
|
|
297
|
+
y: 50 * (allFiles.length + 1),
|
|
298
|
+
wires: [
|
|
299
|
+
[]
|
|
300
|
+
]
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
next() // ready for next entry
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
stream.resume() // just auto drain the stream
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
extract.on('finish', function () {
|
|
310
|
+
// all entries read, wire them together
|
|
311
|
+
for (var idx = 0; idx < allFiles.length - 1; idx++) {
|
|
312
|
+
allFiles[idx].wires = [[allFiles[idx + 1].id]];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
msg.payload = JSON.stringify(allFiles);
|
|
316
|
+
send(msg)
|
|
317
|
+
|
|
318
|
+
if (cfg.autoimport) {
|
|
319
|
+
RED.comms.publish(
|
|
320
|
+
"nodedev:perform-autoimport-nodes",
|
|
321
|
+
RED.util.encodeObject({
|
|
322
|
+
msg: "autoimport",
|
|
323
|
+
payload: msg.payload,
|
|
324
|
+
topic: msg.topic,
|
|
325
|
+
nodeid: node.id,
|
|
326
|
+
_msg: msg
|
|
327
|
+
})
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
done()
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
extract.on('error', function (err) {
|
|
335
|
+
msg.error = err;
|
|
336
|
+
done("extraction error", msg)
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
var stream = streamx.Readable.from(Buffer.from(pakoGzip.inflate(new Uint8Array(msg.payload))))
|
|
340
|
+
stream.pipe(extract);
|
|
341
|
+
} catch (err) {
|
|
342
|
+
msg.error = err
|
|
343
|
+
done(err.message, msg)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
RED.nodes.registerType("NodeFactory", NodeFactoryFunctionality);
|
|
350
|
+
|
|
351
|
+
RED.httpAdmin.post("/NodeFactory/:id",
|
|
352
|
+
RED.auth.needsPermission("NodeFactory.write"),
|
|
353
|
+
(req, res) => {
|
|
354
|
+
var node = RED.nodes.getNode(req.params.id);
|
|
355
|
+
if (node != null) {
|
|
356
|
+
try {
|
|
357
|
+
if (req.body && req.params.id) {
|
|
358
|
+
var nde = RED.nodes.getNode(req.params.id)
|
|
359
|
+
if (nde && nde.type == "NodeFactory") {
|
|
360
|
+
node.receive(req.body);
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
res.sendStatus(404);
|
|
364
|
+
}
|
|
365
|
+
res.sendStatus(200);
|
|
366
|
+
} catch (err) {
|
|
367
|
+
res.sendStatus(500);
|
|
368
|
+
node.error("FlowHub: Submission failed: " +
|
|
369
|
+
err.toString())
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
res.sendStatus(404);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|