@theotherwillembotha/node-red-plugincore 0.0.50
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 +190 -0
- package/build/GenerateNodes.d.ts +2 -0
- package/build/GenerateNodes.d.ts.map +1 -0
- package/build/GenerateNodes.js +36 -0
- package/build/Nodes.html +1729 -0
- package/build/Nodes.js +32 -0
- package/build/Plugins.js +74 -0
- package/build/core/NodeConstructor.d.ts +188 -0
- package/build/core/NodeConstructor.d.ts.map +1 -0
- package/build/core/NodeConstructor.js +679 -0
- package/build/core/NodeGenerator.d.ts +37 -0
- package/build/core/NodeGenerator.d.ts.map +1 -0
- package/build/core/NodeGenerator.js +300 -0
- package/build/core/Utils.d.ts +7 -0
- package/build/core/Utils.d.ts.map +1 -0
- package/build/core/Utils.js +19 -0
- package/build/core/logger/LoggerDecorator.d.ts +2 -0
- package/build/core/logger/LoggerDecorator.d.ts.map +1 -0
- package/build/core/logger/LoggerDecorator.js +93 -0
- package/build/core/logger/node/ConsoleLoggerConfigNode.d.ts +16 -0
- package/build/core/logger/node/ConsoleLoggerConfigNode.d.ts.map +1 -0
- package/build/core/logger/node/ConsoleLoggerConfigNode.html +131 -0
- package/build/core/logger/node/ConsoleLoggerConfigNode.js +47 -0
- package/build/core/logger/node/LoggerConfigNode.d.ts +30 -0
- package/build/core/logger/node/LoggerConfigNode.d.ts.map +1 -0
- package/build/core/logger/node/LoggerConfigNode.html +307 -0
- package/build/core/logger/node/LoggerConfigNode.js +60 -0
- package/build/core/logger/node/LokiLoggerConfigNode.d.ts +20 -0
- package/build/core/logger/node/LokiLoggerConfigNode.d.ts.map +1 -0
- package/build/core/logger/node/LokiLoggerConfigNode.html +153 -0
- package/build/core/logger/node/LokiLoggerConfigNode.js +52 -0
- package/build/core/logger/node/RestLoggerConfigNode.d.ts +24 -0
- package/build/core/logger/node/RestLoggerConfigNode.d.ts.map +1 -0
- package/build/core/logger/node/RestLoggerConfigNode.html +209 -0
- package/build/core/logger/node/RestLoggerConfigNode.js +57 -0
- package/build/core/logger/service/LoggerService.d.ts +88 -0
- package/build/core/logger/service/LoggerService.d.ts.map +1 -0
- package/build/core/logger/service/LoggerService.js +204 -0
- package/build/core/logger/service/LoggerServiceTypes.d.ts +24 -0
- package/build/core/logger/service/LoggerServiceTypes.d.ts.map +1 -0
- package/build/core/logger/service/LoggerServiceTypes.js +30 -0
- package/build/core/logger/template/LoggerTemplate.d.ts +11 -0
- package/build/core/logger/template/LoggerTemplate.d.ts.map +1 -0
- package/build/core/logger/template/LoggerTemplate.html +328 -0
- package/build/core/logger/template/LoggerTemplate.js +12 -0
- package/build/core/metrics/MetricsDecorator.d.ts +40 -0
- package/build/core/metrics/MetricsDecorator.d.ts.map +1 -0
- package/build/core/metrics/MetricsDecorator.js +166 -0
- package/build/core/metrics/node/CounterMetricConfigNode.d.ts +16 -0
- package/build/core/metrics/node/CounterMetricConfigNode.d.ts.map +1 -0
- package/build/core/metrics/node/CounterMetricConfigNode.html +48 -0
- package/build/core/metrics/node/CounterMetricConfigNode.js +61 -0
- package/build/core/metrics/node/GaugeMetricConfigNode.d.ts +17 -0
- package/build/core/metrics/node/GaugeMetricConfigNode.d.ts.map +1 -0
- package/build/core/metrics/node/GaugeMetricConfigNode.html +48 -0
- package/build/core/metrics/node/GaugeMetricConfigNode.js +61 -0
- package/build/core/metrics/node/MetricsConfigNode.d.ts +14 -0
- package/build/core/metrics/node/MetricsConfigNode.d.ts.map +1 -0
- package/build/core/metrics/node/MetricsConfigNode.html +21 -0
- package/build/core/metrics/node/MetricsConfigNode.js +56 -0
- package/build/core/metrics/node/TimerMetricConfigNode.d.ts +28 -0
- package/build/core/metrics/node/TimerMetricConfigNode.d.ts.map +1 -0
- package/build/core/metrics/node/TimerMetricConfigNode.html +190 -0
- package/build/core/metrics/node/TimerMetricConfigNode.js +101 -0
- package/build/core/metrics/service/MetricsService.d.ts +187 -0
- package/build/core/metrics/service/MetricsService.d.ts.map +1 -0
- package/build/core/metrics/service/MetricsService.js +385 -0
- package/build/core/metrics/template/CounterMetricTemplate.d.ts +5 -0
- package/build/core/metrics/template/CounterMetricTemplate.d.ts.map +1 -0
- package/build/core/metrics/template/CounterMetricTemplate.html +40 -0
- package/build/core/metrics/template/CounterMetricTemplate.js +12 -0
- package/build/core/metrics/template/GaugeMetricTemplate.d.ts +5 -0
- package/build/core/metrics/template/GaugeMetricTemplate.d.ts.map +1 -0
- package/build/core/metrics/template/GaugeMetricTemplate.html +39 -0
- package/build/core/metrics/template/GaugeMetricTemplate.js +12 -0
- package/build/core/metrics/template/MetricsTemplate.d.ts +8 -0
- package/build/core/metrics/template/MetricsTemplate.d.ts.map +1 -0
- package/build/core/metrics/template/MetricsTemplate.html +54 -0
- package/build/core/metrics/template/MetricsTemplate.js +12 -0
- package/build/core/metrics/template/TimerMetricTemplate.d.ts +5 -0
- package/build/core/metrics/template/TimerMetricTemplate.d.ts.map +1 -0
- package/build/core/metrics/template/TimerMetricTemplate.html +39 -0
- package/build/core/metrics/template/TimerMetricTemplate.js +12 -0
- package/build/core/other/InputDecorator.d.ts +3 -0
- package/build/core/other/InputDecorator.d.ts.map +1 -0
- package/build/core/other/InputDecorator.js +43 -0
- package/build/core/other/service/InputService.d.ts +16 -0
- package/build/core/other/service/InputService.d.ts.map +1 -0
- package/build/core/other/service/InputService.js +31 -0
- package/build/core/other/service/SettingsService.d.ts +25 -0
- package/build/core/other/service/SettingsService.d.ts.map +1 -0
- package/build/core/other/service/SettingsService.js +74 -0
- package/build/core/other/template/BasicTemplate.d.ts +5 -0
- package/build/core/other/template/BasicTemplate.d.ts.map +1 -0
- package/build/core/other/template/BasicTemplate.html +76 -0
- package/build/core/other/template/BasicTemplate.js +11 -0
- package/build/core/other/template/SettingsTemplate.d.ts +5 -0
- package/build/core/other/template/SettingsTemplate.d.ts.map +1 -0
- package/build/core/other/template/SettingsTemplate.html +128 -0
- package/build/core/other/template/SettingsTemplate.js +11 -0
- package/build/core/tagging/NodeDescriptionDecorator.d.ts +17 -0
- package/build/core/tagging/NodeDescriptionDecorator.d.ts.map +1 -0
- package/build/core/tagging/NodeDescriptionDecorator.js +25 -0
- package/build/core/tagging/service/NodeTypeService.d.ts +15 -0
- package/build/core/tagging/service/NodeTypeService.d.ts.map +1 -0
- package/build/core/tagging/service/NodeTypeService.js +47 -0
- package/build/core/webhook/WebhookDecorator.d.ts +3 -0
- package/build/core/webhook/WebhookDecorator.d.ts.map +1 -0
- package/build/core/webhook/WebhookDecorator.js +45 -0
- package/build/core/webhook/node/WebhookServerConfigNode.d.ts +16 -0
- package/build/core/webhook/node/WebhookServerConfigNode.d.ts.map +1 -0
- package/build/core/webhook/node/WebhookServerConfigNode.html +61 -0
- package/build/core/webhook/node/WebhookServerConfigNode.js +40 -0
- package/build/core/webhook/service/ReverseProxyTypeService.d.ts +29 -0
- package/build/core/webhook/service/ReverseProxyTypeService.d.ts.map +1 -0
- package/build/core/webhook/service/ReverseProxyTypeService.js +24 -0
- package/build/core/webhook/service/WebhookServerService.d.ts +52 -0
- package/build/core/webhook/service/WebhookServerService.d.ts.map +1 -0
- package/build/core/webhook/service/WebhookServerService.js +146 -0
- package/build/core/webhook/template/WebhookTemplate.d.ts +22 -0
- package/build/core/webhook/template/WebhookTemplate.d.ts.map +1 -0
- package/build/core/webhook/template/WebhookTemplate.html +434 -0
- package/build/core/webhook/template/WebhookTemplate.js +12 -0
- package/build/index.d.ts +33 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +55 -0
- package/package.json +67 -0
- package/src/GenerateNodes.ts +42 -0
- package/src/core/NodeConstructor.ts +889 -0
- package/src/core/NodeGenerator.ts +331 -0
- package/src/core/Utils.ts +19 -0
- package/src/core/logger/LoggerDecorator.ts +105 -0
- package/src/core/logger/node/ConsoleLoggerConfigNode.html +131 -0
- package/src/core/logger/node/ConsoleLoggerConfigNode.ts +46 -0
- package/src/core/logger/node/LoggerConfigNode.html +307 -0
- package/src/core/logger/node/LoggerConfigNode.ts +95 -0
- package/src/core/logger/node/LokiLoggerConfigNode.html +153 -0
- package/src/core/logger/node/LokiLoggerConfigNode.ts +58 -0
- package/src/core/logger/node/RestLoggerConfigNode.html +209 -0
- package/src/core/logger/node/RestLoggerConfigNode.ts +67 -0
- package/src/core/logger/service/LoggerService.ts +317 -0
- package/src/core/logger/service/LoggerServiceTypes.ts +26 -0
- package/src/core/logger/template/LoggerTemplate.html +328 -0
- package/src/core/logger/template/LoggerTemplate.ts +23 -0
- package/src/core/metrics/MetricsDecorator.ts +227 -0
- package/src/core/metrics/node/CounterMetricConfigNode.html +48 -0
- package/src/core/metrics/node/CounterMetricConfigNode.ts +62 -0
- package/src/core/metrics/node/GaugeMetricConfigNode.html +48 -0
- package/src/core/metrics/node/GaugeMetricConfigNode.ts +62 -0
- package/src/core/metrics/node/MetricsConfigNode.html +21 -0
- package/src/core/metrics/node/MetricsConfigNode.ts +55 -0
- package/src/core/metrics/node/TimerMetricConfigNode.html +190 -0
- package/src/core/metrics/node/TimerMetricConfigNode.ts +118 -0
- package/src/core/metrics/service/MetricsService.ts +560 -0
- package/src/core/metrics/template/CounterMetricTemplate.html +40 -0
- package/src/core/metrics/template/CounterMetricTemplate.ts +15 -0
- package/src/core/metrics/template/GaugeMetricTemplate.html +39 -0
- package/src/core/metrics/template/GaugeMetricTemplate.ts +15 -0
- package/src/core/metrics/template/MetricsTemplate.html +54 -0
- package/src/core/metrics/template/MetricsTemplate.ts +18 -0
- package/src/core/metrics/template/TimerMetricTemplate.html +39 -0
- package/src/core/metrics/template/TimerMetricTemplate.ts +14 -0
- package/src/core/other/InputDecorator.ts +57 -0
- package/src/core/other/service/InputService.ts +36 -0
- package/src/core/other/service/SettingsService.ts +103 -0
- package/src/core/other/template/BasicTemplate.html +76 -0
- package/src/core/other/template/BasicTemplate.ts +15 -0
- package/src/core/other/template/SettingsTemplate.html +128 -0
- package/src/core/other/template/SettingsTemplate.ts +17 -0
- package/src/core/tagging/NodeDescriptionDecorator.ts +39 -0
- package/src/core/tagging/service/NodeTypeService.ts +67 -0
- package/src/core/webhook/WebhookDecorator.ts +70 -0
- package/src/core/webhook/node/WebhookServerConfigNode.html +61 -0
- package/src/core/webhook/node/WebhookServerConfigNode.ts +38 -0
- package/src/core/webhook/service/ReverseProxyTypeService.ts +46 -0
- package/src/core/webhook/service/WebhookServerService.ts +211 -0
- package/src/core/webhook/template/WebhookTemplate.html +434 -0
- package/src/core/webhook/template/WebhookTemplate.ts +36 -0
- package/src/index.ts +47 -0
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
import { PluginDefinition } from "@node-red/registry";
|
|
2
|
+
import { Node, NodeAPI, NodeDef, NodeAPISettingsWithData} from "node-red";
|
|
3
|
+
import { INamedType, INodeClass, ITemplateClass } from "./NodeGenerator";
|
|
4
|
+
|
|
5
|
+
import "reflect-metadata";
|
|
6
|
+
import jsdom from "jsdom";
|
|
7
|
+
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
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export interface BaseNodeConfig {
|
|
17
|
+
id:string;
|
|
18
|
+
name:string;
|
|
19
|
+
type:string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export abstract class BaseNode<Config extends BaseNodeConfig> {
|
|
23
|
+
|
|
24
|
+
private _node:Node;
|
|
25
|
+
public node() { return this._node; }
|
|
26
|
+
|
|
27
|
+
private _config:Config;
|
|
28
|
+
public config() { return this._config; }
|
|
29
|
+
|
|
30
|
+
private _flow:string
|
|
31
|
+
public flow() { return this._flow; }
|
|
32
|
+
|
|
33
|
+
// THIS value is imported from NodeRed Node itself.
|
|
34
|
+
public id() { return this.config().id };
|
|
35
|
+
public type() { return this.config().type };
|
|
36
|
+
public name() { return this.config().name };
|
|
37
|
+
|
|
38
|
+
protected constructor(node: Node, config: Config){
|
|
39
|
+
this._node = node;
|
|
40
|
+
this._config = config;
|
|
41
|
+
this._flow = (this._node._flow?.flow as any).label;
|
|
42
|
+
|
|
43
|
+
// inject a reverse lookup so the NodeRedNode can refer back to the waos node:
|
|
44
|
+
(this._node as any).node = () => { return this; }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// this function can be overwritten to be triggered after the constructor and
|
|
48
|
+
// decorators have been called.
|
|
49
|
+
protected onInit(){}
|
|
50
|
+
|
|
51
|
+
// note: this function should be implemented by every concrete implementation of the BaseNode class.
|
|
52
|
+
public static getNodeDescriptor():NodeDescriptor{
|
|
53
|
+
throw new Error("getNodeDescriptor not implemented.");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ConfigNodeConfig extends BaseNodeConfig {
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export abstract class ConfigNode<Config extends ConfigNodeConfig> extends BaseNode<Config> {
|
|
61
|
+
|
|
62
|
+
protected constructor(node:Node, config: Config){
|
|
63
|
+
super(node, config);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public static getNodeDescriptor():NodeDescriptor{
|
|
67
|
+
throw new Error("getNodeDescriptor not implemented.");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface Component{
|
|
72
|
+
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface TemplateConfig {
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type NodeDependencyType = INamedType & (INodeClass | IServiceClass);
|
|
79
|
+
|
|
80
|
+
export class NodeDescriptor {
|
|
81
|
+
private _group:string;
|
|
82
|
+
private _name:string;
|
|
83
|
+
private _sourceFile: string;
|
|
84
|
+
private _dependencies: DependencyType[] = []
|
|
85
|
+
private _templates:{template:ITemplateClass, config:TemplateConfig}[] = [];
|
|
86
|
+
private _package: string;
|
|
87
|
+
private _tags: string[] = [];
|
|
88
|
+
|
|
89
|
+
constructor(group:string, name:string, sourceFile:string, pkg:string){
|
|
90
|
+
this._group = group;
|
|
91
|
+
this._name = name;
|
|
92
|
+
this._sourceFile = sourceFile;
|
|
93
|
+
this._package = pkg;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// DEPRECATED
|
|
97
|
+
public addDependency(dependency:NodeDependencyType):NodeDescriptor{
|
|
98
|
+
this._dependencies.push(dependency);
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// DEPRECATED
|
|
103
|
+
public addTag(tag:string):NodeDescriptor{
|
|
104
|
+
this._tags.push(tag);
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// DEPRECATED
|
|
109
|
+
public addTemplate(template:ITemplateClass, config:TemplateConfig):NodeDescriptor{
|
|
110
|
+
// TODO: we can only add every template once.
|
|
111
|
+
this._dependencies.push(template);
|
|
112
|
+
this._templates.push({template:template, config:config});
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public group(){ return this._group; }
|
|
117
|
+
public name(){ return this._name; }
|
|
118
|
+
public sourceFile() { return this._sourceFile; }
|
|
119
|
+
public package() { return this._package; }
|
|
120
|
+
public dependencies():DependencyType[] { return this._dependencies; }
|
|
121
|
+
public templates():{template:ITemplateClass, config:TemplateConfig}[]{ return this._templates; }
|
|
122
|
+
public tags():string[] { return this._tags; }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface IServiceClass {
|
|
126
|
+
new (): BaseService;
|
|
127
|
+
getServiceDescriptor():ServiceDescriptor;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export class TemplateDescriptor{
|
|
131
|
+
private _name: string;
|
|
132
|
+
private _dependencies: DependencyType[];
|
|
133
|
+
private _clazz: ITemplateClass;
|
|
134
|
+
private _templateFile: string;
|
|
135
|
+
|
|
136
|
+
constructor(name:string, clazz:ITemplateClass, templateFile:string, dependencies:DependencyType[]){
|
|
137
|
+
this._name = name;
|
|
138
|
+
this._clazz = clazz;
|
|
139
|
+
this._templateFile = templateFile;
|
|
140
|
+
this._dependencies = dependencies;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
name(): string {
|
|
144
|
+
return this._name;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
clazz(): ITemplateClass {
|
|
148
|
+
return this._clazz;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
dependencies():DependencyType[]{
|
|
152
|
+
return this._dependencies;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
templateFile():string {
|
|
156
|
+
return this._templateFile;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export class ServiceDescriptor {
|
|
162
|
+
|
|
163
|
+
private _id: string;
|
|
164
|
+
private _name:string;
|
|
165
|
+
private _type: string;
|
|
166
|
+
private _sourceFile:string;
|
|
167
|
+
private _clazz:IServiceClass;
|
|
168
|
+
private _dependencies:DependencyType[];
|
|
169
|
+
|
|
170
|
+
public constructor(id:string, name:string, type:string, sourceFile:string, clazz:IServiceClass, dependencies?:DependencyType[]){
|
|
171
|
+
this._id = id;
|
|
172
|
+
this._name = name;
|
|
173
|
+
this._type = type;
|
|
174
|
+
this._sourceFile = sourceFile;
|
|
175
|
+
this._clazz = clazz;
|
|
176
|
+
this._dependencies = dependencies ? dependencies : [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public id():string { return this._id };
|
|
180
|
+
public name():string { return this._name };
|
|
181
|
+
public type():string { return this._type };
|
|
182
|
+
public sourceFile():string { return this._sourceFile };
|
|
183
|
+
public clazz():IServiceClass { return this._clazz };
|
|
184
|
+
public dependencies() { return this._dependencies };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export type DependencyType = INamedType & (ITemplateClass | INodeClass | IServiceClass);
|
|
188
|
+
|
|
189
|
+
export abstract class Template{
|
|
190
|
+
public static getTemplateDescriptor():TemplateDescriptor {
|
|
191
|
+
throw new Error("getTemplateDescriptor not implemented for " + this.name);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// POST CONSTRUCT INITALIZERS **********************
|
|
196
|
+
|
|
197
|
+
type VoidMethod = (...args: any[]) => void | Promise<void>;
|
|
198
|
+
|
|
199
|
+
const POST_CONSTRUCT_KEY = Symbol('postConstructInitializers');
|
|
200
|
+
|
|
201
|
+
// Builder class for post-construct decorators
|
|
202
|
+
class PostConstructDecoratorBuilder<TMetadata = any> {
|
|
203
|
+
private decoratorName: string;
|
|
204
|
+
private initLogic?: (instance: any, propertyKey: string, metadata: any) => void;
|
|
205
|
+
private validator?: (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
206
|
+
private defaultConfig?: TMetadata;
|
|
207
|
+
|
|
208
|
+
constructor(decoratorName: string) {
|
|
209
|
+
this.decoratorName = decoratorName;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Set default config for when brackets are omitted
|
|
213
|
+
withDefaultConfig(config: TMetadata) {
|
|
214
|
+
this.defaultConfig = config;
|
|
215
|
+
return this;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Method to set initialization logic
|
|
219
|
+
withInitLogic(initLogic: (instance: any, propertyKey: string, metadata: any) => void) {
|
|
220
|
+
this.initLogic = initLogic;
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Method to set custom validation logic
|
|
225
|
+
withValidator(validator: (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void) {
|
|
226
|
+
this.validator = validator;
|
|
227
|
+
return this;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Build the actual decorator factory (returns a function that takes metadata and returns a decorator)
|
|
231
|
+
build() {
|
|
232
|
+
const decoratorName = this.decoratorName;
|
|
233
|
+
const initLogic = this.initLogic;
|
|
234
|
+
const validator = this.validator;
|
|
235
|
+
const defaultConfig = this.defaultConfig;
|
|
236
|
+
|
|
237
|
+
// The actual decorator implementation
|
|
238
|
+
const decoratorImpl = (config: TMetadata) => {
|
|
239
|
+
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
|
240
|
+
try{
|
|
241
|
+
const className = target.constructor.name;
|
|
242
|
+
const originalMethod = descriptor.value;
|
|
243
|
+
|
|
244
|
+
if (typeof originalMethod !== 'function') {
|
|
245
|
+
throw new Error(
|
|
246
|
+
`@${decoratorName} decorator error:\n` +
|
|
247
|
+
` Class: ${className}\n` +
|
|
248
|
+
` Property: ${propertyKey}\n` +
|
|
249
|
+
` Issue: Decorator can only be applied to methods.`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const returnType = Reflect.getMetadata?.('design:returntype', target, propertyKey);
|
|
254
|
+
if (returnType && returnType !== Promise && returnType !== void 0 && returnType !== undefined) {
|
|
255
|
+
console.warn(
|
|
256
|
+
`@${decoratorName} decorator warning:\n` +
|
|
257
|
+
` Class: ${className}\n` +
|
|
258
|
+
` Method: ${propertyKey}\n` +
|
|
259
|
+
` Issue: Method should return void or Promise<void>, but appears to return ${returnType.name}.`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (validator) {
|
|
264
|
+
try {
|
|
265
|
+
validator(target, propertyKey, descriptor);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`@${decoratorName} decorator validation error:\n` +
|
|
269
|
+
` Class: ${className}\n` +
|
|
270
|
+
` Method: ${propertyKey}\n` +
|
|
271
|
+
` Issue: ${error instanceof Error ? error.message : String(error)}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const wrappedMethod = async function(this: any, ...args: any[]): Promise<void> {
|
|
277
|
+
const result = await originalMethod.apply(this, args);
|
|
278
|
+
if (result !== undefined && result !== null) {
|
|
279
|
+
console.warn(
|
|
280
|
+
`@${decoratorName} runtime warning:\n` +
|
|
281
|
+
` Class: ${className}\n` +
|
|
282
|
+
` Method: ${propertyKey}\n` +
|
|
283
|
+
` Issue: Method returned a value (${typeof result}) but should return void or Promise<void>.`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
if (!target[POST_CONSTRUCT_KEY]) {
|
|
289
|
+
target[POST_CONSTRUCT_KEY] = [];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
target[POST_CONSTRUCT_KEY].push((instance: any) => {
|
|
293
|
+
if (initLogic) {
|
|
294
|
+
try {
|
|
295
|
+
initLogic(instance, propertyKey, config);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
`@${decoratorName} initialization error:\n` +
|
|
299
|
+
` Class: ${className}\n` +
|
|
300
|
+
` Method: ${propertyKey}\n` +
|
|
301
|
+
` Issue: ${error instanceof Error ? error.message : String(error)}`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
descriptor.value = wrappedMethod;
|
|
308
|
+
return descriptor;
|
|
309
|
+
}
|
|
310
|
+
catch(e){
|
|
311
|
+
console.error("failed creating DecoratorImplementation:", "target:", target,"propertyKey:", propertyKey, "descriptor:", descriptor, "error:", e);
|
|
312
|
+
throw e;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// Return overloaded decorator that works with or without ()
|
|
318
|
+
const overloadedDecorator: any = function(
|
|
319
|
+
targetOrConfig: any,
|
|
320
|
+
propertyKey?: string,
|
|
321
|
+
descriptor?: PropertyDescriptor
|
|
322
|
+
) {
|
|
323
|
+
// Check if called without brackets (directly on method)
|
|
324
|
+
if (propertyKey !== undefined && descriptor !== undefined) {
|
|
325
|
+
// Called as @decorator (without brackets)
|
|
326
|
+
return decoratorImpl(defaultConfig as TMetadata)(targetOrConfig, propertyKey, descriptor);
|
|
327
|
+
} else {
|
|
328
|
+
// Called as @decorator() or @decorator({...})
|
|
329
|
+
return decoratorImpl(targetOrConfig as TMetadata);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
return overloadedDecorator;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Factory function to create decorator builders
|
|
338
|
+
export function createPostConstructDecorator<TMetadata = any>(decoratorName: string) {
|
|
339
|
+
return new PostConstructDecoratorBuilder<TMetadata>(decoratorName);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Single function to run all post-construct initializers
|
|
343
|
+
let runPostConstructInitializers = function(instance: any) {
|
|
344
|
+
const initializers = instance[POST_CONSTRUCT_KEY];
|
|
345
|
+
if (initializers && initializers.length > 0) {
|
|
346
|
+
const className = instance.constructor.name;
|
|
347
|
+
//console.log(`Running ${initializers.length} post-construct initializers for ${className}`);
|
|
348
|
+
|
|
349
|
+
initializers.forEach((init: Function, index: number) => {
|
|
350
|
+
try {
|
|
351
|
+
init(instance);
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error(
|
|
354
|
+
`Post-construct initialization failed:\n` +
|
|
355
|
+
` Class: ${className}\n` +
|
|
356
|
+
` Initializer: ${index + 1}/${initializers.length}\n` +
|
|
357
|
+
` Error: ${error instanceof Error ? error.message : String(error)}`
|
|
358
|
+
);
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Clear the initializers after running
|
|
364
|
+
delete instance[POST_CONSTRUCT_KEY];
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ************************************************
|
|
369
|
+
|
|
370
|
+
export class NodeManager{
|
|
371
|
+
private static PLUGINID = "node-red-contrib-theotherwillembotha/services-plugin";
|
|
372
|
+
private static _RED: NodeAPI<NodeAPISettingsWithData>;
|
|
373
|
+
|
|
374
|
+
private nodeTypeService:NodeTypeService;
|
|
375
|
+
private typeBacklog:INodeClass[] = []
|
|
376
|
+
|
|
377
|
+
public constructor(RED : NodeAPI<NodeAPISettingsWithData>){
|
|
378
|
+
NodeManager._RED = RED;
|
|
379
|
+
|
|
380
|
+
let nodeTypeServiceListener = async (pluginID:String) => {
|
|
381
|
+
if(pluginID === "@theotherwillembotha/nodetypeservice"){
|
|
382
|
+
// disable the listener- the plugin has been installed.
|
|
383
|
+
RED.events.off('plugin.instantiated', nodeTypeServiceListener)
|
|
384
|
+
|
|
385
|
+
// get a handle on the NodeTypeService
|
|
386
|
+
let plugin = RED.plugins.get("@theotherwillembotha/nodetypeservice") as any;
|
|
387
|
+
this.nodeTypeService = (RED.plugins.get("@theotherwillembotha/nodetypeservice") as any).instance as NodeTypeService;
|
|
388
|
+
|
|
389
|
+
// process the backlog.
|
|
390
|
+
for(let nodeType of this.typeBacklog){
|
|
391
|
+
for(let tag of nodeType.getNodeDescriptor().tags()){
|
|
392
|
+
this.nodeTypeService.registerNodeType(tag, nodeType);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// check if the reverserproxyttypesewrvice is installed yet.
|
|
399
|
+
this.nodeTypeService = (RED.plugins.get("@theotherwillembotha/nodetypeservice") as any).instance as NodeTypeService;
|
|
400
|
+
if(this.nodeTypeService) {
|
|
401
|
+
console.log("NodeTypeService installed!")
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
// Register a listener to trigger when the RegerseProxyTypService gets installed.
|
|
405
|
+
console.log("nodetypeservice not installed yet. waiting for it to be installed");
|
|
406
|
+
RED.events.on('plugin.instantiated', nodeTypeServiceListener);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
public static get RED() {
|
|
411
|
+
return NodeManager._RED;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
public registerService(type:IServiceClass):NodeManager{
|
|
415
|
+
let servicePlugin:any = NodeManager.RED.plugins.get(NodeManager.PLUGINID) as PluginDefinition<any>;
|
|
416
|
+
if(servicePlugin){
|
|
417
|
+
servicePlugin.instance.install(NodeManager.RED, type);
|
|
418
|
+
}
|
|
419
|
+
else{
|
|
420
|
+
let serviceName = `${type.getServiceDescriptor().sourceFile()}.${type.getServiceDescriptor().name()}`;
|
|
421
|
+
console.log(`ERROR: plugin ${NodeManager.PLUGINID} does not seem to be loaded. Cannot start service: ${serviceName}`);
|
|
422
|
+
}
|
|
423
|
+
return this;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
public registerNodeType(typeName: string, type: INodeClass):NodeManager{
|
|
427
|
+
if(!type){
|
|
428
|
+
console.error(`Type "${typeName}" has no associated type. Make sure that it was exported and included in the index.js file!`)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
let nodeConstructor = function(this: any, config: BaseNodeConfig | NodeDef){
|
|
432
|
+
console.log("constructing node", typeName);
|
|
433
|
+
|
|
434
|
+
try{
|
|
435
|
+
// instantiate the node.
|
|
436
|
+
NodeManager.RED.nodes.createNode(this, config as any as NodeDef);
|
|
437
|
+
// assign an instance of it to the config.
|
|
438
|
+
//(config as any).__node = this;
|
|
439
|
+
|
|
440
|
+
// instatiate the actual node.
|
|
441
|
+
let node = new (type as any)(this, config);
|
|
442
|
+
//this.__node = node;
|
|
443
|
+
|
|
444
|
+
// run the constructor initializers.
|
|
445
|
+
runPostConstructInitializers(node);
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
// after the node has been instantiated, attempt to invole the onInit method.
|
|
449
|
+
if(node.onInit){
|
|
450
|
+
node.onInit();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
catch(error){
|
|
454
|
+
console.log(error);
|
|
455
|
+
console.log("typeName:", typeName)
|
|
456
|
+
console.log("type:", type)
|
|
457
|
+
console.log("config:", config)
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
try{
|
|
462
|
+
console.log("registering node " + typeName);
|
|
463
|
+
NodeManager.RED.nodes.registerType(typeName, nodeConstructor, { settings: {}});
|
|
464
|
+
|
|
465
|
+
let nodeDescription = type.getNodeDescriptor();
|
|
466
|
+
|
|
467
|
+
// registger the node tags.
|
|
468
|
+
if(this.nodeTypeService){
|
|
469
|
+
for(let tag of nodeDescription.tags()){
|
|
470
|
+
this.nodeTypeService.registerNodeType(tag, type);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
else{
|
|
474
|
+
this.typeBacklog.push(type);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
console.log("registered node " + typeName);
|
|
478
|
+
}
|
|
479
|
+
catch(error){
|
|
480
|
+
console.error("registering node: " + typeName + " failed", error);
|
|
481
|
+
}
|
|
482
|
+
return this;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export type FlowDeployment = {
|
|
487
|
+
rev:string,
|
|
488
|
+
flows:[FlowElement]
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export type FlowElement = {
|
|
492
|
+
id:string;
|
|
493
|
+
type:string;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
export abstract class BaseService {
|
|
498
|
+
|
|
499
|
+
private _name: string;
|
|
500
|
+
|
|
501
|
+
constructor(name:string){
|
|
502
|
+
this._name = name;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
public name():string {
|
|
506
|
+
return this._name;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
public abstract init(red:NodeAPI<NodeAPISettingsWithData>):Promise<void>|void
|
|
510
|
+
public abstract deinit(red:NodeAPI<NodeAPISettingsWithData>):Promise<void>|void
|
|
511
|
+
|
|
512
|
+
public onDeploy(flowDeployment: FlowDeployment):Promise<void>|void{
|
|
513
|
+
return Promise.resolve();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
public static getServiceDescriptor():ServiceDescriptor {
|
|
517
|
+
throw new Error("getServiceDescriptor not implemented.");
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const { JSDOM } = jsdom;
|
|
522
|
+
const md = markdownit({
|
|
523
|
+
html: true,
|
|
524
|
+
linkify: true,
|
|
525
|
+
typographer: true
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
// Custom renderer for headings
|
|
529
|
+
md.renderer.rules.heading_open = function (tokens, idx, options, env, self) {
|
|
530
|
+
const token = tokens[idx];
|
|
531
|
+
const level = token.tag.substring(1); // 'h1', 'h2', etc. -> '1', '2'
|
|
532
|
+
return `<h${level} class="custom-heading">`;
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
md.renderer.rules.heading_close = function (tokens, idx, options, env, self) {
|
|
536
|
+
const token = tokens[idx];
|
|
537
|
+
const level = token.tag.substring(1);
|
|
538
|
+
return `</h${level}>`;
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
let beautifyOptions = {
|
|
542
|
+
"indent_size": 4,
|
|
543
|
+
"preserve-newlines": false,
|
|
544
|
+
"html": {
|
|
545
|
+
"end_with_newline": true,
|
|
546
|
+
"js": {
|
|
547
|
+
"indent_size": 2,
|
|
548
|
+
"preserve-newlines": false
|
|
549
|
+
},
|
|
550
|
+
"css": {
|
|
551
|
+
"indent_size": 2,
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
"css": {
|
|
555
|
+
"indent_size": 1
|
|
556
|
+
},
|
|
557
|
+
"js": {
|
|
558
|
+
"preserve-newlines": false
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
export type DefaultTemplateType = {
|
|
563
|
+
type?:string;
|
|
564
|
+
value?:string;
|
|
565
|
+
required:boolean;
|
|
566
|
+
minInstances?:number;
|
|
567
|
+
maxInstances?:number;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export class NodeBuilder {
|
|
571
|
+
// see https://nodered.org/docs/creating-nodes/node-html#node-definition
|
|
572
|
+
private _name: string;
|
|
573
|
+
private _category: string;
|
|
574
|
+
private _defaults:{[name:string]:DefaultTemplateType} = {};
|
|
575
|
+
private _credentials?: string; // TODO
|
|
576
|
+
private _color?: string;
|
|
577
|
+
private _label?: string;
|
|
578
|
+
private _icon?: string;
|
|
579
|
+
private _paletteLabel?: string;
|
|
580
|
+
private _labelStyle?: string;
|
|
581
|
+
private _inputs?:string;
|
|
582
|
+
private _outputs:string[] = [];
|
|
583
|
+
private _onIncludeOnce:{source:string, script:string}[] = [];
|
|
584
|
+
private _onIncludeEditPrepare:{source:string, script:string}[] = [];
|
|
585
|
+
private _onIncludeEditSave:{source:string, script:string}[] = [];
|
|
586
|
+
private _onIncludeEditCancel:{source:string, script:string}[] = [];
|
|
587
|
+
private _onIncludeEditDelete:{source:string, script:string}[] = [];
|
|
588
|
+
private _onIncludeEditForm:{source:string, script:string}[] = [];
|
|
589
|
+
private _onIncludeDocumentation:{source:string, script:string}[] = [];
|
|
590
|
+
|
|
591
|
+
constructor(name:string, category:string){
|
|
592
|
+
this._name = name;
|
|
593
|
+
this._category = category;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
public addDefault(name:string, template:DefaultTemplateType):NodeBuilder{
|
|
597
|
+
// if the template type ends with squeare brackets, its an array type.
|
|
598
|
+
if(template.type && template.type.endsWith("[]")) {
|
|
599
|
+
template.maxInstances = template.maxInstances ? template.maxInstances : 10;
|
|
600
|
+
this._defaults[name] = {type:template.type, value:"", required:true, maxInstances:template.maxInstances};
|
|
601
|
+
for(let i = 0; i < template.maxInstances; i++){
|
|
602
|
+
this._defaults["_" + name + "_" + i] = {value:"", required:false};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
else{
|
|
606
|
+
this._defaults[name] = template;
|
|
607
|
+
}
|
|
608
|
+
return this;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
public setIcon(icon:string):NodeBuilder{
|
|
612
|
+
this._icon = icon;
|
|
613
|
+
return this;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
public setColor(color:string):NodeBuilder{
|
|
617
|
+
this._color = color;
|
|
618
|
+
return this;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
public setLabel(label:() => {}):NodeBuilder{
|
|
622
|
+
this._label = label.toString();
|
|
623
|
+
return this;
|
|
624
|
+
}
|
|
625
|
+
public setPaletteLabel(paletteLabel:string):NodeBuilder{
|
|
626
|
+
this._paletteLabel = paletteLabel;
|
|
627
|
+
return this;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
public setLabelStyle(labelStyle:string):NodeBuilder{
|
|
631
|
+
this._labelStyle = labelStyle;
|
|
632
|
+
return this;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
public setInput(input:string):NodeBuilder{
|
|
636
|
+
this._inputs = input;
|
|
637
|
+
return this;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
public addOutputs(...outputs:string[]):NodeBuilder{
|
|
641
|
+
outputs.forEach(output => this._outputs.push(output));
|
|
642
|
+
return this;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
public addTemplate(template: { template: ITemplateClass; config: TemplateConfig; }): NodeBuilder {
|
|
646
|
+
let templateDescriptor = template.template.getTemplateDescriptor();
|
|
647
|
+
|
|
648
|
+
// load the html source file
|
|
649
|
+
let uiDom = new JSDOM(fs.readFileSync(templateDescriptor.templateFile()!));
|
|
650
|
+
|
|
651
|
+
// apply the onCompose section if available.
|
|
652
|
+
let onCompose = uiDom.window.document.querySelector("script[template-section='onCompose']")?.innerHTML;
|
|
653
|
+
if(onCompose){
|
|
654
|
+
evalInContext({ node:this }, onCompose.toString()!);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// load the other sections and add them as needed.
|
|
658
|
+
let sections:string[] = ['IncludeOnce', 'IncludeEditPrepare', 'IncludeEditSave', 'IncludeEditCancel', 'IncludeEditDelete', 'IncludeEditForm', 'IncludeDocumentation'];
|
|
659
|
+
Object.values(sections).forEach(section => {
|
|
660
|
+
let sectionData = uiDom.window.document.querySelector(`[template-section='on${section}']`)?.innerHTML;
|
|
661
|
+
if(sectionData){
|
|
662
|
+
(this as any)[`add${section}`](templateDescriptor.name(), sectionData);
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
return this;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
public addIncludeOnce(source:string, script:string):NodeBuilder{
|
|
670
|
+
this._onIncludeOnce.push({source:source, script:script});
|
|
671
|
+
return this;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
public addIncludeEditPrepare(source:string, script:string):NodeBuilder{
|
|
675
|
+
this._onIncludeEditPrepare.push({source:source, script:script});
|
|
676
|
+
return this;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
public addIncludeEditSave(source:string, script:string):NodeBuilder{
|
|
680
|
+
this._onIncludeEditSave.push({source:source, script:script});
|
|
681
|
+
return this;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
public addIncludeEditCancel(source:string, script:string):NodeBuilder{
|
|
685
|
+
this._onIncludeEditCancel.push({source:source, script:script});
|
|
686
|
+
return this;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
public addIncludeEditDelete(source:string, script:string):NodeBuilder{
|
|
690
|
+
this._onIncludeEditDelete.push({source:source, script:script});
|
|
691
|
+
return this;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
public addIncludeEditForm(source:string, script:string):NodeBuilder{
|
|
695
|
+
this._onIncludeEditForm.push({source:source, script:script});
|
|
696
|
+
return this;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
public addIncludeDocumentation(source:string, script:string):NodeBuilder{
|
|
700
|
+
this._onIncludeDocumentation.push({source:source, script:script});
|
|
701
|
+
return this;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
private serializeProperty(value:any):string{
|
|
705
|
+
switch(typeof value){
|
|
706
|
+
case "boolean": {
|
|
707
|
+
return "" + value;
|
|
708
|
+
}
|
|
709
|
+
case "number": {
|
|
710
|
+
return "" + value;
|
|
711
|
+
}
|
|
712
|
+
case "string": {
|
|
713
|
+
return `'${value}'`
|
|
714
|
+
}
|
|
715
|
+
case "object": {
|
|
716
|
+
if(Array.isArray(value)){
|
|
717
|
+
return `[${(value as []).map(t => this.serializeProperty(t)).join(", ")}]`
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
throw new Error(`no serializeProperty handler for ${typeof value}, ${value}`)
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
private serializeHTML(source:string, script:string):string{
|
|
726
|
+
let context = {
|
|
727
|
+
defaults: (this._category === "config")
|
|
728
|
+
? Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-config-input-${k}`]))
|
|
729
|
+
: Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-input-${k}`]))
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return `<!-- ${source} -->
|
|
733
|
+
${Handlebars.compile(script)(context)}
|
|
734
|
+
`
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
private serializeJavaScript(source:string, script:string):string{
|
|
738
|
+
let context = {
|
|
739
|
+
defaults: (this._category === "config")
|
|
740
|
+
? Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-config-input-${k}`]))
|
|
741
|
+
: Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-input-${k}`]))
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
let serialized = Handlebars.compile(script)(context).trim();
|
|
745
|
+
if(!serialized.startsWith("{") && !serialized.endsWith("}")){
|
|
746
|
+
serialized = "{\n" + serialized + "\n}";
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return `// **** ${source} **** //
|
|
750
|
+
${serialized}
|
|
751
|
+
`
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private serializeFormEntry(source:string, script:string):string{
|
|
755
|
+
let context = {
|
|
756
|
+
defaults: (this._category === "config")
|
|
757
|
+
? Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-config-input-${k}`]))
|
|
758
|
+
: Object.fromEntries(Object.keys(this._defaults).map(k => [k, `node-input-${k}`]))
|
|
759
|
+
}
|
|
760
|
+
return `
|
|
761
|
+
<div id='section_${source}'>${Handlebars.compile(script)(context)}
|
|
762
|
+
</div>
|
|
763
|
+
`
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
private serializeDefault(name: string, value: any): String {
|
|
767
|
+
return `${name}: { ${Object.entries(value).map(([k,v]) => `${k}:${this.serializeProperty(v)}`).join(", ")} },`;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
private serializeDocumentationEntry(source: string, script: string): String {
|
|
771
|
+
return script;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
public buildType():string{
|
|
775
|
+
// compose the shaddow defaults if need be.
|
|
776
|
+
let shaddowDefaults = Object.entries(this._defaults).filter(([name, template]) => template.type && template.type.endsWith("[]"));
|
|
777
|
+
if(shaddowDefaults.length > 0){
|
|
778
|
+
this.addIncludeEditSave(this._name + "_shaddowDefaults",
|
|
779
|
+
`{ let node = this; ` +
|
|
780
|
+
shaddowDefaults
|
|
781
|
+
.map(([name, template]) => `
|
|
782
|
+
for(let i = 0; i < ${template.maxInstances}; i++){
|
|
783
|
+
node["_${name}_" + i] = (node.${name}[i] || {}).proxy || "";
|
|
784
|
+
}`)
|
|
785
|
+
.join("\n\n") +
|
|
786
|
+
`}`
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
return beautify.html_beautify(`
|
|
791
|
+
${this._onIncludeOnce.map(entry => this.serializeHTML(entry.source, entry.script)).join("")}
|
|
792
|
+
<script type="text/javascript">
|
|
793
|
+
RED.nodes.registerType('${this._name}',{
|
|
794
|
+
category: '${this._category}',
|
|
795
|
+
${(this._category !== "config" && this._icon) ? `icon: '${this._icon}',` : ""}
|
|
796
|
+
${(this._category !== "config" && this._color) ? `color: '${this._color}',` : ""}
|
|
797
|
+
${(this._category !== "config" && this._labelStyle) ? `labelStyle: '${this._labelStyle}',` : ""}
|
|
798
|
+
${(this._label) ? `label: ${this._label},` : ""}
|
|
799
|
+
${this._paletteLabel ? `paletteLabel: '${this._paletteLabel}',` : ""}
|
|
800
|
+
${(this._category !== "config") ? `inputs:${this._inputs ? "1" : "0"},` : ""}
|
|
801
|
+
${(this._category !== "config") ? `inputLabels:(i) => '${this._inputs}',` : ""}
|
|
802
|
+
${(this._category !== "config") ? `outputs:${this._outputs.length},` : ""}
|
|
803
|
+
${(this._category !== "config") ? `outputLabels:(i) => ${this.serializeProperty(this._outputs)}[i],` : ""}
|
|
804
|
+
defaults: {
|
|
805
|
+
${Object.entries(this._defaults).map(([key, value]) => this.serializeDefault(key, value)).join("\n")}
|
|
806
|
+
},
|
|
807
|
+
oneditprepare: function() {
|
|
808
|
+
${this._onIncludeEditPrepare.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
|
|
809
|
+
},
|
|
810
|
+
oneditsave: function() {
|
|
811
|
+
${this._onIncludeEditSave.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
|
|
812
|
+
},
|
|
813
|
+
oneditcancel: function() {
|
|
814
|
+
${this._onIncludeEditCancel.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
|
|
815
|
+
},
|
|
816
|
+
oneditdelete: function() {
|
|
817
|
+
${this._onIncludeEditDelete.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
|
|
818
|
+
},
|
|
819
|
+
});
|
|
820
|
+
</script>`, beautifyOptions);
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
public buildHtml():string{
|
|
824
|
+
return beautify.html_beautify(`
|
|
825
|
+
<script type="text/html" data-template-name='${this._name}'>
|
|
826
|
+
${this._onIncludeEditForm.map(entry => this.serializeFormEntry(entry.source, entry.script)).join("")}
|
|
827
|
+
</script>
|
|
828
|
+
`);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
public buildDocumentation():string{
|
|
832
|
+
return [
|
|
833
|
+
`<script type="text/markdown" data-help-name='${this._name}'>`,
|
|
834
|
+
this._onIncludeDocumentation.map(entry => this.serializeDocumentationEntry(entry.source, entry.script)).join("").trim(),
|
|
835
|
+
`</script>`
|
|
836
|
+
].join("\n")
|
|
837
|
+
}
|
|
838
|
+
;
|
|
839
|
+
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
let evalInContext = (context: any, js: string) => {
|
|
843
|
+
const contextKeys = Object.keys(context);
|
|
844
|
+
const contextValues = Object.values(context);
|
|
845
|
+
|
|
846
|
+
// For scripts, we don't wrap in return() - just execute the code directly
|
|
847
|
+
try{
|
|
848
|
+
const func = new Function(...contextKeys, js);
|
|
849
|
+
return func(...contextValues);
|
|
850
|
+
}
|
|
851
|
+
catch(error){
|
|
852
|
+
console.log("Error while evaluating onCompose Script for ", context, error);
|
|
853
|
+
throw error;
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
let buildNode = function(node:INodeClass){
|
|
858
|
+
// get the node descriptor and create the builder.
|
|
859
|
+
let nodeDescriptor = node.getNodeDescriptor();
|
|
860
|
+
let nodeBuilder = new NodeBuilder(nodeDescriptor.name(), nodeDescriptor.group());
|
|
861
|
+
|
|
862
|
+
// load the html source file
|
|
863
|
+
let uiDom = new JSDOM(fs.readFileSync(nodeDescriptor.sourceFile()));
|
|
864
|
+
|
|
865
|
+
// apply the onCompose section if available.
|
|
866
|
+
let onCompose = uiDom.window.document.querySelector("script[template-section='onCompose']")?.innerHTML;
|
|
867
|
+
if(onCompose){
|
|
868
|
+
evalInContext({ node:nodeBuilder }, onCompose.toString()!);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// load the other sections and add them as needed.
|
|
872
|
+
let sections:string[] = ['IncludeOnce', 'IncludeEditPrepare', 'IncludeEditSave', 'IncludeEditCancel', 'IncludeEditDelete', 'IncludeEditForm', 'IncludeDocumentation'];
|
|
873
|
+
Object.values(sections).forEach(section => {
|
|
874
|
+
let sectionData = uiDom.window.document.querySelector(`[template-section='on${section}']`)?.innerHTML;
|
|
875
|
+
if(sectionData){
|
|
876
|
+
(nodeBuilder as any)[`add${section}`](nodeDescriptor.name(), sectionData);
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
// add some of the dependencies here.
|
|
881
|
+
nodeDescriptor.templates().forEach(template => nodeBuilder.addTemplate(template));
|
|
882
|
+
|
|
883
|
+
return [nodeBuilder.buildType(), nodeBuilder.buildHtml(), nodeBuilder.buildDocumentation()].join("\n\n");
|
|
884
|
+
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
export {
|
|
888
|
+
buildNode
|
|
889
|
+
}
|