@theotherwillembotha/node-red-plugincore 0.1.0 → 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 -220
- package/build/GenerateNodes.js +3 -6
- package/build/Nodes.html +3 -1504
- package/build/Nodes.js +5 -15
- package/build/Plugins.html +227 -0
- package/build/Plugins.js +18 -8
- package/build/core/NodeConstructor.d.ts +9 -16
- package/build/core/NodeConstructor.d.ts.map +1 -1
- package/build/core/NodeConstructor.js +109 -146
- package/build/core/NodeGenerator.d.ts.map +1 -1
- package/build/core/NodeGenerator.js +79 -28
- 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/node/WebhookServerConfigNode.d.ts.map +1 -1
- 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 -18
- 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 +4 -10
- package/src/GenerateNodes.ts +7 -11
- package/src/core/NodeConstructor.ts +130 -186
- package/src/core/NodeGenerator.ts +88 -33
- package/src/core/NodeManagerRuntime.ts +179 -0
- package/src/core/logger/service/LoggerService.ts +39 -26
- package/src/core/logger/template/LoggerTemplate.html +11 -6
- 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.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 +1 -1
- 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/service/WebhookServerService.ts +39 -23
- package/src/core/webhook/template/WebhookTemplate.html +28 -35
- 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.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.js +0 -128
- 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,22 +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
6
|
|
|
7
|
+
import {
|
|
8
|
+
NODEMANAGER_API_VERSION,
|
|
9
|
+
POST_CONSTRUCT_KEY,
|
|
10
|
+
BaseNodeConfig,
|
|
11
|
+
runPostConstructInitializers,
|
|
12
|
+
NodeManager
|
|
13
|
+
} from "./NodeManagerRuntime";
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
+
};
|
|
20
23
|
|
|
21
24
|
export abstract class BaseNode<Config extends BaseNodeConfig> {
|
|
22
25
|
|
|
@@ -198,8 +201,6 @@ export abstract class Template{
|
|
|
198
201
|
|
|
199
202
|
type VoidMethod = (...args: any[]) => void | Promise<void>;
|
|
200
203
|
|
|
201
|
-
const POST_CONSTRUCT_KEY = Symbol('postConstructInitializers');
|
|
202
|
-
|
|
203
204
|
// Builder class for post-construct decorators
|
|
204
205
|
class PostConstructDecoratorBuilder<TMetadata = any> {
|
|
205
206
|
private decoratorName: string;
|
|
@@ -341,143 +342,6 @@ export function createPostConstructDecorator<TMetadata = any>(decoratorName: str
|
|
|
341
342
|
return new PostConstructDecoratorBuilder<TMetadata>(decoratorName);
|
|
342
343
|
}
|
|
343
344
|
|
|
344
|
-
// Single function to run all post-construct initializers
|
|
345
|
-
let runPostConstructInitializers = function(instance: any) {
|
|
346
|
-
const initializers = instance[POST_CONSTRUCT_KEY];
|
|
347
|
-
if (initializers && initializers.length > 0) {
|
|
348
|
-
const className = instance.constructor.name;
|
|
349
|
-
//console.log(`Running ${initializers.length} post-construct initializers for ${className}`);
|
|
350
|
-
|
|
351
|
-
initializers.forEach((init: Function, index: number) => {
|
|
352
|
-
try {
|
|
353
|
-
init(instance);
|
|
354
|
-
} catch (error) {
|
|
355
|
-
console.error(
|
|
356
|
-
`Post-construct initialization failed:\n` +
|
|
357
|
-
` Class: ${className}\n` +
|
|
358
|
-
` Initializer: ${index + 1}/${initializers.length}\n` +
|
|
359
|
-
` Error: ${error instanceof Error ? error.message : String(error)}`
|
|
360
|
-
);
|
|
361
|
-
throw error;
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
// Clear the initializers after running
|
|
366
|
-
delete instance[POST_CONSTRUCT_KEY];
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// ************************************************
|
|
371
|
-
|
|
372
|
-
export class NodeManager{
|
|
373
|
-
private static PLUGINID = "node-red-contrib-theotherwillembotha/services-plugin";
|
|
374
|
-
private static _RED: NodeAPI<NodeAPISettingsWithData>;
|
|
375
|
-
|
|
376
|
-
private nodeTypeService:NodeTypeService;
|
|
377
|
-
private typeBacklog:INodeClass[] = []
|
|
378
|
-
|
|
379
|
-
public constructor(RED : NodeAPI<NodeAPISettingsWithData>){
|
|
380
|
-
NodeManager._RED = RED;
|
|
381
|
-
|
|
382
|
-
let nodeTypeServiceListener = async (pluginID:String) => {
|
|
383
|
-
if(pluginID === "@theotherwillembotha/nodetypeservice"){
|
|
384
|
-
// disable the listener- the plugin has been installed.
|
|
385
|
-
RED.events.off('plugin.instantiated', nodeTypeServiceListener)
|
|
386
|
-
|
|
387
|
-
// get a handle on the NodeTypeService
|
|
388
|
-
let plugin = RED.plugins.get("@theotherwillembotha/nodetypeservice") as any;
|
|
389
|
-
this.nodeTypeService = (RED.plugins.get("@theotherwillembotha/nodetypeservice") as any).instance as NodeTypeService;
|
|
390
|
-
|
|
391
|
-
// process the backlog.
|
|
392
|
-
for(let nodeType of this.typeBacklog){
|
|
393
|
-
for(let tag of nodeType.getNodeDescriptor().tags()){
|
|
394
|
-
this.nodeTypeService.registerNodeType(tag, nodeType);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// check if the reverserproxyttypesewrvice is installed yet.
|
|
401
|
-
this.nodeTypeService = (RED.plugins.get("@theotherwillembotha/nodetypeservice") as any).instance as NodeTypeService;
|
|
402
|
-
if(!this.nodeTypeService) {
|
|
403
|
-
RED.events.on('plugin.instantiated', nodeTypeServiceListener);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
public static get RED() {
|
|
408
|
-
return NodeManager._RED;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
public registerService(type:IServiceClass):NodeManager{
|
|
412
|
-
let servicePlugin:any = NodeManager.RED.plugins.get(NodeManager.PLUGINID) as PluginDefinition<any>;
|
|
413
|
-
if(servicePlugin){
|
|
414
|
-
servicePlugin.instance.install(NodeManager.RED, type);
|
|
415
|
-
}
|
|
416
|
-
else{
|
|
417
|
-
let serviceName = `${type.getServiceDescriptor().sourceFile()}.${type.getServiceDescriptor().name()}`;
|
|
418
|
-
console.log(`ERROR: plugin ${NodeManager.PLUGINID} does not seem to be loaded. Cannot start service: ${serviceName}`);
|
|
419
|
-
}
|
|
420
|
-
return this;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
public registerNodeType(typeName: string, type: INodeClass):NodeManager{
|
|
424
|
-
if(!type){
|
|
425
|
-
console.error(`Type "${typeName}" has no associated type. Make sure that it was exported and included in the index.js file!`)
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
let nodeConstructor = function(this: any, config: BaseNodeConfig | NodeDef){
|
|
429
|
-
//console.log("constructing node", typeName);
|
|
430
|
-
|
|
431
|
-
try{
|
|
432
|
-
// instantiate the node.
|
|
433
|
-
NodeManager.RED.nodes.createNode(this, config as any as NodeDef);
|
|
434
|
-
// assign an instance of it to the config.
|
|
435
|
-
//(config as any).__node = this;
|
|
436
|
-
|
|
437
|
-
// instatiate the actual node.
|
|
438
|
-
let node = new (type as any)(this, config);
|
|
439
|
-
//this.__node = node;
|
|
440
|
-
|
|
441
|
-
// run the constructor initializers.
|
|
442
|
-
runPostConstructInitializers(node);
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
// after the node has been instantiated, attempt to invole the onInit method.
|
|
446
|
-
if(node.onInit){
|
|
447
|
-
node.onInit();
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
catch(error){
|
|
451
|
-
console.log(error);
|
|
452
|
-
console.log("typeName:", typeName)
|
|
453
|
-
console.log("type:", type)
|
|
454
|
-
console.log("config:", config)
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
try{
|
|
459
|
-
NodeManager.RED.nodes.registerType(typeName, nodeConstructor, { settings: {}});
|
|
460
|
-
|
|
461
|
-
let nodeDescription = type.getNodeDescriptor();
|
|
462
|
-
|
|
463
|
-
// registger the node tags.
|
|
464
|
-
if(this.nodeTypeService){
|
|
465
|
-
for(let tag of nodeDescription.tags()){
|
|
466
|
-
this.nodeTypeService.registerNodeType(tag, type);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
else{
|
|
470
|
-
this.typeBacklog.push(type);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
catch(error){
|
|
475
|
-
console.error("registering node: " + typeName + " failed", error);
|
|
476
|
-
}
|
|
477
|
-
return this;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
345
|
export type FlowDeployment = {
|
|
482
346
|
rev:string,
|
|
483
347
|
flows:[FlowElement]
|
|
@@ -513,25 +377,27 @@ export abstract class BaseService {
|
|
|
513
377
|
}
|
|
514
378
|
}
|
|
515
379
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
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
|
+
}
|
|
535
401
|
|
|
536
402
|
let beautifyOptions = {
|
|
537
403
|
"indent_size": 4,
|
|
@@ -591,7 +457,7 @@ export class NodeBuilder {
|
|
|
591
457
|
}
|
|
592
458
|
|
|
593
459
|
public addDefault(name:string, template:DefaultTemplateType):NodeBuilder{
|
|
594
|
-
// if list:true, this is an array of config references
|
|
460
|
+
// if list:true, this is an array of config references - generate shadow defaults for each slot.
|
|
595
461
|
if(template.list === true) {
|
|
596
462
|
template.maxInstances = template.maxInstances ? template.maxInstances : 10;
|
|
597
463
|
this._defaults[name] = {value:"", required:(template.required) ? template.required : false, maxInstances:template.maxInstances};
|
|
@@ -641,9 +507,10 @@ export class NodeBuilder {
|
|
|
641
507
|
|
|
642
508
|
public addTemplate(template: { template: ITemplateClass; config: TemplateConfig; }): NodeBuilder {
|
|
643
509
|
let templateDescriptor = template.template.getTemplateDescriptor();
|
|
510
|
+
let templateConfig = template.config;
|
|
644
511
|
|
|
645
512
|
// load the html source file
|
|
646
|
-
let uiDom = new
|
|
513
|
+
let uiDom = new (getJSDOM())(fs.readFileSync(templateDescriptor.templateFile()!));
|
|
647
514
|
|
|
648
515
|
// apply the onCompose section if available.
|
|
649
516
|
let onCompose = uiDom.window.document.querySelector("script[template-section='onCompose']")?.innerHTML;
|
|
@@ -655,7 +522,16 @@ export class NodeBuilder {
|
|
|
655
522
|
let sections:string[] = ['IncludeOnce', 'IncludeEditPrepare', 'IncludeEditSave', 'IncludeEditCancel', 'IncludeEditDelete', 'IncludeEditForm', 'IncludeDocumentation'];
|
|
656
523
|
Object.values(sections).forEach(section => {
|
|
657
524
|
let sectionData = uiDom.window.document.querySelector(`[template-section='on${section}']`)?.innerHTML;
|
|
525
|
+
|
|
658
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
|
+
}
|
|
659
535
|
(this as any)[`add${section}`](templateDescriptor.name(), sectionData);
|
|
660
536
|
}
|
|
661
537
|
});
|
|
@@ -698,6 +574,69 @@ export class NodeBuilder {
|
|
|
698
574
|
return this;
|
|
699
575
|
}
|
|
700
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
|
+
|
|
701
640
|
private serializeProperty(value:any):string{
|
|
702
641
|
switch(typeof value){
|
|
703
642
|
case "boolean": {
|
|
@@ -730,18 +669,18 @@ export class NodeBuilder {
|
|
|
730
669
|
}
|
|
731
670
|
|
|
732
671
|
return `<!-- ${source} -->
|
|
733
|
-
${
|
|
672
|
+
${getHandlebars().compile(script)(context)}
|
|
734
673
|
`
|
|
735
674
|
}
|
|
736
675
|
|
|
737
|
-
private serializeJavaScript(source:string, script:string):string{
|
|
676
|
+
private serializeJavaScript(source:string, script:string):string{
|
|
738
677
|
let context = {
|
|
739
|
-
defaults: (this._category === "config")
|
|
678
|
+
defaults: (this._category === "config")
|
|
740
679
|
? Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-config-input-${k}`]))
|
|
741
680
|
: Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-input-${k}`]))
|
|
742
681
|
}
|
|
743
682
|
|
|
744
|
-
let serialized =
|
|
683
|
+
let serialized = getHandlebars().compile(script)(context).trim();
|
|
745
684
|
if(!serialized.startsWith("{") && !serialized.endsWith("}")){
|
|
746
685
|
serialized = "{\n" + serialized + "\n}";
|
|
747
686
|
}
|
|
@@ -751,14 +690,14 @@ export class NodeBuilder {
|
|
|
751
690
|
`
|
|
752
691
|
}
|
|
753
692
|
|
|
754
|
-
private serializeFormEntry(source:string, script:string):string{
|
|
693
|
+
private serializeFormEntry(source:string, script:string):string{
|
|
755
694
|
let context = {
|
|
756
|
-
defaults: (this._category === "config")
|
|
695
|
+
defaults: (this._category === "config")
|
|
757
696
|
? Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-config-input-${k}`]))
|
|
758
697
|
: Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-input-${k}`]))
|
|
759
698
|
}
|
|
760
699
|
return `
|
|
761
|
-
<div id='section_${source}'>${
|
|
700
|
+
<div id='section_${source}'>${getHandlebars().compile(script)(context)}
|
|
762
701
|
</div>
|
|
763
702
|
`
|
|
764
703
|
}
|
|
@@ -787,7 +726,7 @@ export class NodeBuilder {
|
|
|
787
726
|
);
|
|
788
727
|
}
|
|
789
728
|
|
|
790
|
-
return
|
|
729
|
+
return getBeautify().html_beautify(`
|
|
791
730
|
${this._onIncludeOnce.map(entry => this.serializeHTML(entry.source, entry.script)).join("")}
|
|
792
731
|
<script type="text/javascript">
|
|
793
732
|
RED.nodes.registerType('${this._name}',{
|
|
@@ -821,7 +760,7 @@ export class NodeBuilder {
|
|
|
821
760
|
};
|
|
822
761
|
|
|
823
762
|
public buildHtml():string{
|
|
824
|
-
return
|
|
763
|
+
return getBeautify().html_beautify(`
|
|
825
764
|
<script type="text/html" data-template-name='${this._name}'>
|
|
826
765
|
${this._onIncludeEditForm.map(entry => this.serializeFormEntry(entry.source, entry.script)).join("")}
|
|
827
766
|
</script>
|
|
@@ -854,13 +793,13 @@ let evalInContext = (context: any, js: string) => {
|
|
|
854
793
|
}
|
|
855
794
|
};
|
|
856
795
|
|
|
857
|
-
let
|
|
796
|
+
let buildNodeBuilder = function(node: INodeClass): NodeBuilder {
|
|
858
797
|
// get the node descriptor and create the builder.
|
|
859
798
|
let nodeDescriptor = node.getNodeDescriptor();
|
|
860
799
|
let nodeBuilder = new NodeBuilder(nodeDescriptor.id(), nodeDescriptor.group());
|
|
861
800
|
|
|
862
801
|
// load the html source file
|
|
863
|
-
let uiDom = new
|
|
802
|
+
let uiDom = new (getJSDOM())(fs.readFileSync(nodeDescriptor.sourceFile()));
|
|
864
803
|
|
|
865
804
|
// apply the onCompose section if available.
|
|
866
805
|
let onCompose = uiDom.window.document.querySelector("script[template-section='onCompose']")?.innerHTML;
|
|
@@ -880,10 +819,15 @@ let buildNode = function(node:INodeClass){
|
|
|
880
819
|
// add some of the dependencies here.
|
|
881
820
|
nodeDescriptor.templates().forEach(template => nodeBuilder.addTemplate(template));
|
|
882
821
|
|
|
883
|
-
return
|
|
822
|
+
return nodeBuilder;
|
|
823
|
+
}
|
|
884
824
|
|
|
825
|
+
let buildNode = function(node:INodeClass){
|
|
826
|
+
let nodeBuilder = buildNodeBuilder(node);
|
|
827
|
+
return [nodeBuilder.buildType(), nodeBuilder.buildHtml(), nodeBuilder.buildDocumentation()].join("\n\n");
|
|
885
828
|
}
|
|
886
829
|
|
|
887
830
|
export {
|
|
888
|
-
buildNode
|
|
831
|
+
buildNode,
|
|
832
|
+
buildNodeBuilder
|
|
889
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 => {
|
|
@@ -196,29 +205,38 @@ module.exports = function (RED) {
|
|
|
196
205
|
|
|
197
206
|
if(addedPlugin && plugin.instantiate && !plugin.instance){
|
|
198
207
|
plugin.instance = new plugin.class();
|
|
199
|
-
|
|
208
|
+
|
|
200
209
|
// instantiate the plugin.
|
|
201
210
|
await plugin.instance.init(RED);
|
|
202
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
|
+
|
|
203
222
|
// publish an event that it has ben deployed.
|
|
204
223
|
RED.events.emit("plugin.instantiated", pluginID);
|
|
205
|
-
|
|
224
|
+
|
|
206
225
|
pluginList.splice(pluginList.findIndex(current => current.id() === addedPlugin.id()), 1);
|
|
207
226
|
|
|
208
227
|
// register a flow deployment listener.
|
|
209
|
-
|
|
228
|
+
// startupDeployment is only needed for a truly fresh Node-RED with no saved flows.
|
|
229
|
+
let startupDeployment = !hasExistingFlows;
|
|
210
230
|
RED.events.on('runtime-event', async (event) => {
|
|
211
|
-
// startup deployment.
|
|
231
|
+
// startup deployment (fresh Node-RED with no pre-existing flows).
|
|
212
232
|
if ("runtime-deploy" === event?.id && startupDeployment) {
|
|
213
|
-
//console.log("STARTUP DEPLOYMENT");
|
|
214
233
|
startupDeployment = false;
|
|
215
234
|
const flows = await runtime.flows.getFlows({});
|
|
216
235
|
await plugin.instance.onDeploy(flows);
|
|
217
236
|
}
|
|
218
|
-
|
|
237
|
+
|
|
219
238
|
// stopping flows on redeployment.
|
|
220
239
|
if ("runtime-state" === event?.id && event?.payload?.state === "stop" && event?.payload?.deploy) {
|
|
221
|
-
//console.log("REDEPLOY!");
|
|
222
240
|
const flows = await runtime.flows.getFlows({});
|
|
223
241
|
await plugin.instance.onDeploy(flows);
|
|
224
242
|
}
|
|
@@ -239,6 +257,43 @@ module.exports = function (RED) {
|
|
|
239
257
|
`
|
|
240
258
|
fs.writeFileSync(pluginsOutputFile + ".js", output);
|
|
241
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`);
|
|
242
297
|
}
|
|
243
298
|
|
|
244
299
|
private static generateHeader(node:NodeDescriptor){
|