@theotherwillembotha/node-red-plugincore 0.0.55 → 0.2.0
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/README.md +616 -217
- package/build/GenerateNodes.js +3 -6
- package/build/Nodes.html +3 -1502
- package/build/Nodes.js +5 -15
- package/build/Plugins.html +227 -0
- package/build/Plugins.js +18 -9
- package/build/core/NodeConstructor.d.ts +10 -16
- package/build/core/NodeConstructor.d.ts.map +1 -1
- package/build/core/NodeConstructor.js +112 -156
- package/build/core/NodeGenerator.d.ts.map +1 -1
- package/build/core/NodeGenerator.js +79 -29
- package/build/core/NodeManagerRuntime.d.ts +44 -0
- package/build/core/NodeManagerRuntime.d.ts.map +1 -0
- package/build/core/NodeManagerRuntime.js +140 -0
- package/build/core/Utils.d.ts.map +1 -1
- package/build/core/logger/service/LoggerService.d.ts +6 -5
- package/build/core/logger/service/LoggerService.d.ts.map +1 -1
- package/build/core/logger/service/LoggerService.js +51 -20
- package/build/core/logger/template/LoggerTemplate.d.ts.map +1 -1
- package/build/core/metrics/MetricsDecorator.d.ts +1 -2
- package/build/core/metrics/MetricsDecorator.d.ts.map +1 -1
- package/build/core/metrics/MetricsDecorator.js +89 -116
- package/build/core/metrics/node/CounterMetricConfigNode.d.ts.map +1 -1
- package/build/core/metrics/node/CounterMetricConfigNode.js +4 -5
- package/build/core/metrics/node/GaugeMetricConfigNode.d.ts.map +1 -1
- package/build/core/metrics/node/GaugeMetricConfigNode.js +5 -6
- package/build/core/metrics/node/MetricsConfigNode.d.ts +19 -9
- package/build/core/metrics/node/MetricsConfigNode.d.ts.map +1 -1
- package/build/core/metrics/node/MetricsConfigNode.js +14 -45
- package/build/core/metrics/node/TimerMetricConfigNode.d.ts.map +1 -1
- package/build/core/metrics/node/TimerMetricConfigNode.js +11 -17
- package/build/core/metrics/service/MetricsService.d.ts +110 -114
- package/build/core/metrics/service/MetricsService.d.ts.map +1 -1
- package/build/core/metrics/service/MetricsService.js +122 -339
- package/build/core/metrics/template/CounterMetricTemplate.d.ts.map +1 -1
- package/build/core/metrics/template/GaugeMetricTemplate.d.ts.map +1 -1
- package/build/core/metrics/template/MetricsTemplate.d.ts.map +1 -1
- package/build/core/metrics/template/MetricsTemplate.js +1 -2
- package/build/core/metrics/template/TimerMetricTemplate.d.ts.map +1 -1
- package/build/core/other/node/DelegatedConfigReferenceNode.d.ts +1 -1
- package/build/core/other/node/DelegatedConfigReferenceNode.d.ts.map +1 -1
- package/build/core/other/node/DelegatedConfigReferenceNode.js +1 -1
- package/build/core/other/service/InputService.d.ts.map +1 -1
- package/build/core/other/service/SettingsService.d.ts +1 -2
- package/build/core/other/service/SettingsService.d.ts.map +1 -1
- package/build/core/other/service/SettingsService.js +19 -5
- package/build/core/other/template/BasicTemplate.d.ts.map +1 -1
- package/build/core/other/template/SettingsTemplate.d.ts.map +1 -1
- package/build/core/state/node/InternalStateConfigNode.d.ts +16 -0
- package/build/core/state/node/InternalStateConfigNode.d.ts.map +1 -0
- package/build/core/state/node/InternalStateConfigNode.js +89 -0
- package/build/core/state/node/StateConfigNode.d.ts +24 -0
- package/build/core/state/node/StateConfigNode.d.ts.map +1 -0
- package/build/core/state/node/StateConfigNode.js +22 -0
- package/build/core/state/service/StateService.d.ts +34 -0
- package/build/core/state/service/StateService.d.ts.map +1 -0
- package/build/core/state/service/StateService.js +201 -0
- package/build/core/state/template/StateTemplate.d.ts +8 -0
- package/build/core/state/template/StateTemplate.d.ts.map +1 -0
- package/build/core/state/template/StateTemplate.js +11 -0
- package/build/core/tagging/ServiceDescriptionDecorator.d.ts +10 -0
- package/build/core/tagging/ServiceDescriptionDecorator.d.ts.map +1 -0
- package/build/core/tagging/ServiceDescriptionDecorator.js +10 -0
- package/build/core/tagging/service/NodeTypeService.d.ts +1 -2
- package/build/core/tagging/service/NodeTypeService.d.ts.map +1 -1
- package/build/core/tagging/service/NodeTypeService.js +19 -5
- package/build/core/ui/template/ScriptEditorTemplate.d.ts.map +1 -1
- package/build/core/ui/template/UIHelperTemplate.d.ts.map +1 -1
- package/build/core/webhook/WebhookDecorator.d.ts.map +1 -1
- package/build/core/webhook/WebhookDecorator.js +43 -6
- package/build/core/webhook/node/WebhookServerConfigNode.d.ts.map +1 -1
- package/build/core/webhook/service/ReverseProxyTypeService.d.ts +1 -0
- package/build/core/webhook/service/ReverseProxyTypeService.d.ts.map +1 -1
- package/build/core/webhook/service/WebhookServerService.d.ts +3 -4
- package/build/core/webhook/service/WebhookServerService.d.ts.map +1 -1
- package/build/core/webhook/service/WebhookServerService.js +48 -23
- package/build/core/webhook/template/WebhookTemplate.d.ts.map +1 -1
- package/build/index.d.ts +5 -2
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -2
- package/build/runtime/NodeManagerRuntime.js +140 -0
- package/documentation/CounterMetricConfigNode.png +0 -0
- package/documentation/GaugeMetricConfigNode.png +0 -0
- package/documentation/LoggerTemplate.png +0 -0
- package/documentation/MetricsTemplate.png +0 -0
- package/documentation/StateTemplate.png +0 -0
- package/documentation/TimerMetricConfigNode_histogram.png +0 -0
- package/documentation/TimerMetricConfigNode_summary.png +0 -0
- package/documentation/WebhookServerConfigNode.png +0 -0
- package/documentation/WebhookTemplate.png +0 -0
- package/package.json +5 -12
- package/src/GenerateNodes.ts +7 -11
- package/src/core/NodeConstructor.ts +134 -197
- package/src/core/NodeGenerator.ts +88 -34
- package/src/core/NodeManagerRuntime.ts +179 -0
- package/src/core/logger/service/LoggerService.ts +39 -26
- package/src/core/logger/template/LoggerTemplate.html +13 -8
- package/src/core/metrics/MetricsDecorator.ts +140 -168
- package/src/core/metrics/node/CounterMetricConfigNode.ts +20 -20
- package/src/core/metrics/node/GaugeMetricConfigNode.ts +24 -25
- package/src/core/metrics/node/MetricsConfigNode.ts +29 -48
- package/src/core/metrics/node/TimerMetricConfigNode.html +2 -2
- package/src/core/metrics/node/TimerMetricConfigNode.ts +50 -59
- package/src/core/metrics/service/MetricsService.ts +214 -489
- package/src/core/metrics/template/CounterMetricTemplate.html +0 -1
- package/src/core/metrics/template/GaugeMetricTemplate.html +1 -1
- package/src/core/metrics/template/MetricsTemplate.html +132 -13
- package/src/core/metrics/template/MetricsTemplate.ts +3 -4
- package/src/core/other/node/DelegatedConfigReferenceNode.ts +1 -1
- package/src/core/other/service/SettingsService.ts +6 -10
- package/src/core/other/template/BasicTemplate.html +5 -2
- package/src/core/state/node/InternalStateConfigNode.html +77 -0
- package/src/core/state/node/InternalStateConfigNode.ts +102 -0
- package/src/core/state/node/StateConfigNode.ts +29 -0
- package/src/core/state/service/StateService.ts +180 -0
- package/src/core/state/template/StateTemplate.html +244 -0
- package/src/core/state/template/StateTemplate.ts +17 -0
- package/src/core/tagging/ServiceDescriptionDecorator.ts +23 -0
- package/src/core/tagging/service/NodeTypeService.ts +6 -11
- package/src/core/ui/template/UIHelperTemplate.html +41 -26
- package/src/core/webhook/WebhookDecorator.ts +53 -10
- package/src/core/webhook/service/ReverseProxyTypeService.ts +1 -0
- package/src/core/webhook/service/WebhookServerService.ts +39 -28
- package/src/core/webhook/template/WebhookTemplate.html +29 -36
- package/src/index.ts +7 -2
- package/build/core/logger/node/ConsoleLoggerConfigNode.d.ts +0 -20
- package/build/core/logger/node/ConsoleLoggerConfigNode.d.ts.map +0 -1
- package/build/core/logger/node/ConsoleLoggerConfigNode.html +0 -90
- package/build/core/logger/node/ConsoleLoggerConfigNode.js +0 -62
- package/build/core/logger/node/RestLoggerConfigNode.d.ts +0 -40
- package/build/core/logger/node/RestLoggerConfigNode.d.ts.map +0 -1
- package/build/core/logger/node/RestLoggerConfigNode.html +0 -183
- package/build/core/logger/node/RestLoggerConfigNode.js +0 -128
- package/build/core/logger/template/LoggerTemplate.html +0 -331
- package/build/core/metrics/node/CounterMetricConfigNode.html +0 -48
- package/build/core/metrics/node/GaugeMetricConfigNode.html +0 -48
- package/build/core/metrics/node/MetricsConfigNode.html +0 -21
- package/build/core/metrics/node/TimerMetricConfigNode.html +0 -190
- package/build/core/metrics/template/CounterMetricTemplate.html +0 -40
- package/build/core/metrics/template/GaugeMetricTemplate.html +0 -39
- package/build/core/metrics/template/MetricsTemplate.html +0 -54
- package/build/core/metrics/template/TimerMetricTemplate.html +0 -39
- package/build/core/other/node/DelegatedConfigReferenceNode.html +0 -1
- package/build/core/other/template/BasicTemplate.html +0 -76
- package/build/core/other/template/SettingsTemplate.html +0 -128
- package/build/core/ui/template/ScriptEditorTemplate.html +0 -50
- package/build/core/ui/template/UIHelperTemplate.html +0 -229
- package/build/core/webhook/node/WebhookServerConfigNode.html +0 -61
- package/build/core/webhook/template/WebhookTemplate.html +0 -442
- package/documentation/ConsoleLoggerConfigNode.png +0 -0
- package/documentation/MetricsConfigNode.png +0 -0
- package/documentation/RestLoggerConfigNode.png +0 -0
- package/documentation/TimerMetricConfigNode.png +0 -0
- package/documentation/WebhookNode.png +0 -0
- package/src/core/logger/node/ConsoleLoggerConfigNode.html +0 -90
- package/src/core/logger/node/ConsoleLoggerConfigNode.ts +0 -71
- package/src/core/logger/node/RestLoggerConfigNode.html +0 -183
- package/src/core/logger/node/RestLoggerConfigNode.ts +0 -170
|
@@ -1,23 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Node, NodeAPI, NodeDef, NodeAPISettingsWithData} from "node-red";
|
|
1
|
+
import { Node, NodeAPI, NodeAPISettingsWithData} from "node-red";
|
|
3
2
|
import { INamedType, INodeClass, ITemplateClass } from "./NodeGenerator";
|
|
4
3
|
|
|
5
4
|
import "reflect-metadata";
|
|
6
|
-
import jsdom from "jsdom";
|
|
7
5
|
import fs from 'fs';
|
|
8
|
-
import beautify from 'js-beautify';
|
|
9
|
-
import markdownit from 'markdown-it'
|
|
10
|
-
import Handlebars, { template } from "handlebars";
|
|
11
|
-
import { NodeTypeService } from "./tagging/service/NodeTypeService";
|
|
12
|
-
import { NodeDescriptionConfig } from "./tagging/NodeDescriptionDecorator";
|
|
13
6
|
|
|
7
|
+
import {
|
|
8
|
+
NODEMANAGER_API_VERSION,
|
|
9
|
+
POST_CONSTRUCT_KEY,
|
|
10
|
+
BaseNodeConfig,
|
|
11
|
+
runPostConstructInitializers,
|
|
12
|
+
NodeManager
|
|
13
|
+
} from "./NodeManagerRuntime";
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
export
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
// Re-export so existing downstream code keeps working unchanged.
|
|
16
|
+
export {
|
|
17
|
+
NODEMANAGER_API_VERSION,
|
|
18
|
+
POST_CONSTRUCT_KEY,
|
|
19
|
+
BaseNodeConfig,
|
|
20
|
+
runPostConstructInitializers,
|
|
21
|
+
NodeManager
|
|
22
|
+
};
|
|
21
23
|
|
|
22
24
|
export abstract class BaseNode<Config extends BaseNodeConfig> {
|
|
23
25
|
|
|
@@ -199,8 +201,6 @@ export abstract class Template{
|
|
|
199
201
|
|
|
200
202
|
type VoidMethod = (...args: any[]) => void | Promise<void>;
|
|
201
203
|
|
|
202
|
-
const POST_CONSTRUCT_KEY = Symbol('postConstructInitializers');
|
|
203
|
-
|
|
204
204
|
// Builder class for post-construct decorators
|
|
205
205
|
class PostConstructDecoratorBuilder<TMetadata = any> {
|
|
206
206
|
private decoratorName: string;
|
|
@@ -342,150 +342,6 @@ export function createPostConstructDecorator<TMetadata = any>(decoratorName: str
|
|
|
342
342
|
return new PostConstructDecoratorBuilder<TMetadata>(decoratorName);
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
// Single function to run all post-construct initializers
|
|
346
|
-
let runPostConstructInitializers = function(instance: any) {
|
|
347
|
-
const initializers = instance[POST_CONSTRUCT_KEY];
|
|
348
|
-
if (initializers && initializers.length > 0) {
|
|
349
|
-
const className = instance.constructor.name;
|
|
350
|
-
//console.log(`Running ${initializers.length} post-construct initializers for ${className}`);
|
|
351
|
-
|
|
352
|
-
initializers.forEach((init: Function, index: number) => {
|
|
353
|
-
try {
|
|
354
|
-
init(instance);
|
|
355
|
-
} catch (error) {
|
|
356
|
-
console.error(
|
|
357
|
-
`Post-construct initialization failed:\n` +
|
|
358
|
-
` Class: ${className}\n` +
|
|
359
|
-
` Initializer: ${index + 1}/${initializers.length}\n` +
|
|
360
|
-
` Error: ${error instanceof Error ? error.message : String(error)}`
|
|
361
|
-
);
|
|
362
|
-
throw error;
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
// Clear the initializers after running
|
|
367
|
-
delete instance[POST_CONSTRUCT_KEY];
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// ************************************************
|
|
372
|
-
|
|
373
|
-
export class NodeManager{
|
|
374
|
-
private static PLUGINID = "node-red-contrib-theotherwillembotha/services-plugin";
|
|
375
|
-
private static _RED: NodeAPI<NodeAPISettingsWithData>;
|
|
376
|
-
|
|
377
|
-
private nodeTypeService:NodeTypeService;
|
|
378
|
-
private typeBacklog:INodeClass[] = []
|
|
379
|
-
|
|
380
|
-
public constructor(RED : NodeAPI<NodeAPISettingsWithData>){
|
|
381
|
-
NodeManager._RED = RED;
|
|
382
|
-
|
|
383
|
-
let nodeTypeServiceListener = async (pluginID:String) => {
|
|
384
|
-
if(pluginID === "@theotherwillembotha/nodetypeservice"){
|
|
385
|
-
// disable the listener- the plugin has been installed.
|
|
386
|
-
RED.events.off('plugin.instantiated', nodeTypeServiceListener)
|
|
387
|
-
|
|
388
|
-
// get a handle on the NodeTypeService
|
|
389
|
-
let plugin = RED.plugins.get("@theotherwillembotha/nodetypeservice") as any;
|
|
390
|
-
this.nodeTypeService = (RED.plugins.get("@theotherwillembotha/nodetypeservice") as any).instance as NodeTypeService;
|
|
391
|
-
|
|
392
|
-
// process the backlog.
|
|
393
|
-
for(let nodeType of this.typeBacklog){
|
|
394
|
-
for(let tag of nodeType.getNodeDescriptor().tags()){
|
|
395
|
-
this.nodeTypeService.registerNodeType(tag, nodeType);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// check if the reverserproxyttypesewrvice is installed yet.
|
|
402
|
-
this.nodeTypeService = (RED.plugins.get("@theotherwillembotha/nodetypeservice") as any).instance as NodeTypeService;
|
|
403
|
-
if(this.nodeTypeService) {
|
|
404
|
-
console.log("NodeTypeService installed!")
|
|
405
|
-
}
|
|
406
|
-
else {
|
|
407
|
-
// Register a listener to trigger when the RegerseProxyTypService gets installed.
|
|
408
|
-
console.log("nodetypeservice not installed yet. waiting for it to be installed");
|
|
409
|
-
RED.events.on('plugin.instantiated', nodeTypeServiceListener);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
public static get RED() {
|
|
414
|
-
return NodeManager._RED;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
public registerService(type:IServiceClass):NodeManager{
|
|
418
|
-
let servicePlugin:any = NodeManager.RED.plugins.get(NodeManager.PLUGINID) as PluginDefinition<any>;
|
|
419
|
-
if(servicePlugin){
|
|
420
|
-
servicePlugin.instance.install(NodeManager.RED, type);
|
|
421
|
-
}
|
|
422
|
-
else{
|
|
423
|
-
let serviceName = `${type.getServiceDescriptor().sourceFile()}.${type.getServiceDescriptor().name()}`;
|
|
424
|
-
console.log(`ERROR: plugin ${NodeManager.PLUGINID} does not seem to be loaded. Cannot start service: ${serviceName}`);
|
|
425
|
-
}
|
|
426
|
-
return this;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
public registerNodeType(typeName: string, type: INodeClass):NodeManager{
|
|
430
|
-
if(!type){
|
|
431
|
-
console.error(`Type "${typeName}" has no associated type. Make sure that it was exported and included in the index.js file!`)
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
let nodeConstructor = function(this: any, config: BaseNodeConfig | NodeDef){
|
|
435
|
-
//console.log("constructing node", typeName);
|
|
436
|
-
|
|
437
|
-
try{
|
|
438
|
-
// instantiate the node.
|
|
439
|
-
NodeManager.RED.nodes.createNode(this, config as any as NodeDef);
|
|
440
|
-
// assign an instance of it to the config.
|
|
441
|
-
//(config as any).__node = this;
|
|
442
|
-
|
|
443
|
-
// instatiate the actual node.
|
|
444
|
-
let node = new (type as any)(this, config);
|
|
445
|
-
//this.__node = node;
|
|
446
|
-
|
|
447
|
-
// run the constructor initializers.
|
|
448
|
-
runPostConstructInitializers(node);
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
// after the node has been instantiated, attempt to invole the onInit method.
|
|
452
|
-
if(node.onInit){
|
|
453
|
-
node.onInit();
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
catch(error){
|
|
457
|
-
console.log(error);
|
|
458
|
-
console.log("typeName:", typeName)
|
|
459
|
-
console.log("type:", type)
|
|
460
|
-
console.log("config:", config)
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
try{
|
|
465
|
-
console.log("registering node " + typeName);
|
|
466
|
-
NodeManager.RED.nodes.registerType(typeName, nodeConstructor, { settings: {}});
|
|
467
|
-
|
|
468
|
-
let nodeDescription = type.getNodeDescriptor();
|
|
469
|
-
|
|
470
|
-
// registger the node tags.
|
|
471
|
-
if(this.nodeTypeService){
|
|
472
|
-
for(let tag of nodeDescription.tags()){
|
|
473
|
-
this.nodeTypeService.registerNodeType(tag, type);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
else{
|
|
477
|
-
this.typeBacklog.push(type);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
console.log("registered node " + typeName);
|
|
481
|
-
}
|
|
482
|
-
catch(error){
|
|
483
|
-
console.error("registering node: " + typeName + " failed", error);
|
|
484
|
-
}
|
|
485
|
-
return this;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
345
|
export type FlowDeployment = {
|
|
490
346
|
rev:string,
|
|
491
347
|
flows:[FlowElement]
|
|
@@ -521,25 +377,27 @@ export abstract class BaseService {
|
|
|
521
377
|
}
|
|
522
378
|
}
|
|
523
379
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
380
|
+
// Build-time deps - lazy loaded so they are never required at runtime in production bundles.
|
|
381
|
+
// These are only invoked during `npm run build` (GenerateNodes), never in the deployed container.
|
|
382
|
+
function getJSDOM(): any { return require('jsdom').JSDOM; }
|
|
383
|
+
function getBeautify(): any { return require('js-beautify'); }
|
|
384
|
+
function getHandlebars(): any { return require('handlebars'); }
|
|
385
|
+
|
|
386
|
+
let _md: any = null;
|
|
387
|
+
function getMd(): any {
|
|
388
|
+
if (!_md) {
|
|
389
|
+
_md = require('markdown-it')({ html: true, linkify: true, typographer: true });
|
|
390
|
+
_md.renderer.rules.heading_open = (tokens: any[], idx: number) => {
|
|
391
|
+
const level = tokens[idx].tag.substring(1);
|
|
392
|
+
return `<h${level} class="custom-heading">`;
|
|
393
|
+
};
|
|
394
|
+
_md.renderer.rules.heading_close = (tokens: any[], idx: number) => {
|
|
395
|
+
const level = tokens[idx].tag.substring(1);
|
|
396
|
+
return `</h${level}>`;
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
return _md;
|
|
400
|
+
}
|
|
543
401
|
|
|
544
402
|
let beautifyOptions = {
|
|
545
403
|
"indent_size": 4,
|
|
@@ -569,6 +427,7 @@ export type DefaultTemplateType = {
|
|
|
569
427
|
minInstances?:number;
|
|
570
428
|
maxInstances?:number;
|
|
571
429
|
validate?:Function;
|
|
430
|
+
list?:boolean;
|
|
572
431
|
}
|
|
573
432
|
|
|
574
433
|
export class NodeBuilder {
|
|
@@ -598,12 +457,12 @@ export class NodeBuilder {
|
|
|
598
457
|
}
|
|
599
458
|
|
|
600
459
|
public addDefault(name:string, template:DefaultTemplateType):NodeBuilder{
|
|
601
|
-
// if
|
|
602
|
-
if(template.
|
|
460
|
+
// if list:true, this is an array of config references - generate shadow defaults for each slot.
|
|
461
|
+
if(template.list === true) {
|
|
603
462
|
template.maxInstances = template.maxInstances ? template.maxInstances : 10;
|
|
604
|
-
this._defaults[name] = {
|
|
463
|
+
this._defaults[name] = {value:"", required:(template.required) ? template.required : false, maxInstances:template.maxInstances};
|
|
605
464
|
for(let i = 0; i < template.maxInstances; i++){
|
|
606
|
-
this._defaults["_" + name + "_" + i] = {value:"", required:false};
|
|
465
|
+
this._defaults["_" + name + "_" + i] = {value:"", required:false, type: template.type};
|
|
607
466
|
}
|
|
608
467
|
}
|
|
609
468
|
else{
|
|
@@ -648,9 +507,10 @@ export class NodeBuilder {
|
|
|
648
507
|
|
|
649
508
|
public addTemplate(template: { template: ITemplateClass; config: TemplateConfig; }): NodeBuilder {
|
|
650
509
|
let templateDescriptor = template.template.getTemplateDescriptor();
|
|
510
|
+
let templateConfig = template.config;
|
|
651
511
|
|
|
652
512
|
// load the html source file
|
|
653
|
-
let uiDom = new
|
|
513
|
+
let uiDom = new (getJSDOM())(fs.readFileSync(templateDescriptor.templateFile()!));
|
|
654
514
|
|
|
655
515
|
// apply the onCompose section if available.
|
|
656
516
|
let onCompose = uiDom.window.document.querySelector("script[template-section='onCompose']")?.innerHTML;
|
|
@@ -662,7 +522,16 @@ export class NodeBuilder {
|
|
|
662
522
|
let sections:string[] = ['IncludeOnce', 'IncludeEditPrepare', 'IncludeEditSave', 'IncludeEditCancel', 'IncludeEditDelete', 'IncludeEditForm', 'IncludeDocumentation'];
|
|
663
523
|
Object.values(sections).forEach(section => {
|
|
664
524
|
let sectionData = uiDom.window.document.querySelector(`[template-section='on${section}']`)?.innerHTML;
|
|
525
|
+
|
|
665
526
|
if(sectionData){
|
|
527
|
+
if(section !== "IncludeOnce" && section !== "IncludeEditForm" && section !== "IncludeDocumentation") {
|
|
528
|
+
// add the packageing around the sectionData
|
|
529
|
+
sectionData = `{
|
|
530
|
+
let node = this;
|
|
531
|
+
let templateConfig = ${JSON.stringify(templateConfig)};
|
|
532
|
+
${sectionData}
|
|
533
|
+
}`;
|
|
534
|
+
}
|
|
666
535
|
(this as any)[`add${section}`](templateDescriptor.name(), sectionData);
|
|
667
536
|
}
|
|
668
537
|
});
|
|
@@ -705,6 +574,69 @@ export class NodeBuilder {
|
|
|
705
574
|
return this;
|
|
706
575
|
}
|
|
707
576
|
|
|
577
|
+
public buildOnceHtml(): {source: string, html: string}[] {
|
|
578
|
+
return this._onIncludeOnce.map(entry => ({
|
|
579
|
+
source: entry.source,
|
|
580
|
+
html: this.serializeHTML(entry.source, entry.script)
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
public buildDeferredType(): string {
|
|
585
|
+
// Apply shadow defaults (same side-effect as buildType)
|
|
586
|
+
let shaddowDefaults = Object.entries(this._defaults).filter(([name, template]) => template.type && template.type.endsWith("[]"));
|
|
587
|
+
if (shaddowDefaults.length > 0) {
|
|
588
|
+
this.addIncludeEditSave(this._name + "_shaddowDefaults",
|
|
589
|
+
`{ let node = this; ` +
|
|
590
|
+
shaddowDefaults
|
|
591
|
+
.map(([name, template]) => `
|
|
592
|
+
for(let i = 0; i < ${template.maxInstances}; i++){
|
|
593
|
+
node["_${name}_" + i] = (node.${name}[i] || {}).proxy || "";
|
|
594
|
+
}`)
|
|
595
|
+
.join("\n\n") +
|
|
596
|
+
`}`
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const registerTypeBody = `{
|
|
601
|
+
category: '${this._category}',
|
|
602
|
+
${(this._category !== "config" && this._icon) ? `icon: '${this._icon}',` : ""}
|
|
603
|
+
${(this._category !== "config" && this._color) ? `color: '${this._color}',` : ""}
|
|
604
|
+
${(this._category !== "config" && this._labelStyle) ? `labelStyle: '${this._labelStyle}',` : ""}
|
|
605
|
+
${(this._label) ? `label: ${this._label},` : ""}
|
|
606
|
+
${this._paletteLabel ? `paletteLabel: '${this._paletteLabel}',` : ""}
|
|
607
|
+
${(this._category !== "config") ? `inputs:${this._inputs ? "1" : "0"},` : ""}
|
|
608
|
+
${(this._category !== "config") ? `inputLabels:(i) => '${this._inputs}',` : ""}
|
|
609
|
+
${(this._category !== "config") ? `outputs:${this._outputs.length},` : ""}
|
|
610
|
+
${(this._category !== "config") ? `outputLabels:(i) => ${this.serializeProperty(this._outputs)}[i],` : ""}
|
|
611
|
+
defaults: {
|
|
612
|
+
${Object.entries(this._defaults).map(([key, value]) => this.serializeDefault(key, value)).join("\n")}
|
|
613
|
+
},
|
|
614
|
+
oneditprepare: function() {
|
|
615
|
+
${this._onIncludeEditPrepare.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
|
|
616
|
+
},
|
|
617
|
+
oneditsave: function() {
|
|
618
|
+
${this._onIncludeEditSave.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
|
|
619
|
+
},
|
|
620
|
+
oneditcancel: function() {
|
|
621
|
+
${this._onIncludeEditCancel.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
|
|
622
|
+
},
|
|
623
|
+
oneditdelete: function() {
|
|
624
|
+
${this._onIncludeEditDelete.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
|
|
625
|
+
},
|
|
626
|
+
}`;
|
|
627
|
+
|
|
628
|
+
return getBeautify().html_beautify(`
|
|
629
|
+
<script type="text/javascript">
|
|
630
|
+
(function () {
|
|
631
|
+
RED.events.on('registry:node-set-added', function (ns) {
|
|
632
|
+
if (ns.types && ns.types.indexOf('${this._name}') !== -1) {
|
|
633
|
+
RED.nodes.registerType('${this._name}', ${registerTypeBody});
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
}());
|
|
637
|
+
</script>`, beautifyOptions);
|
|
638
|
+
}
|
|
639
|
+
|
|
708
640
|
private serializeProperty(value:any):string{
|
|
709
641
|
switch(typeof value){
|
|
710
642
|
case "boolean": {
|
|
@@ -737,18 +669,18 @@ export class NodeBuilder {
|
|
|
737
669
|
}
|
|
738
670
|
|
|
739
671
|
return `<!-- ${source} -->
|
|
740
|
-
${
|
|
672
|
+
${getHandlebars().compile(script)(context)}
|
|
741
673
|
`
|
|
742
674
|
}
|
|
743
675
|
|
|
744
|
-
private serializeJavaScript(source:string, script:string):string{
|
|
676
|
+
private serializeJavaScript(source:string, script:string):string{
|
|
745
677
|
let context = {
|
|
746
|
-
defaults: (this._category === "config")
|
|
678
|
+
defaults: (this._category === "config")
|
|
747
679
|
? Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-config-input-${k}`]))
|
|
748
680
|
: Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-input-${k}`]))
|
|
749
681
|
}
|
|
750
682
|
|
|
751
|
-
let serialized =
|
|
683
|
+
let serialized = getHandlebars().compile(script)(context).trim();
|
|
752
684
|
if(!serialized.startsWith("{") && !serialized.endsWith("}")){
|
|
753
685
|
serialized = "{\n" + serialized + "\n}";
|
|
754
686
|
}
|
|
@@ -758,14 +690,14 @@ export class NodeBuilder {
|
|
|
758
690
|
`
|
|
759
691
|
}
|
|
760
692
|
|
|
761
|
-
private serializeFormEntry(source:string, script:string):string{
|
|
693
|
+
private serializeFormEntry(source:string, script:string):string{
|
|
762
694
|
let context = {
|
|
763
|
-
defaults: (this._category === "config")
|
|
695
|
+
defaults: (this._category === "config")
|
|
764
696
|
? Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-config-input-${k}`]))
|
|
765
697
|
: Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-input-${k}`]))
|
|
766
698
|
}
|
|
767
699
|
return `
|
|
768
|
-
<div id='section_${source}'>${
|
|
700
|
+
<div id='section_${source}'>${getHandlebars().compile(script)(context)}
|
|
769
701
|
</div>
|
|
770
702
|
`
|
|
771
703
|
}
|
|
@@ -794,7 +726,7 @@ export class NodeBuilder {
|
|
|
794
726
|
);
|
|
795
727
|
}
|
|
796
728
|
|
|
797
|
-
return
|
|
729
|
+
return getBeautify().html_beautify(`
|
|
798
730
|
${this._onIncludeOnce.map(entry => this.serializeHTML(entry.source, entry.script)).join("")}
|
|
799
731
|
<script type="text/javascript">
|
|
800
732
|
RED.nodes.registerType('${this._name}',{
|
|
@@ -828,7 +760,7 @@ export class NodeBuilder {
|
|
|
828
760
|
};
|
|
829
761
|
|
|
830
762
|
public buildHtml():string{
|
|
831
|
-
return
|
|
763
|
+
return getBeautify().html_beautify(`
|
|
832
764
|
<script type="text/html" data-template-name='${this._name}'>
|
|
833
765
|
${this._onIncludeEditForm.map(entry => this.serializeFormEntry(entry.source, entry.script)).join("")}
|
|
834
766
|
</script>
|
|
@@ -861,13 +793,13 @@ let evalInContext = (context: any, js: string) => {
|
|
|
861
793
|
}
|
|
862
794
|
};
|
|
863
795
|
|
|
864
|
-
let
|
|
796
|
+
let buildNodeBuilder = function(node: INodeClass): NodeBuilder {
|
|
865
797
|
// get the node descriptor and create the builder.
|
|
866
798
|
let nodeDescriptor = node.getNodeDescriptor();
|
|
867
799
|
let nodeBuilder = new NodeBuilder(nodeDescriptor.id(), nodeDescriptor.group());
|
|
868
800
|
|
|
869
801
|
// load the html source file
|
|
870
|
-
let uiDom = new
|
|
802
|
+
let uiDom = new (getJSDOM())(fs.readFileSync(nodeDescriptor.sourceFile()));
|
|
871
803
|
|
|
872
804
|
// apply the onCompose section if available.
|
|
873
805
|
let onCompose = uiDom.window.document.querySelector("script[template-section='onCompose']")?.innerHTML;
|
|
@@ -887,10 +819,15 @@ let buildNode = function(node:INodeClass){
|
|
|
887
819
|
// add some of the dependencies here.
|
|
888
820
|
nodeDescriptor.templates().forEach(template => nodeBuilder.addTemplate(template));
|
|
889
821
|
|
|
890
|
-
return
|
|
822
|
+
return nodeBuilder;
|
|
823
|
+
}
|
|
891
824
|
|
|
825
|
+
let buildNode = function(node:INodeClass){
|
|
826
|
+
let nodeBuilder = buildNodeBuilder(node);
|
|
827
|
+
return [nodeBuilder.buildType(), nodeBuilder.buildHtml(), nodeBuilder.buildDocumentation()].join("\n\n");
|
|
892
828
|
}
|
|
893
829
|
|
|
894
830
|
export {
|
|
895
|
-
buildNode
|
|
831
|
+
buildNode,
|
|
832
|
+
buildNodeBuilder
|
|
896
833
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
2
3
|
|
|
3
|
-
import { buildNode, Template, NodeDescriptor, BaseService, ServiceDescriptor, BaseNode, DependencyType, TemplateDescriptor } from "./NodeConstructor"
|
|
4
|
+
import { buildNode, buildNodeBuilder, Template, NodeDescriptor, BaseService, ServiceDescriptor, BaseNode, DependencyType, TemplateDescriptor } from "./NodeConstructor"
|
|
4
5
|
|
|
5
6
|
const templatePropertyKeys = ["onIncludeOnce", "onIncludeDefaults", "onIncludeEditPrepare", "onIncludeEditPrepare", "onIncludeEditSave", "onIncludeEditForm"];
|
|
6
7
|
|
|
@@ -105,31 +106,17 @@ class NodeGenerator {
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
public generate(nodesOutputFile:string, pluginsOutputFile:string){
|
|
108
|
-
// STEP 1.
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
// })
|
|
114
|
-
// .join("\n");
|
|
115
|
-
let templateInclude = ""
|
|
116
|
-
|
|
117
|
-
// STEP 2. compose the HTMLStructure of each of the nodes.
|
|
118
|
-
let nodeHTMLOutut = Object.values(this._nodes)
|
|
119
|
-
.map(node => {
|
|
120
|
-
console.log(`Processing node: ${node.descriptor.id()}`);
|
|
121
|
-
|
|
122
|
-
return buildNode(node.nodeClass);
|
|
123
|
-
})
|
|
124
|
-
.join("\n")
|
|
125
|
-
|
|
126
|
-
fs.writeFileSync(nodesOutputFile + ".html",`
|
|
109
|
+
// STEP 1. Write an empty Nodes.html.
|
|
110
|
+
// Client-side node type definitions live in Plugins.html (generated below)
|
|
111
|
+
// to prevent HTML-scanning conflicts when multiple plugins bundle the same
|
|
112
|
+
// plugincore infrastructure nodes.
|
|
113
|
+
fs.writeFileSync(nodesOutputFile + ".html", `
|
|
127
114
|
<!--
|
|
128
115
|
${NodeGenerator.warning}
|
|
116
|
+
Client-side node type definitions have been moved to Plugins.html to prevent
|
|
117
|
+
duplicate type registration conflicts when multiple plugins share the same
|
|
118
|
+
plugincore infrastructure nodes.
|
|
129
119
|
-->
|
|
130
|
-
${templateInclude}
|
|
131
|
-
${nodeHTMLOutut}
|
|
132
|
-
|
|
133
120
|
`);
|
|
134
121
|
|
|
135
122
|
// Step 3. compose the JS structure for each of the nodes.
|
|
@@ -153,15 +140,37 @@ ${nodeHTMLOutut}
|
|
|
153
140
|
${NodeGenerator.warning}
|
|
154
141
|
*/
|
|
155
142
|
"use strict";
|
|
156
|
-
const NodeManager = require("
|
|
143
|
+
const { NodeManager } = require("./runtime/NodeManagerRuntime");
|
|
157
144
|
${importList}
|
|
158
145
|
module.exports = (RED) => {
|
|
159
146
|
let manager = new NodeManager(RED);
|
|
160
147
|
${moduleExports}
|
|
161
|
-
}
|
|
162
|
-
|
|
148
|
+
}
|
|
149
|
+
|
|
163
150
|
`);
|
|
164
151
|
|
|
152
|
+
// Copy NodeManagerRuntime.js into the plugin's build/runtime/ folder so that
|
|
153
|
+
// the generated Nodes.js require("./runtime/NodeManagerRuntime") resolves locally
|
|
154
|
+
// with no dependency on plugincore being installed in the user's environment.
|
|
155
|
+
const outputDir = path.dirname(nodesOutputFile);
|
|
156
|
+
const runtimeDestDir = path.join(outputDir, 'runtime');
|
|
157
|
+
const runtimeDestPath = path.join(runtimeDestDir, 'NodeManagerRuntime.js');
|
|
158
|
+
|
|
159
|
+
let runtimeSrcPath: string;
|
|
160
|
+
try {
|
|
161
|
+
// Resolves when this is a consumer plugin - plugincore is in node_modules.
|
|
162
|
+
runtimeSrcPath = require.resolve('@theotherwillembotha/node-red-plugincore/build/runtime/NodeManagerRuntime');
|
|
163
|
+
} catch {
|
|
164
|
+
// We ARE plugincore - NodeManagerRuntime.js is compiled alongside us in build/core/.
|
|
165
|
+
runtimeSrcPath = path.join(__dirname, 'NodeManagerRuntime.js');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (path.resolve(runtimeSrcPath) !== path.resolve(runtimeDestPath)) {
|
|
169
|
+
fs.mkdirSync(runtimeDestDir, { recursive: true });
|
|
170
|
+
fs.copyFileSync(runtimeSrcPath, runtimeDestPath);
|
|
171
|
+
}
|
|
172
|
+
console.log(`NodeManagerRuntime.js → ${runtimeDestPath}`);
|
|
173
|
+
|
|
165
174
|
console.log(`Done building ${Object.keys(this._nodes).length} nodes\n${Object.values(this._nodes).map(node => " - " + node.descriptor.id() + "\n").join("")}\n`);
|
|
166
175
|
|
|
167
176
|
let serviceExports = this._services.map(service => {
|
|
@@ -195,31 +204,39 @@ module.exports = function (RED) {
|
|
|
195
204
|
let plugin = RED.plugins.get(pluginID);
|
|
196
205
|
|
|
197
206
|
if(addedPlugin && plugin.instantiate && !plugin.instance){
|
|
198
|
-
console.log("Deploying instance of: " + plugin.id);
|
|
199
207
|
plugin.instance = new plugin.class();
|
|
200
|
-
|
|
208
|
+
|
|
201
209
|
// instantiate the plugin.
|
|
202
210
|
await plugin.instance.init(RED);
|
|
203
211
|
|
|
212
|
+
// Eagerly call onDeploy with any pre-existing saved flows so that
|
|
213
|
+
// service registries (MetricsService, WebhookServerService, etc.) are
|
|
214
|
+
// populated before config nodes are constructed during flow startup.
|
|
215
|
+
// Without this, MetricsConfigNode._metrics is undefined on first boot.
|
|
216
|
+
const existingFlows = await runtime.flows.getFlows({});
|
|
217
|
+
const hasExistingFlows = existingFlows && existingFlows.flows && existingFlows.flows.length > 0;
|
|
218
|
+
if (hasExistingFlows) {
|
|
219
|
+
await plugin.instance.onDeploy(existingFlows);
|
|
220
|
+
}
|
|
221
|
+
|
|
204
222
|
// publish an event that it has ben deployed.
|
|
205
223
|
RED.events.emit("plugin.instantiated", pluginID);
|
|
206
|
-
|
|
224
|
+
|
|
207
225
|
pluginList.splice(pluginList.findIndex(current => current.id() === addedPlugin.id()), 1);
|
|
208
226
|
|
|
209
227
|
// register a flow deployment listener.
|
|
210
|
-
|
|
228
|
+
// startupDeployment is only needed for a truly fresh Node-RED with no saved flows.
|
|
229
|
+
let startupDeployment = !hasExistingFlows;
|
|
211
230
|
RED.events.on('runtime-event', async (event) => {
|
|
212
|
-
// startup deployment.
|
|
231
|
+
// startup deployment (fresh Node-RED with no pre-existing flows).
|
|
213
232
|
if ("runtime-deploy" === event?.id && startupDeployment) {
|
|
214
|
-
//console.log("STARTUP DEPLOYMENT");
|
|
215
233
|
startupDeployment = false;
|
|
216
234
|
const flows = await runtime.flows.getFlows({});
|
|
217
235
|
await plugin.instance.onDeploy(flows);
|
|
218
236
|
}
|
|
219
|
-
|
|
237
|
+
|
|
220
238
|
// stopping flows on redeployment.
|
|
221
239
|
if ("runtime-state" === event?.id && event?.payload?.state === "stop" && event?.payload?.deploy) {
|
|
222
|
-
//console.log("REDEPLOY!");
|
|
223
240
|
const flows = await runtime.flows.getFlows({});
|
|
224
241
|
await plugin.instance.onDeploy(flows);
|
|
225
242
|
}
|
|
@@ -240,6 +257,43 @@ module.exports = function (RED) {
|
|
|
240
257
|
`
|
|
241
258
|
fs.writeFileSync(pluginsOutputFile + ".js", output);
|
|
242
259
|
console.log(`Done building ${this._services.length} services\n${this._services.map(service => " - " + service.name() + "\n").join("")}\n`);
|
|
260
|
+
|
|
261
|
+
// STEP 4. Generate Plugins.html with deferred client-side node definitions.
|
|
262
|
+
// All nodes are registered via registry:node-set-added so that:
|
|
263
|
+
// a) registerType is only called after typeToId/nodeSets are populated, and
|
|
264
|
+
// b) data-template-name declarations are absent from Nodes.html, avoiding
|
|
265
|
+
// the HTML-scanning conflict that causes Node-RED to reject an entire
|
|
266
|
+
// plugin when a second package declares the same node type.
|
|
267
|
+
const seenOnce = new Set<string>();
|
|
268
|
+
const onceHtmlParts: string[] = [];
|
|
269
|
+
const nodePluginParts: string[] = [];
|
|
270
|
+
|
|
271
|
+
Object.values(this._nodes).forEach(node => {
|
|
272
|
+
console.log(`Building Plugins.html for: ${node.descriptor.id()}`);
|
|
273
|
+
const nb = buildNodeBuilder(node.nodeClass);
|
|
274
|
+
|
|
275
|
+
nb.buildOnceHtml().forEach(({source, html}) => {
|
|
276
|
+
if (!seenOnce.has(source)) {
|
|
277
|
+
seenOnce.add(source);
|
|
278
|
+
onceHtmlParts.push(html);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
nodePluginParts.push([
|
|
283
|
+
nb.buildDeferredType(),
|
|
284
|
+
nb.buildHtml(),
|
|
285
|
+
nb.buildDocumentation()
|
|
286
|
+
].join('\n\n'));
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
fs.writeFileSync(pluginsOutputFile + ".html", `
|
|
290
|
+
<!--
|
|
291
|
+
${NodeGenerator.warning}
|
|
292
|
+
-->
|
|
293
|
+
${onceHtmlParts.join('\n')}
|
|
294
|
+
${nodePluginParts.join('\n\n')}
|
|
295
|
+
`);
|
|
296
|
+
console.log(`Done building Plugins.html\n`);
|
|
243
297
|
}
|
|
244
298
|
|
|
245
299
|
private static generateHeader(node:NodeDescriptor){
|