@bonsae/nrg 0.1.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.
package/build/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // src/utils.ts
2
+ function defineRuntimeSettings(settings) {
3
+ return settings;
4
+ }
5
+ export {
6
+ defineRuntimeSettings
7
+ };
@@ -661,13 +661,24 @@ function NodeRef(nodeClass, options) {
661
661
  };
662
662
  }
663
663
  function TypedInput(options) {
664
+ const { types, ...rest } = options ?? {};
664
665
  return {
665
666
  ...TypedInputSchema,
666
- ...options,
667
+ ...rest,
668
+ ...types ? { "x-typed-types": types } : {},
667
669
  [import_typebox.Kind]: "TypedInput"
668
670
  };
669
671
  }
672
+ var _OriginalString = import_typebox.Type.String.bind(import_typebox.Type);
673
+ function StringWithLang(options) {
674
+ const { lang, ...rest } = options ?? {};
675
+ return _OriginalString({
676
+ ...rest,
677
+ ...lang ? { "x-editor-language": lang } : {}
678
+ });
679
+ }
670
680
  var SchemaType = Object.assign({}, import_typebox.Type, {
681
+ String: StringWithLang,
671
682
  NodeRef,
672
683
  TypedInput
673
684
  });
@@ -1,4 +1,4 @@
1
- (function(){var s=document.createElement("style");s.textContent="[data-v-e5c98949] .node-red-vue-input-error-message{color:var(--red-ui-text-color-error)}[data-v-e5c98949] .nrg-label{display:inline-block;width:100%;cursor:default}[data-v-e5c98949] .form-row input[type=text],[data-v-e5c98949] .form-row input[type=number],[data-v-e5c98949] .form-row input[type=password]{height:34px;padding:0 8px;box-sizing:border-box}.container[data-v-508cfdb8]{position:relative}.expand-button[data-v-508cfdb8]{position:absolute;top:-23px;right:0;z-index:10;transition:color .3s ease;cursor:pointer}.nrg-required[data-v-642581e2]{color:var(--red-ui-text-color-error);margin-left:2px}\n";document.head.appendChild(s);})();
1
+ (function(){var s=document.createElement("style");s.textContent="[data-v-e5c98949] .node-red-vue-input-error-message{color:var(--red-ui-text-color-error)}[data-v-e5c98949] .nrg-label{display:inline-block;width:100%;cursor:default}[data-v-e5c98949] .form-row input[type=text],[data-v-e5c98949] .form-row input[type=number],[data-v-e5c98949] .form-row input[type=password]{height:34px;padding:0 8px;box-sizing:border-box}.container[data-v-508cfdb8]{position:relative}.expand-button[data-v-508cfdb8]{position:absolute;top:-23px;right:0;z-index:10;transition:color .3s ease;cursor:pointer}.nrg-required[data-v-2a313890]{color:var(--red-ui-text-color-error);margin-left:2px}\n";document.head.appendChild(s);})();
2
2
  import { defineComponent as je, resolveComponent as qe, openBlock as te, createElementBlock as re, Fragment as Ke, createElementVNode as ue, createCommentVNode as ge, createVNode as Vn, toDisplayString as Pe, withDirectives as ws, vShow as bs, renderList as fr, createTextVNode as pr, createBlock as ze, createApp as Es } from "/nrg/assets/vue.esm-browser.prod.js";
3
3
  function Ss() {
4
4
  }
@@ -6168,7 +6168,13 @@ function Dn(e, t, r) {
6168
6168
  language: t["x-editor-language"]
6169
6169
  } : { key: e, label: s, inputType: "text", required: r, htmlType: "text" };
6170
6170
  default:
6171
- return {
6171
+ return t["x-editor-language"] ? {
6172
+ key: e,
6173
+ label: s,
6174
+ inputType: "editor",
6175
+ required: r,
6176
+ language: t["x-editor-language"]
6177
+ } : {
6172
6178
  key: e,
6173
6179
  label: s,
6174
6180
  inputType: "text",
@@ -6348,7 +6354,7 @@ function Bo(e, t, r, s, m, o) {
6348
6354
  ))
6349
6355
  ]);
6350
6356
  }
6351
- const Mn = /* @__PURE__ */ Ie(zo, [["render", Bo], ["__scopeId", "data-v-642581e2"]]), gs = {}, sr = {};
6357
+ const Mn = /* @__PURE__ */ Ie(zo, [["render", Bo], ["__scopeId", "data-v-2a313890"]]), gs = {}, sr = {};
6352
6358
  function Qo(e) {
6353
6359
  Object.assign(gs, e);
6354
6360
  }
@@ -111,9 +111,6 @@ function mergeOptions(defaults, overrides) {
111
111
  }
112
112
  return result;
113
113
  }
114
- function defineRuntimeSettings(settings) {
115
- return settings;
116
- }
117
114
 
118
115
  // src/vite/node-red-launcher.ts
119
116
  import { spawn } from "child_process";
@@ -1183,14 +1180,15 @@ function minifier() {
1183
1180
  return {
1184
1181
  name: "vite-plugin-node-red:client:minifier",
1185
1182
  apply: "build",
1186
- renderChunk: {
1187
- order: "post",
1188
- async handler(code, chunk, outputOptions) {
1189
- if (outputOptions.format === "es" && chunk.fileName.endsWith(".js")) {
1190
- const result = await transform(code, { minify: true });
1191
- return result.code;
1183
+ async generateBundle(_options, bundle) {
1184
+ for (const [fileName, chunk] of Object.entries(bundle)) {
1185
+ if (chunk.type === "chunk" && fileName.endsWith(".js")) {
1186
+ const result = await transform(chunk.code, {
1187
+ minify: true
1188
+ });
1189
+ chunk.code = result.code;
1190
+ chunk.map = null;
1192
1191
  }
1193
- return code;
1194
1192
  }
1195
1193
  }
1196
1194
  };
@@ -1204,8 +1202,6 @@ import fs8 from "fs";
1204
1202
  import mime2 from "mime-types";
1205
1203
  var VIRTUAL_ID = "virtual:nrg/node-definitions";
1206
1204
  var RESOLVED_ID = "\0" + VIRTUAL_ID;
1207
- var VIRTUAL_ENTRY_ID = "virtual:nrg/client-entry";
1208
- var RESOLVED_ENTRY_ID = "\0" + VIRTUAL_ENTRY_ID;
1209
1205
  var SKIP_DEFAULTS = /* @__PURE__ */ new Set(["x", "y", "z", "g", "wires", "type", "id"]);
1210
1206
  function getDefaultsFromSchema(schema) {
1211
1207
  if (!schema?.properties) return void 0;
@@ -1243,6 +1239,7 @@ function resolveIcon(iconsDir, type) {
1243
1239
  function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir, nodesDir, hasUserEntry = true) {
1244
1240
  let _nodeTypes = [];
1245
1241
  let _definitions = {};
1242
+ const cacheDir = path8.resolve("node_modules", ".nrg", "client");
1246
1243
  return {
1247
1244
  name: "vite-plugin-node-red:client:node-definitions-inliner",
1248
1245
  enforce: "pre",
@@ -1302,64 +1299,93 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
1302
1299
  outputsSchema
1303
1300
  };
1304
1301
  }
1302
+ if (!hasUserEntry) {
1303
+ const nodesCache = path8.resolve(cacheDir, "nodes");
1304
+ if (fs8.existsSync(nodesCache)) {
1305
+ fs8.rmSync(nodesCache, { recursive: true });
1306
+ }
1307
+ fs8.mkdirSync(nodesCache, { recursive: true });
1308
+ for (const type of _nodeTypes) {
1309
+ const userTsPath = nodesDir ? path8.resolve(nodesDir, `${type}.ts`) : null;
1310
+ if (userTsPath && fs8.existsSync(userTsPath)) continue;
1311
+ const content = [
1312
+ `// auto-generated by nrg`,
1313
+ `import { defineNode } from "@bonsae/nrg/client";`,
1314
+ ``,
1315
+ `export default defineNode({`,
1316
+ ` type: ${JSON.stringify(type)},`,
1317
+ `});`,
1318
+ ``
1319
+ ].join("\n");
1320
+ fs8.writeFileSync(path8.resolve(nodesCache, `${type}.ts`), content);
1321
+ }
1322
+ const entryContent = generateEntryCode("");
1323
+ fs8.mkdirSync(path8.dirname(path8.resolve(cacheDir, "index.ts")), {
1324
+ recursive: true
1325
+ });
1326
+ fs8.writeFileSync(path8.resolve(cacheDir, "index.ts"), entryContent);
1327
+ }
1305
1328
  },
1306
1329
  resolveId(id) {
1307
1330
  if (id === VIRTUAL_ID) return RESOLVED_ID;
1308
- if (id === VIRTUAL_ENTRY_ID) return RESOLVED_ENTRY_ID;
1309
1331
  },
1310
1332
  load(id) {
1311
- if (id === RESOLVED_ENTRY_ID) return "";
1312
- if (id !== RESOLVED_ID) return;
1313
- return `export default ${JSON.stringify(_definitions)};`;
1333
+ if (id === RESOLVED_ID)
1334
+ return `export default ${JSON.stringify(_definitions)};`;
1335
+ if (!hasUserEntry && id === entryPath) {
1336
+ return generateEntryCode("");
1337
+ }
1314
1338
  },
1315
1339
  transform(code, id) {
1316
- if (id !== entryPath && id !== RESOLVED_ENTRY_ID) return;
1317
- const nrgImports = /* @__PURE__ */ new Set(["__setSchemas"]);
1318
- const lines = [`import __nrgSchemas from "${VIRTUAL_ID}";`];
1319
- const postLines = [`__setSchemas(__nrgSchemas);`];
1320
- if (componentsDir && fs8.existsSync(componentsDir)) {
1321
- const formImports = [];
1322
- const formEntries = [];
1323
- for (const type of _nodeTypes) {
1324
- const componentPath = path8.resolve(componentsDir, `${type}.vue`);
1325
- if (fs8.existsSync(componentPath)) {
1326
- const varName = `__nrgForm_${type.replace(/-/g, "_")}`;
1327
- formImports.push(
1328
- `import ${varName} from ${JSON.stringify(componentPath)};`
1329
- );
1330
- formEntries.push(`${JSON.stringify(type)}: ${varName}`);
1331
- }
1332
- }
1333
- if (formImports.length > 0) {
1334
- lines.push(...formImports);
1335
- nrgImports.add("__setForms");
1336
- postLines.push(`__setForms({ ${formEntries.join(", ")} });`);
1340
+ if (id !== entryPath) return;
1341
+ if (!hasUserEntry) return;
1342
+ return { code: generateEntryCode(code), map: null };
1343
+ }
1344
+ };
1345
+ function generateEntryCode(userCode) {
1346
+ const nrgImports = /* @__PURE__ */ new Set(["__setSchemas"]);
1347
+ const lines = [`import __nrgSchemas from "${VIRTUAL_ID}";`];
1348
+ const postLines = [`__setSchemas(__nrgSchemas);`];
1349
+ if (componentsDir && fs8.existsSync(componentsDir)) {
1350
+ const formImports = [];
1351
+ const formEntries = [];
1352
+ for (const type of _nodeTypes) {
1353
+ const componentPath = path8.resolve(componentsDir, `${type}.vue`);
1354
+ if (fs8.existsSync(componentPath)) {
1355
+ const varName = `__nrgForm_${type.replace(/-/g, "_")}`;
1356
+ formImports.push(
1357
+ `import ${varName} from ${JSON.stringify(componentPath)};`
1358
+ );
1359
+ formEntries.push(`${JSON.stringify(type)}: ${varName}`);
1337
1360
  }
1338
1361
  }
1339
- if (!hasUserEntry) {
1340
- const defVarNames = [];
1341
- for (const type of _nodeTypes) {
1342
- const varName = `__nrgNodeDef_${type.replace(/-/g, "_")}`;
1343
- const tsPath = nodesDir ? path8.resolve(nodesDir, `${type}.ts`) : null;
1344
- if (tsPath && fs8.existsSync(tsPath)) {
1345
- lines.push(`import ${varName} from ${JSON.stringify(tsPath)};`);
1346
- } else {
1347
- lines.push(`const ${varName} = { type: ${JSON.stringify(type)} };`);
1348
- }
1349
- defVarNames.push(varName);
1350
- }
1351
- if (defVarNames.length > 0) {
1352
- nrgImports.add("registerTypes");
1353
- postLines.push(`registerTypes([${defVarNames.join(", ")}]);`);
1354
- }
1362
+ if (formImports.length > 0) {
1363
+ lines.push(...formImports);
1364
+ nrgImports.add("__setForms");
1365
+ postLines.push(`__setForms({ ${formEntries.join(", ")} });`);
1355
1366
  }
1356
- const importLine = `import { ${[...nrgImports].join(", ")} } from "@bonsae/nrg/client";`;
1357
- lines.splice(1, 0, importLine);
1358
- lines.push(...postLines);
1359
- lines.push("");
1360
- return { code: lines.join("\n") + code, map: null };
1361
1367
  }
1362
- };
1368
+ if (!hasUserEntry) {
1369
+ const nodesCache = path8.resolve(cacheDir, "nodes");
1370
+ const defVarNames = [];
1371
+ for (const type of _nodeTypes) {
1372
+ const varName = `__nrgNodeDef_${type.replace(/-/g, "_")}`;
1373
+ const userTsPath = nodesDir ? path8.resolve(nodesDir, `${type}.ts`) : null;
1374
+ const tsPath = userTsPath && fs8.existsSync(userTsPath) ? userTsPath : path8.resolve(nodesCache, `${type}.ts`);
1375
+ lines.push(`import ${varName} from ${JSON.stringify(tsPath)};`);
1376
+ defVarNames.push(varName);
1377
+ }
1378
+ if (defVarNames.length > 0) {
1379
+ nrgImports.add("registerTypes");
1380
+ postLines.push(`registerTypes([${defVarNames.join(", ")}]);`);
1381
+ }
1382
+ }
1383
+ const importLine = `import { ${[...nrgImports].join(", ")} } from "@bonsae/nrg/client";`;
1384
+ lines.splice(1, 0, importLine);
1385
+ lines.push(...postLines);
1386
+ lines.push("");
1387
+ return lines.join("\n") + userCode;
1388
+ }
1363
1389
  }
1364
1390
 
1365
1391
  // src/vite/client/plugins/static-copy.ts
@@ -1525,7 +1551,7 @@ async function build2(clientBuildOptions, buildContext) {
1525
1551
  build: {
1526
1552
  outDir: buildContext.outDir,
1527
1553
  emptyOutDir: false,
1528
- sourcemap: buildContext.isDev ? "inline" : true,
1554
+ sourcemap: buildContext.isDev ? "inline" : false,
1529
1555
  minify: !buildContext.isDev && format !== "es",
1530
1556
  copyPublicDir: false,
1531
1557
  lib: {
@@ -1888,6 +1914,5 @@ function nodeRed(options = {}) {
1888
1914
  ];
1889
1915
  }
1890
1916
  export {
1891
- defineRuntimeSettings,
1892
1917
  nodeRed
1893
1918
  };
@@ -48,13 +48,9 @@ function mergeOptions(defaults, overrides) {
48
48
  }
49
49
  return result;
50
50
  }
51
- function defineRuntimeSettings(settings) {
52
- return settings;
53
- }
54
51
  export {
55
52
  cleanDir,
56
53
  copyFiles,
57
- defineRuntimeSettings,
58
54
  getPackageName,
59
55
  mergeOptions
60
56
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonsae/nrg",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "NRG framework — build Node-RED nodes with Vue 3, TypeScript, and JSON Schema",
5
5
  "author": "Allan Oricil <allanoricil@duck.com>",
6
6
  "license": "MIT",
@@ -23,6 +23,9 @@
23
23
  "lint:fix": "eslint src/ --fix",
24
24
  "format": "prettier --write \"src/**/*.{ts,vue,json}\"",
25
25
  "format:check": "prettier --check \"src/**/*.{ts,vue,json}\"",
26
+ "docs:dev": "pnpm --dir docs dev",
27
+ "docs:build": "pnpm --dir docs build",
28
+ "docs:preview": "pnpm --dir docs preview",
26
29
  "prepare": "husky"
27
30
  },
28
31
  "files": [
@@ -30,6 +33,10 @@
30
33
  "build/"
31
34
  ],
32
35
  "exports": {
36
+ ".": {
37
+ "types": "./src/index.ts",
38
+ "default": "./build/index.js"
39
+ },
33
40
  "./server": {
34
41
  "types": "./src/core/server/index.ts",
35
42
  "require": "./build/server/index.cjs",
@@ -291,6 +291,16 @@ function buildField(
291
291
  return { key, label, inputType: "text", required, htmlType: "text" };
292
292
 
293
293
  default:
294
+ // string with editor language → code editor
295
+ if (schema["x-editor-language"]) {
296
+ return {
297
+ key,
298
+ label,
299
+ inputType: "editor",
300
+ required,
301
+ language: schema["x-editor-language"],
302
+ };
303
+ }
294
304
  // string (or untyped)
295
305
  return {
296
306
  key,
@@ -26,15 +26,29 @@ function NodeRef<T extends new (...args: any[]) => any>(
26
26
  } as unknown as TNodeRef<InstanceType<T>>;
27
27
  }
28
28
 
29
- function TypedInput(options?: SchemaOptions): TTypedInput {
29
+ function TypedInput(
30
+ options?: SchemaOptions & { types?: string[] },
31
+ ): TTypedInput {
32
+ const { types, ...rest } = options ?? {};
30
33
  return {
31
34
  ...TypedInputSchema,
32
- ...options,
35
+ ...rest,
36
+ ...(types ? { "x-typed-types": types } : {}),
33
37
  [Kind]: "TypedInput",
34
38
  } as unknown as TTypedInput;
35
39
  }
36
40
 
41
+ const _OriginalString = BaseType.String.bind(BaseType);
42
+ function StringWithLang(options?: SchemaOptions & { lang?: string }) {
43
+ const { lang, ...rest } = options ?? {};
44
+ return _OriginalString({
45
+ ...rest,
46
+ ...(lang ? { "x-editor-language": lang } : {}),
47
+ });
48
+ }
49
+
37
50
  const SchemaType = Object.assign({}, BaseType, {
51
+ String: StringWithLang,
38
52
  NodeRef,
39
53
  TypedInput,
40
54
  });
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { defineRuntimeSettings } from "./utils";
2
+ export type { NodeRedRuntimeSettings } from "./types";
package/src/types.ts ADDED
@@ -0,0 +1,189 @@
1
+ import type { Http2ServerRequest } from "http2";
2
+
3
+ export interface NodeRedRuntimeSettings {
4
+ userDir?: string;
5
+ nodesDir?: string | string[];
6
+ flowFile?: string;
7
+ flowFilePretty?: boolean;
8
+ credentialSecret?: string | false;
9
+ requireHttps?: boolean;
10
+ https?:
11
+ | { key: string; cert: string }
12
+ | (() =>
13
+ | Promise<{ key: string; cert: string }>
14
+ | { key: string; cert: string });
15
+ httpsRefreshInterval?: number;
16
+ httpAdminRoot?: string;
17
+ httpNodeRoot?: string;
18
+ httpNodeCors?: { origin: string; methods: string };
19
+ httpStatic?: string | { path: string; root: string }[];
20
+ httpStaticRoot?: string;
21
+ httpAdminMiddleware?: (req: unknown, res: unknown, next: () => void) => void;
22
+ httpNodeMiddleware?: (req: unknown, res: unknown, next: () => void) => void;
23
+ httpServerOptions?: Record<string, unknown>;
24
+ adminAuth?: {
25
+ type?: "credentials" | "strategy";
26
+ users?: {
27
+ username: string;
28
+ password: string;
29
+ permissions?: string | string[];
30
+ }[];
31
+ default?: {
32
+ permissions?: string | string[];
33
+ };
34
+ tokens?: (
35
+ token: string,
36
+ ) => Promise<{ user: string; permissions: string | string[] } | null>;
37
+ tokenHeader: "string";
38
+ sessionExpiryTime?: number;
39
+ [key: string]: unknown;
40
+ };
41
+ httpNodeAuth?: {
42
+ user?: string;
43
+ pass?: string;
44
+ };
45
+ httpStaticAuth?: {
46
+ user?: string;
47
+ pass?: string;
48
+ };
49
+ lang?:
50
+ | "en-US"
51
+ | "de"
52
+ | "es-ES"
53
+ | "fr"
54
+ | "ko"
55
+ | "pt-BR"
56
+ | "ru"
57
+ | "ja"
58
+ | "zh-CN"
59
+ | "zh-TW";
60
+ diagnostics?: {
61
+ enabled?: boolean;
62
+ ui?: boolean;
63
+ };
64
+ runtimeState?: {
65
+ enabled?: boolean;
66
+ ui?: boolean;
67
+ };
68
+ disableEditor?: boolean;
69
+ editorTheme?: {
70
+ page?: {
71
+ title?: string;
72
+ favicon?: string;
73
+ css?: string | string[];
74
+ scripts?: string | string[];
75
+ };
76
+ header?: {
77
+ title?: string;
78
+ image?: string;
79
+ url?: string;
80
+ };
81
+ deployButton?: {
82
+ type?: "simple" | "default";
83
+ label?: string;
84
+ icon?: string;
85
+ };
86
+ menu?: {
87
+ "menu-item-import-library"?: boolean;
88
+ "menu-item-export-library"?: boolean;
89
+ "menu-item-keyboard-shortcuts"?: boolean;
90
+ "menu-item-help"?: {
91
+ label?: string;
92
+ url?: string;
93
+ };
94
+ [menuItem: string]:
95
+ | boolean
96
+ | { label?: string; url?: string }
97
+ | undefined;
98
+ };
99
+ userMenu?: boolean;
100
+ login?: {
101
+ image?: string;
102
+ };
103
+ logout?: {
104
+ redirect?: string;
105
+ };
106
+ palette?: {
107
+ catalogues?: string[];
108
+ categories?: string[];
109
+ theme?: { category: string; type: string; color: string }[];
110
+ };
111
+ projects?: {
112
+ enabled?: boolean;
113
+ workflow?: {
114
+ mode: "manual" | "auto";
115
+ };
116
+ };
117
+ codeEditor?: {
118
+ lib?: "monaco" | "ace";
119
+ options?: Record<string, unknown>;
120
+ };
121
+ mermaid?: {
122
+ theme?: string;
123
+ };
124
+ tours?: boolean;
125
+ theme?: string;
126
+ [key: string]: unknown;
127
+ };
128
+ contextStorage?: {
129
+ default?: {
130
+ module?: "memory" | "localfilesystem" | object;
131
+ config?: Record<string, unknown>;
132
+ };
133
+ [store: string]:
134
+ | {
135
+ module?: "memory" | "localfilesystem" | object;
136
+ config?: Record<string, unknown>;
137
+ }
138
+ | undefined;
139
+ };
140
+ exportGlobalContextKeys?: boolean;
141
+ logging?: {
142
+ console?: {
143
+ level?: "fatal" | "error" | "warn" | "info" | "debug" | "trace" | "off";
144
+ metrics?: boolean;
145
+ audit?: boolean;
146
+ };
147
+ };
148
+ fileWorkingDirectory?: string;
149
+ functionExternalModules?: boolean;
150
+ functionGlobalContext?: Record<string, unknown>;
151
+ nodeMessageBufferMaxLength?: number;
152
+ functionTimeout?: number;
153
+ externalModules?: {
154
+ autoInstall?: boolean;
155
+ autoInstallRetry?: number;
156
+ palette?: {
157
+ allowInstall?: boolean;
158
+ allowUpdate?: boolean;
159
+ allowUpload?: boolean;
160
+ allowList?: string[];
161
+ denyList?: string[];
162
+ allowUpdateList?: string[];
163
+ denyUpdateList?: string[];
164
+ };
165
+ modules?: {
166
+ allowInstall?: boolean;
167
+ allowList?: string[];
168
+ denyList?: string[];
169
+ };
170
+ };
171
+ execMaxBufferSize?: number;
172
+ debugMaxLength?: number;
173
+ debugUseColors?: boolean;
174
+ httpRequestTimeout?: number;
175
+ mqttReconnectTime?: number;
176
+ serialReconnectTime?: number;
177
+ socketReconnectTime?: number;
178
+ socketTimeout?: number;
179
+ tcpMsgQueueSize?: number;
180
+ inboundWebSocketTimeout?: number;
181
+ tlsConfigDisableLocalFiles?: boolean;
182
+ webSocketNodeVerifyClient?: (info: {
183
+ origin: string;
184
+ req: Http2ServerRequest;
185
+ secure: boolean;
186
+ }) => boolean;
187
+ apiMaxLength?: string;
188
+ [key: string]: unknown;
189
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,20 @@
1
+ import type { NodeRedRuntimeSettings } from "./types";
2
+
3
+ /**
4
+ * Type-safe helper for defining Node-RED runtime settings.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { defineRuntimeSettings } from "@bonsae/nrg";
9
+ *
10
+ * export default defineRuntimeSettings({
11
+ * flowFile: "flows.json",
12
+ * flowFilePretty: true,
13
+ * });
14
+ * ```
15
+ */
16
+ export function defineRuntimeSettings(
17
+ settings: NodeRedRuntimeSettings,
18
+ ): NodeRedRuntimeSettings {
19
+ return settings;
20
+ }
@@ -173,7 +173,7 @@ async function build(
173
173
  build: {
174
174
  outDir: buildContext.outDir,
175
175
  emptyOutDir: false,
176
- sourcemap: buildContext.isDev ? "inline" : true,
176
+ sourcemap: buildContext.isDev ? "inline" : false,
177
177
  minify: !buildContext.isDev && format !== "es",
178
178
  copyPublicDir: false,
179
179
  lib: {
@@ -6,15 +6,16 @@ function minifier(): Plugin {
6
6
  name: "vite-plugin-node-red:client:minifier",
7
7
  apply: "build",
8
8
 
9
- renderChunk: {
10
- order: "post",
11
- async handler(code, chunk, outputOptions) {
12
- if (outputOptions.format === "es" && chunk.fileName.endsWith(".js")) {
13
- const result = await transform(code, { minify: true });
14
- return result.code;
9
+ async generateBundle(_options, bundle) {
10
+ for (const [fileName, chunk] of Object.entries(bundle)) {
11
+ if (chunk.type === "chunk" && fileName.endsWith(".js")) {
12
+ const result = await transform(chunk.code, {
13
+ minify: true,
14
+ });
15
+ chunk.code = result.code;
16
+ chunk.map = null as any;
15
17
  }
16
- return code;
17
- },
18
+ }
18
19
  },
19
20
  };
20
21
  }
@@ -8,9 +8,6 @@ import mime from "mime-types";
8
8
  const VIRTUAL_ID = "virtual:nrg/node-definitions";
9
9
  const RESOLVED_ID = "\0" + VIRTUAL_ID;
10
10
 
11
- const VIRTUAL_ENTRY_ID = "virtual:nrg/client-entry";
12
- const RESOLVED_ENTRY_ID = "\0" + VIRTUAL_ENTRY_ID;
13
-
14
11
  const SKIP_DEFAULTS = new Set(["x", "y", "z", "g", "wires", "type", "id"]);
15
12
 
16
13
  function getDefaultsFromSchema(
@@ -77,6 +74,8 @@ function nodeDefinitionsInliner(
77
74
  ): Plugin {
78
75
  let _nodeTypes: string[] = [];
79
76
  let _definitions: Record<string, any> = {};
77
+ // Cache directory for generated files (inside node_modules, gitignored)
78
+ const cacheDir = path.resolve("node_modules", ".nrg", "client");
80
79
 
81
80
  return {
82
81
  name: "vite-plugin-node-red:client:node-definitions-inliner",
@@ -145,80 +144,132 @@ function nodeDefinitionsInliner(
145
144
  outputsSchema,
146
145
  };
147
146
  }
147
+
148
+ // When no user entry, generate node definition files in cache
149
+ // so they appear as separate files in browser devtools.
150
+ if (!hasUserEntry) {
151
+ const nodesCache = path.resolve(cacheDir, "nodes");
152
+ if (fs.existsSync(nodesCache)) {
153
+ fs.rmSync(nodesCache, { recursive: true });
154
+ }
155
+ fs.mkdirSync(nodesCache, { recursive: true });
156
+
157
+ for (const type of _nodeTypes) {
158
+ // Skip if user has a physical node definition file
159
+ const userTsPath = nodesDir
160
+ ? path.resolve(nodesDir, `${type}.ts`)
161
+ : null;
162
+ if (userTsPath && fs.existsSync(userTsPath)) continue;
163
+
164
+ const content = [
165
+ `// auto-generated by nrg`,
166
+ `import { defineNode } from "@bonsae/nrg/client";`,
167
+ ``,
168
+ `export default defineNode({`,
169
+ ` type: ${JSON.stringify(type)},`,
170
+ `});`,
171
+ ``,
172
+ ].join("\n");
173
+ fs.writeFileSync(path.resolve(nodesCache, `${type}.ts`), content);
174
+ }
175
+
176
+ // Also write the entry file to cache
177
+ const entryContent = generateEntryCode("");
178
+ fs.mkdirSync(path.dirname(path.resolve(cacheDir, "index.ts")), {
179
+ recursive: true,
180
+ });
181
+ fs.writeFileSync(path.resolve(cacheDir, "index.ts"), entryContent);
182
+ }
148
183
  },
149
184
 
150
185
  resolveId(id) {
151
186
  if (id === VIRTUAL_ID) return RESOLVED_ID;
152
- if (id === VIRTUAL_ENTRY_ID) return RESOLVED_ENTRY_ID;
153
187
  },
154
188
 
155
189
  load(id) {
156
- if (id === RESOLVED_ENTRY_ID) return "";
157
- if (id !== RESOLVED_ID) return;
158
- return `export default ${JSON.stringify(_definitions)};`;
190
+ if (id === RESOLVED_ID)
191
+ return `export default ${JSON.stringify(_definitions)};`;
192
+
193
+ // For auto-generated entries, return the generated code directly
194
+ // so the browser devtools shows the actual source instead of the
195
+ // placeholder comment on disk.
196
+ if (!hasUserEntry && id === entryPath) {
197
+ return generateEntryCode("");
198
+ }
159
199
  },
160
200
 
161
201
  transform(code, id) {
162
- if (id !== entryPath && id !== RESOLVED_ENTRY_ID) return;
202
+ if (id !== entryPath) return;
203
+ // When the user provides their own entry, prepend the generated
204
+ // code to their source. For auto-generated entries, the load hook
205
+ // already returned the full code.
206
+ if (!hasUserEntry) return;
163
207
 
164
- const nrgImports = new Set<string>(["__setSchemas"]);
165
- const lines = [`import __nrgSchemas from "${VIRTUAL_ID}";`];
166
- const postLines: string[] = [`__setSchemas(__nrgSchemas);`];
208
+ return { code: generateEntryCode(code), map: null };
209
+ },
210
+ };
167
211
 
168
- // Auto-detect form components by convention: {componentsDir}/{type}.vue
169
- if (componentsDir && fs.existsSync(componentsDir)) {
170
- const formImports: string[] = [];
171
- const formEntries: string[] = [];
212
+ function generateEntryCode(userCode: string): string {
213
+ const nrgImports = new Set<string>(["__setSchemas"]);
214
+ const lines = [`import __nrgSchemas from "${VIRTUAL_ID}";`];
215
+ const postLines: string[] = [`__setSchemas(__nrgSchemas);`];
172
216
 
173
- for (const type of _nodeTypes) {
174
- const componentPath = path.resolve(componentsDir, `${type}.vue`);
175
- if (fs.existsSync(componentPath)) {
176
- const varName = `__nrgForm_${type.replace(/-/g, "_")}`;
177
- formImports.push(
178
- `import ${varName} from ${JSON.stringify(componentPath)};`,
179
- );
180
- formEntries.push(`${JSON.stringify(type)}: ${varName}`);
181
- }
182
- }
217
+ // Auto-detect form components by convention: {componentsDir}/{type}.vue
218
+ if (componentsDir && fs.existsSync(componentsDir)) {
219
+ const formImports: string[] = [];
220
+ const formEntries: string[] = [];
183
221
 
184
- if (formImports.length > 0) {
185
- lines.push(...formImports);
186
- nrgImports.add("__setForms");
187
- postLines.push(`__setForms({ ${formEntries.join(", ")} });`);
222
+ for (const type of _nodeTypes) {
223
+ const componentPath = path.resolve(componentsDir, `${type}.vue`);
224
+ if (fs.existsSync(componentPath)) {
225
+ const varName = `__nrgForm_${type.replace(/-/g, "_")}`;
226
+ formImports.push(
227
+ `import ${varName} from ${JSON.stringify(componentPath)};`,
228
+ );
229
+ formEntries.push(`${JSON.stringify(type)}: ${varName}`);
188
230
  }
189
231
  }
190
232
 
191
- // Auto-register only when no user entry was provided.
192
- // When the user provides client/index.ts, they control registration.
193
- if (!hasUserEntry) {
194
- const defVarNames: string[] = [];
195
- for (const type of _nodeTypes) {
196
- const varName = `__nrgNodeDef_${type.replace(/-/g, "_")}`;
197
- const tsPath = nodesDir ? path.resolve(nodesDir, `${type}.ts`) : null;
198
-
199
- if (tsPath && fs.existsSync(tsPath)) {
200
- lines.push(`import ${varName} from ${JSON.stringify(tsPath)};`);
201
- } else {
202
- lines.push(`const ${varName} = { type: ${JSON.stringify(type)} };`);
203
- }
204
- defVarNames.push(varName);
205
- }
233
+ if (formImports.length > 0) {
234
+ lines.push(...formImports);
235
+ nrgImports.add("__setForms");
236
+ postLines.push(`__setForms({ ${formEntries.join(", ")} });`);
237
+ }
238
+ }
206
239
 
207
- if (defVarNames.length > 0) {
208
- nrgImports.add("registerTypes");
209
- postLines.push(`registerTypes([${defVarNames.join(", ")}]);`);
210
- }
240
+ // Auto-register only when no user entry was provided.
241
+ if (!hasUserEntry) {
242
+ const nodesCache = path.resolve(cacheDir, "nodes");
243
+ const defVarNames: string[] = [];
244
+
245
+ for (const type of _nodeTypes) {
246
+ const varName = `__nrgNodeDef_${type.replace(/-/g, "_")}`;
247
+ // Use physical file if user has one, otherwise use cached generated file
248
+ const userTsPath = nodesDir
249
+ ? path.resolve(nodesDir, `${type}.ts`)
250
+ : null;
251
+ const tsPath =
252
+ userTsPath && fs.existsSync(userTsPath)
253
+ ? userTsPath
254
+ : path.resolve(nodesCache, `${type}.ts`);
255
+ lines.push(`import ${varName} from ${JSON.stringify(tsPath)};`);
256
+ defVarNames.push(varName);
211
257
  }
212
258
 
213
- // Build the @bonsae/nrg/client import line
214
- const importLine = `import { ${[...nrgImports].join(", ")} } from "@bonsae/nrg/client";`;
215
- lines.splice(1, 0, importLine);
259
+ if (defVarNames.length > 0) {
260
+ nrgImports.add("registerTypes");
261
+ postLines.push(`registerTypes([${defVarNames.join(", ")}]);`);
262
+ }
263
+ }
216
264
 
217
- lines.push(...postLines);
218
- lines.push("");
219
- return { code: lines.join("\n") + code, map: null };
220
- },
221
- };
265
+ // Build the @bonsae/nrg/client import line
266
+ const importLine = `import { ${[...nrgImports].join(", ")} } from "@bonsae/nrg/client";`;
267
+ lines.splice(1, 0, importLine);
268
+
269
+ lines.push(...postLines);
270
+ lines.push("");
271
+ return lines.join("\n") + userCode;
272
+ }
222
273
  }
223
274
 
224
275
  export { nodeDefinitionsInliner };
package/src/vite/index.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  export { nodeRed } from "./plugin";
2
- export { defineRuntimeSettings } from "./utils";
3
- export type { NodeRedPluginOptions, NodeRedRuntimeSettings } from "./types";
2
+ export type { NodeRedPluginOptions } from "./types";
package/src/vite/types.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { Plugin } from "vite";
2
2
  import type { NodeRedLauncher } from "./node-red-launcher";
3
- import type { Http2ServerRequest } from "http2";
4
3
 
5
4
  interface BuildContext {
6
5
  outDir: string;
@@ -105,199 +104,6 @@ interface NodeRedPluginOptions {
105
104
  extraFilesCopyTargets?: CopyTarget[];
106
105
  }
107
106
 
108
- interface NodeRedRuntimeSettings {
109
- // NOTE: commented out because the preferred port must be set using NodeRedLauncherOptions.runtime.port
110
- // uiPort?: number;
111
- // NOTE: commented out because the plugin set it to 127.0.0.1
112
- // uiHost?: string;
113
- userDir?: string;
114
- nodesDir?: string | string[];
115
- flowFile?: string;
116
- flowFilePretty?: boolean;
117
- credentialSecret?: string | false;
118
- requireHttps?: boolean;
119
- https?:
120
- | { key: string; cert: string }
121
- | (() =>
122
- | Promise<{ key: string; cert: string }>
123
- | { key: string; cert: string });
124
- httpsRefreshInterval?: number;
125
- httpAdminRoot?: string;
126
- httpNodeRoot?: string;
127
- httpNodeCors?: { origin: string; methods: string };
128
- httpStatic?: string | { path: string; root: string }[];
129
- httpStaticRoot?: string;
130
- httpAdminMiddleware?: (req: unknown, res: unknown, next: () => void) => void;
131
- httpNodeMiddleware?: (req: unknown, res: unknown, next: () => void) => void;
132
- httpServerOptions?: Record<string, unknown>;
133
- adminAuth?: {
134
- type?: "credentials" | "strategy";
135
- users?: {
136
- username: string;
137
- password: string;
138
- permissions?: string | string[];
139
- }[];
140
- default?: {
141
- permissions?: string | string[];
142
- };
143
- tokens?: (
144
- token: string,
145
- ) => Promise<{ user: string; permissions: string | string[] } | null>;
146
- tokenHeader: "string";
147
- sessionExpiryTime?: number;
148
- [key: string]: unknown;
149
- };
150
- httpNodeAuth?: {
151
- user?: string;
152
- pass?: string;
153
- };
154
- httpStaticAuth?: {
155
- user?: string;
156
- pass?: string;
157
- };
158
- lang?:
159
- | "en-US"
160
- | "de"
161
- | "es-ES"
162
- | "fr"
163
- | "ko"
164
- | "pt-BR"
165
- | "ru"
166
- | "ja"
167
- | "zh-CN"
168
- | "zh-TW";
169
- diagnostics?: {
170
- enabled?: boolean;
171
- ui?: boolean;
172
- };
173
- runtimeState?: {
174
- enabled?: boolean;
175
- ui?: boolean;
176
- };
177
- disableEditor?: boolean;
178
- editorTheme?: {
179
- page?: {
180
- title?: string;
181
- favicon?: string;
182
- css?: string | string[];
183
- scripts?: string | string[];
184
- };
185
- header?: {
186
- title?: string;
187
- image?: string;
188
- url?: string;
189
- };
190
- deployButton?: {
191
- type?: "simple" | "default";
192
- label?: string;
193
- icon?: string;
194
- };
195
- menu?: {
196
- "menu-item-import-library"?: boolean;
197
- "menu-item-export-library"?: boolean;
198
- "menu-item-keyboard-shortcuts"?: boolean;
199
- "menu-item-help"?: {
200
- label?: string;
201
- url?: string;
202
- };
203
- [menuItem: string]:
204
- | boolean
205
- | { label?: string; url?: string }
206
- | undefined;
207
- };
208
- userMenu?: boolean;
209
- login?: {
210
- image?: string;
211
- };
212
- logout?: {
213
- redirect?: string;
214
- };
215
- palette?: {
216
- catalogues?: string[];
217
- categories?: string[];
218
- theme?: { category: string; type: string; color: string }[];
219
- };
220
- projects?: {
221
- enabled?: boolean;
222
- workflow?: {
223
- mode: "manual" | "auto";
224
- };
225
- };
226
- codeEditor?: {
227
- lib?: "monaco" | "ace";
228
- // TODO: add monaco option types
229
- options?: Record<string, unknown>;
230
- };
231
- mermaid?: {
232
- theme?: string;
233
- };
234
- tours?: boolean;
235
- theme?: string;
236
- [key: string]: unknown;
237
- };
238
- contextStorage?: {
239
- default?: {
240
- module?: "memory" | "localfilesystem" | object;
241
- config?: Record<string, unknown>;
242
- };
243
- [store: string]:
244
- | {
245
- module?: "memory" | "localfilesystem" | object;
246
- config?: Record<string, unknown>;
247
- }
248
- | undefined;
249
- };
250
- exportGlobalContextKeys?: boolean;
251
- logging?: {
252
- console?: {
253
- level?: "fatal" | "error" | "warn" | "info" | "debug" | "trace" | "off";
254
- metrics?: boolean;
255
- audit?: boolean;
256
- };
257
- };
258
- fileWorkingDirectory?: string;
259
- functionExternalModules?: boolean;
260
- functionGlobalContext?: Record<string, unknown>;
261
- nodeMessageBufferMaxLength?: number;
262
- functionTimeout?: number;
263
- externalModules?: {
264
- autoInstall?: boolean;
265
- autoInstallRetry?: number;
266
- palette?: {
267
- allowInstall?: boolean;
268
- allowUpdate?: boolean;
269
- allowUpload?: boolean;
270
- allowList?: string[];
271
- denyList?: string[];
272
- allowUpdateList?: string[];
273
- denyUpdateList?: string[];
274
- };
275
- modules?: {
276
- allowInstall?: boolean;
277
- allowList?: string[];
278
- denyList?: string[];
279
- };
280
- };
281
- execMaxBufferSize?: number;
282
- debugMaxLength?: number;
283
- debugUseColors?: boolean;
284
- httpRequestTimeout?: number;
285
- mqttReconnectTime?: number;
286
- serialReconnectTime?: number;
287
- socketReconnectTime?: number;
288
- socketTimeout?: number;
289
- tcpMsgQueueSize?: number;
290
- inboundWebSocketTimeout?: number;
291
- tlsConfigDisableLocalFiles?: boolean;
292
- webSocketNodeVerifyClient?: (info: {
293
- origin: string;
294
- req: Http2ServerRequest;
295
- secure: boolean;
296
- }) => boolean;
297
- apiMaxLength?: string;
298
- [key: string]: unknown;
299
- }
300
-
301
107
  interface PackageJson {
302
108
  name: string;
303
109
  version: string;
@@ -362,7 +168,6 @@ export {
362
168
  LoggerOptions,
363
169
  NodeRedLauncherOptions,
364
170
  NodeRedPluginOptions,
365
- NodeRedRuntimeSettings,
366
171
  PackageJson,
367
172
  ServerBuildOptions,
368
173
  ServerPluginOptions,
package/src/vite/utils.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import type { NodeRedRuntimeSettings, PackageJson } from "./types";
3
+ import type { PackageJson } from "./types";
4
4
 
5
5
  function cleanDir(dir: string) {
6
6
  if (fs.existsSync(dir)) {
@@ -69,35 +69,4 @@ function mergeOptions<T extends Record<string, unknown>>(
69
69
  return result;
70
70
  }
71
71
 
72
- /**
73
- * Define Node-RED runtime settings with full type support.
74
- *
75
- * Note: All paths should be absolute. Use `import.meta.dirname` or `__dirname` to resolve relative paths.
76
- *
77
- * @example
78
- * ```typescript
79
- * import { defineRuntimeSettings } from "./vite/utils";
80
- * import path from "path";
81
- *
82
- * const __dirname = import.meta.dirname;
83
- *
84
- * export default defineRuntimeSettings({
85
- * userDir: path.resolve(__dirname, ".node-red"),
86
- * flowFile: "flows.json",
87
- * httpStatic: path.resolve(__dirname, "public"),
88
- * });
89
- * ```
90
- */
91
- function defineRuntimeSettings(
92
- settings: NodeRedRuntimeSettings,
93
- ): NodeRedRuntimeSettings {
94
- return settings;
95
- }
96
-
97
- export {
98
- cleanDir,
99
- copyFiles,
100
- defineRuntimeSettings,
101
- getPackageName,
102
- mergeOptions,
103
- };
72
+ export { cleanDir, copyFiles, getPackageName, mergeOptions };