@theotherwillembotha/node-red-plugincore 0.2.0 → 0.3.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.
Files changed (71) hide show
  1. package/build/GenerateNodes.js +9 -30
  2. package/build/Nodes.html +2 -4
  3. package/build/Nodes.js +2 -6
  4. package/build/Plugins.html +2 -218
  5. package/build/Plugins.js +1 -6
  6. package/build/core/NodeConstructor.d.ts +3 -0
  7. package/build/core/NodeConstructor.d.ts.map +1 -1
  8. package/build/core/NodeConstructor.js +19 -42
  9. package/build/core/NodeGenerator.d.ts +3 -1
  10. package/build/core/NodeGenerator.d.ts.map +1 -1
  11. package/build/core/NodeGenerator.js +76 -31
  12. package/build/core/logger/service/LoggerService.d.ts +2 -1
  13. package/build/core/logger/service/LoggerService.d.ts.map +1 -1
  14. package/build/core/logger/service/LoggerService.js +55 -8
  15. package/build/core/logger/template/LoggerTemplate.d.ts +1 -2
  16. package/build/core/logger/template/LoggerTemplate.d.ts.map +1 -1
  17. package/build/core/logger/template/LoggerTemplate.js +18 -5
  18. package/build/core/metrics/template/CounterMetricTemplate.d.ts +1 -2
  19. package/build/core/metrics/template/CounterMetricTemplate.d.ts.map +1 -1
  20. package/build/core/metrics/template/CounterMetricTemplate.js +16 -5
  21. package/build/core/metrics/template/GaugeMetricTemplate.d.ts +1 -2
  22. package/build/core/metrics/template/GaugeMetricTemplate.d.ts.map +1 -1
  23. package/build/core/metrics/template/GaugeMetricTemplate.js +16 -5
  24. package/build/core/metrics/template/MetricsTemplate.d.ts +1 -2
  25. package/build/core/metrics/template/MetricsTemplate.d.ts.map +1 -1
  26. package/build/core/metrics/template/MetricsTemplate.js +18 -5
  27. package/build/core/metrics/template/TimerMetricTemplate.d.ts +1 -2
  28. package/build/core/metrics/template/TimerMetricTemplate.d.ts.map +1 -1
  29. package/build/core/metrics/template/TimerMetricTemplate.js +16 -5
  30. package/build/core/other/template/BasicTemplate.d.ts +1 -2
  31. package/build/core/other/template/BasicTemplate.d.ts.map +1 -1
  32. package/build/core/other/template/BasicTemplate.js +15 -5
  33. package/build/core/other/template/SettingsTemplate.d.ts +1 -2
  34. package/build/core/other/template/SettingsTemplate.d.ts.map +1 -1
  35. package/build/core/other/template/SettingsTemplate.js +17 -5
  36. package/build/core/state/template/StateTemplate.d.ts +1 -2
  37. package/build/core/state/template/StateTemplate.d.ts.map +1 -1
  38. package/build/core/state/template/StateTemplate.js +19 -5
  39. package/build/core/tagging/TemplateDescriptionDecorator.d.ts +8 -0
  40. package/build/core/tagging/TemplateDescriptionDecorator.d.ts.map +1 -0
  41. package/build/core/tagging/TemplateDescriptionDecorator.js +10 -0
  42. package/build/core/ui/template/ScriptEditorTemplate.d.ts +1 -2
  43. package/build/core/ui/template/ScriptEditorTemplate.d.ts.map +1 -1
  44. package/build/core/ui/template/ScriptEditorTemplate.js +15 -5
  45. package/build/core/ui/template/UIHelperTemplate.d.ts +1 -2
  46. package/build/core/ui/template/UIHelperTemplate.d.ts.map +1 -1
  47. package/build/core/ui/template/UIHelperTemplate.js +15 -5
  48. package/build/core/webhook/template/WebhookTemplate.d.ts +1 -2
  49. package/build/core/webhook/template/WebhookTemplate.d.ts.map +1 -1
  50. package/build/core/webhook/template/WebhookTemplate.js +19 -6
  51. package/build/index.d.ts +1 -0
  52. package/build/index.d.ts.map +1 -1
  53. package/build/index.js +1 -0
  54. package/package.json +1 -2
  55. package/src/GenerateNodes.ts +9 -35
  56. package/src/core/NodeConstructor.ts +23 -45
  57. package/src/core/NodeGenerator.ts +87 -33
  58. package/src/core/logger/service/LoggerService.ts +23 -8
  59. package/src/core/logger/template/LoggerTemplate.ts +9 -11
  60. package/src/core/metrics/template/CounterMetricTemplate.ts +8 -11
  61. package/src/core/metrics/template/GaugeMetricTemplate.ts +8 -11
  62. package/src/core/metrics/template/MetricsTemplate.ts +9 -9
  63. package/src/core/metrics/template/TimerMetricTemplate.ts +8 -10
  64. package/src/core/other/template/BasicTemplate.ts +6 -11
  65. package/src/core/other/template/SettingsTemplate.ts +8 -13
  66. package/src/core/state/template/StateTemplate.ts +10 -9
  67. package/src/core/tagging/TemplateDescriptionDecorator.ts +19 -0
  68. package/src/core/ui/template/ScriptEditorTemplate.ts +6 -10
  69. package/src/core/ui/template/UIHelperTemplate.ts +6 -10
  70. package/src/core/webhook/template/WebhookTemplate.ts +10 -12
  71. package/src/index.ts +1 -0
@@ -1,4 +1,4 @@
1
- import { BaseNodeConfig, Template, TemplateDescriptor } from "../../NodeConstructor";
1
+ import { BaseNodeConfig, Template } from "../../NodeConstructor";
2
2
  import { RestAuthType, ApiKeyMechanismType } from "../../logger/service/LoggerService";
3
3
  export interface WebhookTemplateConfig extends BaseNodeConfig {
4
4
  webhook: string;
@@ -17,6 +17,5 @@ export type ReverseproxyConnection = {
17
17
  domainname: string;
18
18
  };
19
19
  export declare class WebhookTemplate extends Template {
20
- static getTemplateDescriptor(): TemplateDescriptor;
21
20
  }
22
21
  //# sourceMappingURL=WebhookTemplate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"WebhookTemplate.d.ts","sourceRoot":"","sources":["../../../../src/core/webhook/template/WebhookTemplate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAGpF,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAEvF,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IACzD,OAAO,EAAC,MAAM,CAAC;IACf,WAAW,EAAC,MAAM,CAAC;IACnB,WAAW,EAAC,YAAY,CAAC;IACzB,0BAA0B,EAAC,MAAM,CAAC;IAClC,0BAA0B,EAAC,MAAM,CAAC;IAClC,4BAA4B,EAAC,mBAAmB,CAAC;IACjD,sBAAsB,EAAC,MAAM,CAAC;IAC9B,wBAAwB,EAAC,MAAM,CAAC;IAEhC,sBAAsB,EAAC,OAAO,CAAC;IAC/B,0BAA0B,EAAC,sBAAsB,EAAE,CAAC;CACvD;AAED,MAAM,MAAM,sBAAsB,GAAG;IACjC,KAAK,EAAC,MAAM,CAAC;IACb,UAAU,EAAC,MAAM,CAAA;CACpB,CAAA;AAED,qBAAa,eAAgB,SAAQ,QAAQ;IAEzC,MAAM,CAAC,qBAAqB,IAAI,kBAAkB,CAKjD;CACJ"}
1
+ {"version":3,"file":"WebhookTemplate.d.ts","sourceRoot":"","sources":["../../../../src/core/webhook/template/WebhookTemplate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AAMhE,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,oCAAoC,CAAC;AAEvF,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IACzD,OAAO,EAAC,MAAM,CAAC;IACf,WAAW,EAAC,MAAM,CAAC;IACnB,WAAW,EAAC,YAAY,CAAC;IACzB,0BAA0B,EAAC,MAAM,CAAC;IAClC,0BAA0B,EAAC,MAAM,CAAC;IAClC,4BAA4B,EAAC,mBAAmB,CAAC;IACjD,sBAAsB,EAAC,MAAM,CAAC;IAC9B,wBAAwB,EAAC,MAAM,CAAC;IAEhC,sBAAsB,EAAC,OAAO,CAAC;IAC/B,0BAA0B,EAAC,sBAAsB,EAAE,CAAC;CACvD;AAED,MAAM,MAAM,sBAAsB,GAAG;IACjC,KAAK,EAAC,MAAM,CAAC;IACb,UAAU,EAAC,MAAM,CAAA;CACpB,CAAA;AAED,qBAKa,eAAgB,SAAQ,QAAQ;CAC5C"}
@@ -1,12 +1,25 @@
1
1
  "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
2
8
  Object.defineProperty(exports, "__esModule", { value: true });
3
9
  exports.WebhookTemplate = void 0;
4
10
  const NodeConstructor_1 = require("../../NodeConstructor");
5
- const WebhookServerConfigNode_1 = require("../node/WebhookServerConfigNode");
6
11
  const NodeGenerator_1 = require("../../NodeGenerator");
7
- class WebhookTemplate extends NodeConstructor_1.Template {
8
- static getTemplateDescriptor() {
9
- return new NodeConstructor_1.TemplateDescriptor("webhook", WebhookTemplate, NodeGenerator_1.SourceUtility.getSourcePath("/build/", "/src/") + "WebhookTemplate.html", [WebhookServerConfigNode_1.WebhookServerConfigNode]);
10
- }
11
- }
12
+ const TemplateDescriptionDecorator_1 = require("../../tagging/TemplateDescriptionDecorator");
13
+ const DelegatedConfigReferenceNode_1 = require("../../other/node/DelegatedConfigReferenceNode");
14
+ const WebhookServerConfigNode_1 = require("../node/WebhookServerConfigNode");
15
+ const WebhookServerService_1 = require("../service/WebhookServerService");
16
+ let WebhookTemplate = class WebhookTemplate extends NodeConstructor_1.Template {
17
+ };
12
18
  exports.WebhookTemplate = WebhookTemplate;
19
+ exports.WebhookTemplate = WebhookTemplate = __decorate([
20
+ (0, TemplateDescriptionDecorator_1.TemplateDescription)({
21
+ name: "webhook",
22
+ templateFile: NodeGenerator_1.SourceUtility.getSourcePath("/build/", "/src/") + "WebhookTemplate.html",
23
+ dependencies: [DelegatedConfigReferenceNode_1.DelegatedConfigReferenceNode, WebhookServerConfigNode_1.WebhookServerConfigNode, WebhookServerService_1.WebhookServerService],
24
+ })
25
+ ], WebhookTemplate);
package/build/index.d.ts CHANGED
@@ -19,6 +19,7 @@ export * from "./core/state/template/StateTemplate";
19
19
  export * from "./core/tagging/service/NodeTypeService";
20
20
  export * from "./core/tagging/NodeDescriptionDecorator";
21
21
  export * from "./core/tagging/ServiceDescriptionDecorator";
22
+ export * from "./core/tagging/TemplateDescriptionDecorator";
22
23
  export * from "./core/ui/template/UIHelperTemplate";
23
24
  export * from "./core/ui/template/ScriptEditorTemplate";
24
25
  export * from "./core/other/node/DelegatedConfigReferenceNode";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AAItD,cAAc,uCAAuC,CAAC;AACtD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,uCAAuC,CAAC;AACtD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,6CAA6C,CAAC;AAG5D,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,qCAAqC,CAAC;AAGpD,cAAc,wCAAwC,CAAC;AACvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,4CAA4C,CAAC;AAG3D,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AAGxD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,mCAAmC,CAAC;AAClD,cAAc,sCAAsC,CAAC;AACrD,cAAc,qCAAqC,CAAC;AACpD,cAAc,wCAAwC,CAAC;AAGvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,6CAA6C,CAAC;AAG5D,cAAc,gDAAgD,CAAC;AAG/D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,qCAAqC,CAAC;AACpD,cAAc,uCAAuC,CAAC;AAItD,cAAc,uCAAuC,CAAC;AACtD,cAAc,yCAAyC,CAAC;AACxD,cAAc,+CAA+C,CAAC;AAC9D,cAAc,6CAA6C,CAAC;AAC5D,cAAc,uCAAuC,CAAC;AACtD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,2CAA2C,CAAC;AAC1D,cAAc,6CAA6C,CAAC;AAG5D,cAAc,mCAAmC,CAAC;AAClD,cAAc,mCAAmC,CAAC;AAClD,cAAc,2CAA2C,CAAC;AAC1D,cAAc,qCAAqC,CAAC;AAGpD,cAAc,wCAAwC,CAAC;AACvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,4CAA4C,CAAC;AAC3D,cAAc,6CAA6C,CAAC;AAG5D,cAAc,qCAAqC,CAAC;AACpD,cAAc,yCAAyC,CAAC;AAGxD,cAAc,gDAAgD,CAAC;AAC/D,cAAc,mCAAmC,CAAC;AAClD,cAAc,sCAAsC,CAAC;AACrD,cAAc,qCAAqC,CAAC;AACpD,cAAc,wCAAwC,CAAC;AAGvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,6CAA6C,CAAC;AAG5D,cAAc,gDAAgD,CAAC;AAG/D,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,iCAAiC,CAAC;AAChD,cAAc,6BAA6B,CAAC"}
package/build/index.js CHANGED
@@ -39,6 +39,7 @@ __exportStar(require("./core/state/template/StateTemplate"), exports);
39
39
  __exportStar(require("./core/tagging/service/NodeTypeService"), exports);
40
40
  __exportStar(require("./core/tagging/NodeDescriptionDecorator"), exports);
41
41
  __exportStar(require("./core/tagging/ServiceDescriptionDecorator"), exports);
42
+ __exportStar(require("./core/tagging/TemplateDescriptionDecorator"), exports);
42
43
  // UI
43
44
  __exportStar(require("./core/ui/template/UIHelperTemplate"), exports);
44
45
  __exportStar(require("./core/ui/template/ScriptEditorTemplate"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theotherwillembotha/node-red-plugincore",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A core framework for building production-grade Node-RED plugins, with built-in support for structured logging, metrics, and webhook ingestion.",
5
5
  "author": "Willem Botha (@theotherwillembotha)",
6
6
  "license": "ISC",
@@ -60,7 +60,6 @@
60
60
  "js-beautify": "^1.15.4",
61
61
  "jsdom": "^29.1.1",
62
62
  "markdown-it": "^14.1.0",
63
- "network": "^0.7.0",
64
63
  "reflect-metadata": "^0.2.2"
65
64
  },
66
65
  "node-red": {
@@ -1,42 +1,16 @@
1
1
 
2
- import { DelegatedConfigReferenceNode } from "./core/other/node/DelegatedConfigReferenceNode.js";
2
+ import { NodeGenerator } from "./index.js"
3
3
  import { NodeTypeService } from "./core/tagging/service/NodeTypeService.js";
4
- import { NodeGenerator, SettingsService, BasicTemplate, SettingsTemplate, TimerMetricTemplate, UIHelperTemplate, ScriptEditorTemplate } from "./index.js"
5
- import { WebhookServerConfigNode, WebhookServerService, WebhookTemplate, } from "./index.js"
6
-
7
- import {LoggerService, LoggerTemplate } from "./index.js";
8
-
9
- import { MetricsService, MetricsTemplate, CounterMetricTemplate, GaugeMetricTemplate} from "./index.js";
10
- import { StateService, StateTemplate, InternalStateConfigNode } from "./index.js";
11
4
 
5
+ // Plugincore is a build framework — it has no leaf nodes of its own.
6
+ // Infrastructure nodes (DelegatedConfigReferenceNode, config nodes, etc.)
7
+ // are pulled in automatically via template dependencies when downstream
8
+ // packages register their nodes.
9
+ //
10
+ // NodeTypeService is the one service that must always be present because
11
+ // it powers the tag registry used by NodeManager at runtime.
12
12
  new NodeGenerator("./src/core/")
13
- // services.
14
- .registerService(LoggerService)
15
- .registerService(MetricsService)
16
- .registerService(SettingsService)
17
- .registerService(WebhookServerService)
18
13
  .registerService(NodeTypeService)
19
- .registerService(StateService)
20
-
21
- // templates.
22
- .registerTemplate(BasicTemplate)
23
- .registerTemplate(LoggerTemplate)
24
- .registerTemplate(SettingsTemplate)
25
- .registerTemplate(MetricsTemplate)
26
- .registerTemplate(CounterMetricTemplate)
27
- .registerTemplate(GaugeMetricTemplate)
28
- .registerTemplate(TimerMetricTemplate)
29
- .registerTemplate(WebhookTemplate)
30
- .registerTemplate(UIHelperTemplate)
31
- .registerTemplate(ScriptEditorTemplate)
32
- .registerTemplate(StateTemplate)
33
-
34
- // nodes
35
- .registerNode(DelegatedConfigReferenceNode)
36
- .registerNode(WebhookServerConfigNode)
37
- .registerNode(InternalStateConfigNode)
38
-
39
- // done.
40
- .generate("./build/Nodes", "./build/Plugins");
14
+ .generate("./build/Nodes", "./build/Plugins", "@theotherwillembotha/node-red-plugincore");
41
15
 
42
16
  process.exit(0);
@@ -581,8 +581,7 @@ export class NodeBuilder {
581
581
  }));
582
582
  }
583
583
 
584
- public buildDeferredType(): string {
585
- // Apply shadow defaults (same side-effect as buildType)
584
+ private applyShaddowDefaults(): void {
586
585
  let shaddowDefaults = Object.entries(this._defaults).filter(([name, template]) => template.type && template.type.endsWith("[]"));
587
586
  if (shaddowDefaults.length > 0) {
588
587
  this.addIncludeEditSave(this._name + "_shaddowDefaults",
@@ -596,8 +595,10 @@ export class NodeBuilder {
596
595
  `}`
597
596
  );
598
597
  }
598
+ }
599
599
 
600
- const registerTypeBody = `{
600
+ private buildRegisterTypeBody(): string {
601
+ return `{
601
602
  category: '${this._category}',
602
603
  ${(this._category !== "config" && this._icon) ? `icon: '${this._icon}',` : ""}
603
604
  ${(this._category !== "config" && this._color) ? `color: '${this._color}',` : ""}
@@ -624,6 +625,11 @@ export class NodeBuilder {
624
625
  ${this._onIncludeEditDelete.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
625
626
  },
626
627
  }`;
628
+ }
629
+
630
+ public buildDeferredType(): string {
631
+ this.applyShaddowDefaults();
632
+ const registerTypeBody = this.buildRegisterTypeBody();
627
633
 
628
634
  return getBeautify().html_beautify(`
629
635
  <script type="text/javascript">
@@ -637,6 +643,16 @@ export class NodeBuilder {
637
643
  </script>`, beautifyOptions);
638
644
  }
639
645
 
646
+ public buildStandardType(): string {
647
+ this.applyShaddowDefaults();
648
+ const registerTypeBody = this.buildRegisterTypeBody();
649
+
650
+ return getBeautify().html_beautify(`
651
+ <script type="text/javascript">
652
+ RED.nodes.registerType('${this._name}', ${registerTypeBody});
653
+ </script>`, beautifyOptions);
654
+ }
655
+
640
656
  private serializeProperty(value:any):string{
641
657
  switch(typeof value){
642
658
  case "boolean": {
@@ -710,52 +726,14 @@ export class NodeBuilder {
710
726
  return script;
711
727
  }
712
728
 
713
- public buildType():string{
714
- // compose the shaddow defaults if need be.
715
- let shaddowDefaults = Object.entries(this._defaults).filter(([name, template]) => template.type && template.type.endsWith("[]"));
716
- if(shaddowDefaults.length > 0){
717
- this.addIncludeEditSave(this._name + "_shaddowDefaults",
718
- `{ let node = this; ` +
719
- shaddowDefaults
720
- .map(([name, template]) => `
721
- for(let i = 0; i < ${template.maxInstances}; i++){
722
- node["_${name}_" + i] = (node.${name}[i] || {}).proxy || "";
723
- }`)
724
- .join("\n\n") +
725
- `}`
726
- );
727
- }
729
+ public buildType():string{
730
+ this.applyShaddowDefaults();
731
+ const registerTypeBody = this.buildRegisterTypeBody();
728
732
 
729
733
  return getBeautify().html_beautify(`
730
734
  ${this._onIncludeOnce.map(entry => this.serializeHTML(entry.source, entry.script)).join("")}
731
735
  <script type="text/javascript">
732
- RED.nodes.registerType('${this._name}',{
733
- category: '${this._category}',
734
- ${(this._category !== "config" && this._icon) ? `icon: '${this._icon}',` : ""}
735
- ${(this._category !== "config" && this._color) ? `color: '${this._color}',` : ""}
736
- ${(this._category !== "config" && this._labelStyle) ? `labelStyle: '${this._labelStyle}',` : ""}
737
- ${(this._label) ? `label: ${this._label},` : ""}
738
- ${this._paletteLabel ? `paletteLabel: '${this._paletteLabel}',` : ""}
739
- ${(this._category !== "config") ? `inputs:${this._inputs ? "1" : "0"},` : ""}
740
- ${(this._category !== "config") ? `inputLabels:(i) => '${this._inputs}',` : ""}
741
- ${(this._category !== "config") ? `outputs:${this._outputs.length},` : ""}
742
- ${(this._category !== "config") ? `outputLabels:(i) => ${this.serializeProperty(this._outputs)}[i],` : ""}
743
- defaults: {
744
- ${Object.entries(this._defaults).map(([key, value]) => this.serializeDefault(key, value)).join("\n")}
745
- },
746
- oneditprepare: function() {
747
- ${this._onIncludeEditPrepare.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
748
- },
749
- oneditsave: function() {
750
- ${this._onIncludeEditSave.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
751
- },
752
- oneditcancel: function() {
753
- ${this._onIncludeEditCancel.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
754
- },
755
- oneditdelete: function() {
756
- ${this._onIncludeEditDelete.map(entry => this.serializeJavaScript(entry.source, entry.script)).join("")}
757
- },
758
- });
736
+ RED.nodes.registerType('${this._name}', ${registerTypeBody});
759
737
  </script>`, beautifyOptions);
760
738
  };
761
739
 
@@ -58,12 +58,10 @@ class NodeGenerator {
58
58
 
59
59
  public registerTemplate(template: ITemplateClass): NodeGenerator {
60
60
  try{
61
- // verify that we dont already have this template.
62
61
  let descriptor = template.getTemplateDescriptor();
63
62
  if(!this._templates.map(t => t.name()).includes(descriptor.name())){
64
63
  this._templates.push(descriptor);
65
- // FIXME: skip recursive dependency resolution for now.
66
- // descriptor.dependencies().forEach(dependency => this.addDependency(dependency));
64
+ descriptor.dependencies().forEach(dependency => this.addDependency(dependency));
67
65
  }
68
66
  }
69
67
  catch(error){
@@ -75,14 +73,14 @@ class NodeGenerator {
75
73
 
76
74
  public registerNode(node:INodeClass):NodeGenerator{
77
75
  try{
78
- // verify that we have dont already have this node.
79
76
  let descriptor = node.getNodeDescriptor();
80
77
  if(!this._nodes[descriptor.id()]){
81
78
  this._nodes[descriptor.id()] = {nodeClass:node, descriptor:descriptor};
82
- // skip the recursive dependencies for now.
83
- //descriptor.dependencies().forEach(dependency => this.addDependency(dependency));
79
+ // resolve templates declared in @NodeDescription
80
+ descriptor.templates().forEach(t => this.registerTemplate(t.template));
81
+ // resolve explicit dependencies (services, nodes, templates)
82
+ descriptor.dependencies().forEach(dependency => this.addDependency(dependency));
84
83
  }
85
-
86
84
  }
87
85
  catch(error){
88
86
  console.log("ERROR:", error);
@@ -90,13 +88,12 @@ class NodeGenerator {
90
88
  return this;
91
89
  }
92
90
 
93
- public registerService(service: IServiceClass):NodeGenerator {
91
+ public registerService(service: IServiceClass):NodeGenerator {
94
92
  try{
95
93
  let descriptor = service.getServiceDescriptor();
96
94
  if(!this._services.map(s => s.name()).includes(descriptor.name())){
97
95
  this._services.push(descriptor);
98
- // FIXME: skip recursive dependency resolution for now.
99
- //descriptor.dependencies().forEach(dependency => this.addDependency(dependency));
96
+ descriptor.dependencies().forEach(dependency => this.addDependency(dependency));
100
97
  }
101
98
  }
102
99
  catch(error){
@@ -105,19 +102,22 @@ class NodeGenerator {
105
102
  return this;
106
103
  }
107
104
 
108
- public generate(nodesOutputFile:string, pluginsOutputFile:string){
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", `
114
- <!--
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.
119
- -->
120
- `);
105
+ public generate(nodesOutputFile:string, pluginsOutputFile:string, packageName:string){
106
+ // Partition nodes into owned (belongs to this package) vs shared (from dependencies).
107
+ // Owned nodes get standard registerType + data-template-name in Nodes.html so that
108
+ // Node-RED's registry maps them to this module (enabling palette icons, flow library
109
+ // detection, etc.). Shared nodes keep the deferred pattern in Plugins.html to avoid
110
+ // HTML-scanning conflicts when multiple plugins bundle the same infrastructure nodes.
111
+ const allNodes = Object.values(this._nodes);
112
+ const ownedNodes = allNodes.filter(node => node.descriptor.package() === packageName);
113
+ const sharedNodes = allNodes.filter(node => node.descriptor.package() !== packageName);
114
+
115
+ console.log(`\nPackage: ${packageName}`);
116
+ console.log(` Owned nodes (→ Nodes.html): ${ownedNodes.map(n => n.descriptor.id()).join(', ') || '(none)'}`);
117
+ console.log(` Shared nodes (→ Plugins.html): ${sharedNodes.map(n => n.descriptor.id()).join(', ') || '(none)'}\n`);
118
+
119
+ // STEP 1. Generate Nodes.html with standard client-side definitions for owned nodes.
120
+ this.generateNodesHtml(nodesOutputFile + ".html", ownedNodes);
121
121
 
122
122
  // Step 3. compose the JS structure for each of the nodes.
123
123
  // 1. get a list of the files to import.
@@ -258,17 +258,71 @@ module.exports = function (RED) {
258
258
  fs.writeFileSync(pluginsOutputFile + ".js", output);
259
259
  console.log(`Done building ${this._services.length} services\n${this._services.map(service => " - " + service.name() + "\n").join("")}\n`);
260
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.
261
+ // STEP 4. Generate Plugins.html with deferred client-side definitions for shared nodes.
262
+ this.generatePluginsHtml(pluginsOutputFile + ".html", sharedNodes);
263
+ }
264
+
265
+ private generateNodesHtml(filePath: string, nodes: {nodeClass: INodeClass, descriptor: NodeDescriptor}[]): void {
266
+ if (nodes.length === 0) {
267
+ fs.writeFileSync(filePath, `
268
+ <!--
269
+ ${NodeGenerator.warning}
270
+ No owned nodes in this package. Shared nodes are registered via Plugins.html.
271
+ -->
272
+ `);
273
+ console.log(`Nodes.html: no owned nodes\n`);
274
+ return;
275
+ }
276
+
277
+ const seenOnce = new Set<string>();
278
+ const onceHtmlParts: string[] = [];
279
+ const nodeParts: string[] = [];
280
+
281
+ nodes.forEach(node => {
282
+ console.log(`Building Nodes.html for: ${node.descriptor.id()}`);
283
+ const nb = buildNodeBuilder(node.nodeClass);
284
+
285
+ nb.buildOnceHtml().forEach(({source, html}) => {
286
+ if (!seenOnce.has(source)) {
287
+ seenOnce.add(source);
288
+ onceHtmlParts.push(html);
289
+ }
290
+ });
291
+
292
+ nodeParts.push([
293
+ nb.buildStandardType(),
294
+ nb.buildHtml(),
295
+ nb.buildDocumentation()
296
+ ].join('\n\n'));
297
+ });
298
+
299
+ fs.writeFileSync(filePath, `
300
+ <!--
301
+ ${NodeGenerator.warning}
302
+ -->
303
+ ${onceHtmlParts.join('\n')}
304
+ ${nodeParts.join('\n\n')}
305
+ `);
306
+ console.log(`Done building Nodes.html (${nodes.length} owned nodes)\n`);
307
+ }
308
+
309
+ private generatePluginsHtml(filePath: string, nodes: {nodeClass: INodeClass, descriptor: NodeDescriptor}[]): void {
310
+ if (nodes.length === 0) {
311
+ fs.writeFileSync(filePath, `
312
+ <!--
313
+ ${NodeGenerator.warning}
314
+ No shared nodes in this package. All nodes are registered via Nodes.html.
315
+ -->
316
+ `);
317
+ console.log(`Plugins.html: no shared nodes\n`);
318
+ return;
319
+ }
320
+
267
321
  const seenOnce = new Set<string>();
268
322
  const onceHtmlParts: string[] = [];
269
323
  const nodePluginParts: string[] = [];
270
324
 
271
- Object.values(this._nodes).forEach(node => {
325
+ nodes.forEach(node => {
272
326
  console.log(`Building Plugins.html for: ${node.descriptor.id()}`);
273
327
  const nb = buildNodeBuilder(node.nodeClass);
274
328
 
@@ -286,14 +340,14 @@ module.exports = function (RED) {
286
340
  ].join('\n\n'));
287
341
  });
288
342
 
289
- fs.writeFileSync(pluginsOutputFile + ".html", `
343
+ fs.writeFileSync(filePath, `
290
344
  <!--
291
345
  ${NodeGenerator.warning}
292
346
  -->
293
347
  ${onceHtmlParts.join('\n')}
294
348
  ${nodePluginParts.join('\n\n')}
295
349
  `);
296
- console.log(`Done building Plugins.html\n`);
350
+ console.log(`Done building Plugins.html (${nodes.length} shared nodes)\n`);
297
351
  }
298
352
 
299
353
  private static generateHeader(node:NodeDescriptor){
@@ -320,7 +374,7 @@ class TypeUtility {
320
374
  private constructor(){}
321
375
 
322
376
  private static getPrototypes(t:INamedType):string[] {
323
- if(!t.name){
377
+ if(!t || !t.name){
324
378
  return [];
325
379
  }
326
380
  else{
@@ -1,4 +1,5 @@
1
1
 
2
+ import * as os from 'os';
2
3
  import {BaseService, ConfigNodeConfig, ConfigNode } from "../../NodeConstructor";
3
4
  import { ServiceDescription } from "../../tagging/ServiceDescriptionDecorator";
4
5
  import { NodeAPI, NodeAPISettingsWithData, Node } from "node-red";
@@ -6,7 +7,18 @@ import { LoggerTemplate, LoggerTemplateConfig } from "../template/LoggerTemplate
6
7
 
7
8
  // Build-time / optional runtime deps - lazy so they are never required at bundle load time.
8
9
  function getHandlebars(): any { return require('handlebars'); }
9
- function getNetwork(): any { return require('network'); }
10
+
11
+ function getLocalIpAddress(): string {
12
+ const interfaces = os.networkInterfaces();
13
+ for (const name of Object.keys(interfaces)) {
14
+ for (const iface of interfaces[name] ?? []) {
15
+ if (iface.family === 'IPv4' && !iface.internal) {
16
+ return iface.address;
17
+ }
18
+ }
19
+ }
20
+ return '127.0.0.1';
21
+ }
10
22
 
11
23
  let _helpersRegistered = false;
12
24
  function ensureHelpersRegistered(): void {
@@ -134,7 +146,15 @@ export abstract class AbstractLogger<BaseLoggerConfig>{
134
146
  })
135
147
  export class LoggerService extends BaseService {
136
148
 
137
- public static instanceID:string;
149
+ // Backed by a global so the value is shared across separately-bundled copies
150
+ // of plugincore (Nodes.js and Plugins.js each inline their own copy).
151
+ public static get instanceID(): string {
152
+ return (global as any).__plugincore_instance_id__ ?? '';
153
+ }
154
+ public static set instanceID(value: string) {
155
+ (global as any).__plugincore_instance_id__ = value;
156
+ }
157
+
138
158
  private red!: NodeAPI<NodeAPISettingsWithData>;
139
159
 
140
160
  constructor(){
@@ -143,12 +163,7 @@ export class LoggerService extends BaseService {
143
163
 
144
164
  public init(red: NodeAPI<NodeAPISettingsWithData>): void | Promise<void> {
145
165
  this.red = red;
146
-
147
- return new Promise<void>((resolve) => {
148
- getNetwork().get_active_interface((_err:any, obj:any) => {
149
- LoggerService.instanceID = obj.ip_address;
150
- });
151
- });
166
+ LoggerService.instanceID = getLocalIpAddress();
152
167
  }
153
168
 
154
169
  public deinit(red: NodeAPI<NodeAPISettingsWithData>): void | Promise<void> {}
@@ -1,5 +1,8 @@
1
1
  import { SourceUtility } from "../../NodeGenerator";
2
- import { BaseNodeConfig, Template, TemplateDescriptor } from "../../NodeConstructor"
2
+ import { BaseNodeConfig, Template } from "../../NodeConstructor"
3
+ import { TemplateDescription } from "../../tagging/TemplateDescriptionDecorator";
4
+ import { DelegatedConfigReferenceNode } from "../../other/node/DelegatedConfigReferenceNode";
5
+ import { LoggerService } from "../service/LoggerService";
3
6
 
4
7
  // TODO: we need to prefix logger teplate properties with logtemplate_
5
8
  export interface LoggerTemplateNodeConfig extends BaseNodeConfig{
@@ -18,15 +21,10 @@ export type LoggerTemplateConfig = {
18
21
  level:string;
19
22
  }
20
23
 
24
+ @TemplateDescription({
25
+ name: "logger",
26
+ templateFile: SourceUtility.getSourcePath("/build/", "/src/") + "LoggerTemplate.html",
27
+ dependencies: [DelegatedConfigReferenceNode, LoggerService],
28
+ })
21
29
  export class LoggerTemplate extends Template {
22
-
23
-
24
- static getTemplateDescriptor(): TemplateDescriptor {
25
- return new TemplateDescriptor(
26
- "logger",
27
- LoggerTemplate,
28
- SourceUtility.getSourcePath("/build/", "/src/") + "LoggerTemplate.html",
29
- [],
30
- )
31
- }
32
30
  }
@@ -1,15 +1,12 @@
1
1
  import { SourceUtility } from "../../NodeGenerator";
2
- import { Template, TemplateDescriptor } from "../../NodeConstructor"
2
+ import { Template } from "../../NodeConstructor"
3
+ import { TemplateDescription } from "../../tagging/TemplateDescriptionDecorator";
3
4
  import { CounterMetricConfigNode } from "../node/CounterMetricConfigNode";
4
5
 
6
+ @TemplateDescription({
7
+ name: "countermetric",
8
+ templateFile: SourceUtility.getSourcePath("/build/", "/src/") + "CounterMetricTemplate.html",
9
+ dependencies: [CounterMetricConfigNode],
10
+ })
5
11
  export class CounterMetricTemplate extends Template {
6
-
7
-
8
- public static getTemplateDescriptor(): TemplateDescriptor {
9
- return new TemplateDescriptor(
10
- "countermetric",
11
- CounterMetricTemplate,
12
- SourceUtility.getSourcePath("/build/", "/src/") + "CounterMetricTemplate.html",
13
- [CounterMetricConfigNode]);
14
- }
15
- }
12
+ }
@@ -1,15 +1,12 @@
1
1
  import { SourceUtility } from "../../NodeGenerator";
2
- import { Template, TemplateDescriptor } from "../../NodeConstructor"
2
+ import { Template } from "../../NodeConstructor"
3
+ import { TemplateDescription } from "../../tagging/TemplateDescriptionDecorator";
3
4
  import { GaugeMetricConfigNode } from "../node/GaugeMetricConfigNode";
4
5
 
6
+ @TemplateDescription({
7
+ name: "gaugemetric",
8
+ templateFile: SourceUtility.getSourcePath("/build/", "/src/") + "GaugeMetricTemplate.html",
9
+ dependencies: [GaugeMetricConfigNode],
10
+ })
5
11
  export class GaugeMetricTemplate extends Template {
6
-
7
-
8
- public static getTemplateDescriptor(): TemplateDescriptor {
9
- return new TemplateDescriptor(
10
- "gaugemetric",
11
- GaugeMetricTemplate,
12
- SourceUtility.getSourcePath("/build/", "/src/") + "GaugeMetricTemplate.html",
13
- [GaugeMetricConfigNode]);
14
- }
15
- }
12
+ }
@@ -1,17 +1,17 @@
1
1
  import { SourceUtility } from "../../NodeGenerator";
2
- import { BaseNodeConfig, Template, TemplateDescriptor } from "../../NodeConstructor"
2
+ import { BaseNodeConfig, Template } from "../../NodeConstructor"
3
+ import { TemplateDescription } from "../../tagging/TemplateDescriptionDecorator";
4
+ import { DelegatedConfigReferenceNode } from "../../other/node/DelegatedConfigReferenceNode";
5
+ import { MetricsService } from "../service/MetricsService";
3
6
  import { MetricsReference } from "../service/MetricsService";
4
7
 
5
8
  export interface MetricsTemplateConfig extends BaseNodeConfig, MetricsReference {
6
9
  }
7
10
 
11
+ @TemplateDescription({
12
+ name: "metrics",
13
+ templateFile: SourceUtility.getSourcePath("/build/", "/src/") + "MetricsTemplate.html",
14
+ dependencies: [DelegatedConfigReferenceNode, MetricsService],
15
+ })
8
16
  export class MetricsTemplate extends Template {
9
-
10
- public static getTemplateDescriptor(): TemplateDescriptor {
11
- return new TemplateDescriptor(
12
- "metrics",
13
- MetricsTemplate,
14
- SourceUtility.getSourcePath("/build/", "/src/") + "MetricsTemplate.html",
15
- []);
16
- }
17
17
  }
@@ -1,14 +1,12 @@
1
1
  import { SourceUtility } from "../../NodeGenerator";
2
- import { Template, TemplateDescriptor } from "../../NodeConstructor"
2
+ import { Template } from "../../NodeConstructor"
3
+ import { TemplateDescription } from "../../tagging/TemplateDescriptionDecorator";
3
4
  import { TimerMetricConfigNode } from "../node/TimerMetricConfigNode";
4
5
 
6
+ @TemplateDescription({
7
+ name: "timermetric",
8
+ templateFile: SourceUtility.getSourcePath("/build/", "/src/") + "TimerMetricTemplate.html",
9
+ dependencies: [TimerMetricConfigNode],
10
+ })
5
11
  export class TimerMetricTemplate extends Template {
6
-
7
- public static getTemplateDescriptor(): TemplateDescriptor {
8
- return new TemplateDescriptor(
9
- "timermetric",
10
- TimerMetricTemplate,
11
- SourceUtility.getSourcePath("/build/", "/src/") + "TimerMetricTemplate.html",
12
- [TimerMetricConfigNode]);
13
- }
14
- }
12
+ }