@bonsae/nrg 0.21.2 → 0.22.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 (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -3
  3. package/package.json +14 -9
  4. package/schemas/labels.schema.json +15 -5
  5. package/server/index.cjs +2 -1358
  6. package/test/client/component/index.js +41 -224
  7. package/test/client/component/setup.js +201 -1475
  8. package/test/client/e2e/config.js +12 -0
  9. package/test/client/e2e/index.js +419 -199
  10. package/test/client/unit/index.js +19 -32
  11. package/test/client/unit/setup.js +28 -21
  12. package/test/server/integration/index.js +2 -26
  13. package/test/server/unit/index.js +2 -184
  14. package/types/client.d.ts +1 -266
  15. package/types/server.d.ts +1 -900
  16. package/types/test-client-component.d.ts +7 -143
  17. package/types/test-client-e2e.d.ts +0 -6
  18. package/types/test-client-unit.d.ts +11 -105
  19. package/types/test-server-integration.d.ts +73 -49
  20. package/types/test-server-unit.d.ts +26 -2
  21. package/types/vite.d.ts +2 -0
  22. package/vite/index.js +378 -150
  23. package/server/resources/nrg-client.js +0 -7493
  24. package/test/client/component/nrg.css +0 -1
  25. package/types/shims/brands.d.ts +0 -32
  26. package/types/shims/client/form/components/node-red-config-input.vue.d.ts +0 -125
  27. package/types/shims/client/form/components/node-red-editor-input.vue.d.ts +0 -124
  28. package/types/shims/client/form/components/node-red-input-label.vue.d.ts +0 -34
  29. package/types/shims/client/form/components/node-red-input.vue.d.ts +0 -123
  30. package/types/shims/client/form/components/node-red-json-schema-form.vue.d.ts +0 -772
  31. package/types/shims/client/form/components/node-red-select-input.vue.d.ts +0 -132
  32. package/types/shims/client/form/components/node-red-toggle.vue.d.ts +0 -36
  33. package/types/shims/client/form/components/node-red-typed-input.vue.d.ts +0 -151
  34. package/types/shims/client/globals.d.ts +0 -320
  35. package/types/shims/client/types.d.ts +0 -227
  36. package/types/shims/components.d.ts +0 -23
  37. package/types/shims/constants.d.ts +0 -4
  38. package/types/shims/schema-options.d.ts +0 -24
  39. package/types/shims/shims-vue.d.ts +0 -5
  40. package/types/shims/typebox.d.ts +0 -10
@@ -1,10 +1,10 @@
1
1
  // src/test/client/e2e/index.ts
2
- import fs14 from "fs";
3
- import path13 from "path";
2
+ import fs15 from "fs";
3
+ import path14 from "path";
4
4
 
5
5
  // src/test/client/e2e/environment.ts
6
- import fs13 from "fs";
7
- import path12 from "path";
6
+ import fs14 from "fs";
7
+ import path13 from "path";
8
8
 
9
9
  // src/vite/server/build.ts
10
10
  import { build as viteBuild } from "vite";
@@ -115,6 +115,26 @@ import dts from "vite-plugin-dts";
115
115
  import fs from "fs";
116
116
  import path from "path";
117
117
  import ts from "typescript";
118
+ var RUNTIME_TYPE_REWRITES = {
119
+ "@bonsae/nrg/server": "@bonsae/nrg-runtime/server",
120
+ "@bonsae/nrg/client": "@bonsae/nrg-runtime/client"
121
+ };
122
+ function rewriteRuntimeTypeImports(outDir, entryNames) {
123
+ for (const name of entryNames) {
124
+ const dtsPath = path.join(outDir, `${name}.d.ts`);
125
+ if (!fs.existsSync(dtsPath)) continue;
126
+ const original = fs.readFileSync(dtsPath, "utf-8");
127
+ let rewritten = original;
128
+ for (const [from, to] of Object.entries(RUNTIME_TYPE_REWRITES)) {
129
+ const escaped = from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
130
+ const re = new RegExp(`(['"])${escaped}\\1`, "g");
131
+ rewritten = rewritten.replace(re, `$1${to}$1`);
132
+ }
133
+ if (rewritten !== original) {
134
+ fs.writeFileSync(dtsPath, rewritten);
135
+ }
136
+ }
137
+ }
118
138
  function collectTsFiles(dir) {
119
139
  if (!fs.existsSync(dir)) return [];
120
140
  return fs.readdirSync(dir, { withFileTypes: true }).flatMap((dirent) => {
@@ -489,18 +509,23 @@ ${reexports}
489
509
  }
490
510
 
491
511
  // src/vite/server/plugins/output-wrapper.ts
492
- function cjsWrapper() {
512
+ function nrgServerSpecifier(isDev) {
513
+ return isDev ? "@bonsae/nrg/server" : "@bonsae/nrg-runtime/server";
514
+ }
515
+ function cjsWrapper(isDev = false) {
516
+ const serverSpecifier = nrgServerSpecifier(isDev);
493
517
  return {
494
518
  name: "vite-plugin-node-red:server:cjs-wrapper",
495
519
  renderChunk(code, chunk, outputOptions) {
496
520
  if (!chunk.isEntry || outputOptions.format !== "cjs") return null;
497
- const footer = `(function(){var _exp=module.exports&&module.exports.__esModule?module.exports.default:module.exports;if(_exp&&typeof _exp==="object"&&Array.isArray(_exp.nodes)){var _nrg=require("@bonsae/nrg/server");module.exports=_nrg.registerTypes(_exp.nodes);}else if(typeof _exp==="function"&&Array.isArray(_exp.nodes)){module.exports=_exp;_exp.nodes.forEach(function(cls){if(cls&&cls.type){_exp[cls.type.replace(/(?:^|[-_])(\\w)/g,function(_,c){return c.toUpperCase();})] = cls;}});}})();`;
521
+ const footer = `(function(){var _exp=module.exports&&module.exports.__esModule?module.exports.default:module.exports;if(_exp&&typeof _exp==="object"&&Array.isArray(_exp.nodes)){var _nrg=require(${JSON.stringify(serverSpecifier)});module.exports=_nrg.registerTypes(_exp.nodes);}else if(typeof _exp==="function"&&Array.isArray(_exp.nodes)){module.exports=_exp;_exp.nodes.forEach(function(cls){if(cls&&cls.type){_exp[cls.type.replace(/(?:^|[-_])(\\w)/g,function(_,c){return c.toUpperCase();})] = cls;}});}})();`;
498
522
  return { code: `${code}
499
523
  ${footer}`, map: null };
500
524
  }
501
525
  };
502
526
  }
503
- function esmWrapper() {
527
+ function esmWrapper(isDev = false) {
528
+ const serverSpecifier = nrgServerSpecifier(isDev);
504
529
  return {
505
530
  name: "vite-plugin-node-red:server:esm-wrapper",
506
531
  renderChunk(code, chunk, outputOptions) {
@@ -526,7 +551,9 @@ function esmWrapper() {
526
551
  `import { dirname as __nrgDirname } from "path";`,
527
552
  `var __filename = __nrgFileURLToPath(import.meta.url);`,
528
553
  `var __dirname = __nrgDirname(__filename);`,
529
- `import { registerTypes as __nrgRegisterTypes } from "@bonsae/nrg/server";`,
554
+ `import { registerTypes as __nrgRegisterTypes } from ${JSON.stringify(
555
+ serverSpecifier
556
+ )};`,
530
557
  ``
531
558
  ].join("\n");
532
559
  const replacement = [
@@ -550,6 +577,12 @@ var nodeBuiltins = /* @__PURE__ */ new Set([
550
577
  ...builtinModules,
551
578
  ...builtinModules.map((m) => `node:${m}`)
552
579
  ]);
580
+ var RUNTIME_REWRITES = {
581
+ "@bonsae/nrg/server": "@bonsae/nrg-runtime/server"
582
+ };
583
+ var RUNTIME_VERSION_SOURCE = {
584
+ "@bonsae/nrg-runtime": "@bonsae/nrg"
585
+ };
553
586
  function buildTypesPath(entryName) {
554
587
  return `./${entryName}.d.ts`;
555
588
  }
@@ -606,7 +639,8 @@ function packageJsonGenerator(options) {
606
639
  bundled = [],
607
640
  types = false,
608
641
  entryNames = [],
609
- format = "cjs"
642
+ format = "cjs",
643
+ isDev = false
610
644
  } = options;
611
645
  const trackedDependencies = /* @__PURE__ */ new Set();
612
646
  return {
@@ -624,12 +658,13 @@ function packageJsonGenerator(options) {
624
658
  if (nodeBuiltins.has(source)) {
625
659
  return { id: source, external: true };
626
660
  }
627
- const packageName = source.startsWith("@") ? source.split("/").slice(0, 2).join("/") : source.split("/")[0];
661
+ const resolved = isDev ? source : RUNTIME_REWRITES[source] ?? source;
662
+ const packageName = resolved.startsWith("@") ? resolved.split("/").slice(0, 2).join("/") : resolved.split("/")[0];
628
663
  if (bundled.includes(packageName)) {
629
664
  return null;
630
665
  }
631
666
  trackedDependencies.add(packageName);
632
- return { id: source, external: true };
667
+ return { id: resolved, external: true };
633
668
  }
634
669
  },
635
670
  closeBundle() {
@@ -642,14 +677,19 @@ function packageJsonGenerator(options) {
642
677
  fs2.readFileSync(rootPackageJsonPath, "utf-8")
643
678
  );
644
679
  const sourceDeps = rootPackageJson.dependencies ?? {};
680
+ const devDeps = rootPackageJson.devDependencies ?? {};
645
681
  const peerDeps = rootPackageJson.peerDependencies ?? {};
646
682
  let distDependencies = {};
647
683
  for (const dep of trackedDependencies) {
648
684
  if (peerDeps[dep]) {
649
685
  continue;
650
686
  }
687
+ const versionSource = RUNTIME_VERSION_SOURCE[dep];
688
+ const sourceVersion = versionSource ? sourceDeps[versionSource] ?? devDeps[versionSource] : void 0;
651
689
  if (sourceDeps[dep]) {
652
690
  distDependencies[dep] = sourceDeps[dep];
691
+ } else if (sourceVersion) {
692
+ distDependencies[dep] = sourceVersion;
653
693
  } else {
654
694
  const dependencyPackageJsonPath = path2.resolve(
655
695
  `./node_modules/${dep}/package.json`
@@ -736,9 +776,10 @@ async function build(serverOpts, buildContext) {
736
776
  bundled,
737
777
  types: types && !buildContext.isDev,
738
778
  entryNames: Object.keys(entryPoints),
739
- format
779
+ format,
780
+ isDev: buildContext.isDev
740
781
  }),
741
- isEsm ? esmWrapper() : cjsWrapper()
782
+ isEsm ? esmWrapper(buildContext.isDev) : cjsWrapper(buildContext.isDev)
742
783
  ];
743
784
  if (types && !buildContext.isDev) {
744
785
  plugins.push(
@@ -780,6 +821,9 @@ async function build(serverOpts, buildContext) {
780
821
  };
781
822
  try {
782
823
  await viteBuild(config);
824
+ if (types && !buildContext.isDev) {
825
+ rewriteRuntimeTypeImports(buildContext.outDir, Object.keys(entryPoints));
826
+ }
783
827
  if (isEsm) {
784
828
  const bridgeCode = `'use strict';
785
829
  // CJS bridge \u2014 auto-generated by @bonsae/nrg/vite
@@ -806,12 +850,13 @@ module.exports = function (RED) {
806
850
  // src/vite/client/build.ts
807
851
  import { build as viteBuild2 } from "vite";
808
852
  import vue from "@vitejs/plugin-vue";
809
- import fs9 from "fs";
810
- import path9 from "path";
853
+ import fs10 from "fs";
854
+ import path10 from "path";
855
+ import crypto from "crypto";
811
856
 
812
857
  // src/vite/client/plugins/help-generator.ts
813
- import fs4 from "fs";
814
- import path4 from "path";
858
+ import fs5 from "fs";
859
+ import path5 from "path";
815
860
  import { pathToFileURL } from "url";
816
861
  import { createRequire } from "module";
817
862
 
@@ -1042,8 +1087,127 @@ function getHelpTranslations(lang) {
1042
1087
  return translations[lang] ?? translations["en-US"];
1043
1088
  }
1044
1089
 
1090
+ // src/vite/client/plugins/unsafe-types.ts
1091
+ import ts2 from "typescript";
1092
+ import fs4 from "fs";
1093
+ import path4 from "path";
1094
+ var NRG_SERVER_MODULE = "@bonsae/nrg/server";
1095
+ function normalizeType(text) {
1096
+ return text.replace(/\s+/g, " ").trim();
1097
+ }
1098
+ function resolveImports(sf) {
1099
+ const defineSchema = /* @__PURE__ */ new Set();
1100
+ const schemaType = /* @__PURE__ */ new Set();
1101
+ sf.forEachChild((node) => {
1102
+ if (!ts2.isImportDeclaration(node) || !ts2.isStringLiteral(node.moduleSpecifier) || node.moduleSpecifier.text !== NRG_SERVER_MODULE || !node.importClause?.namedBindings || !ts2.isNamedImports(node.importClause.namedBindings)) {
1103
+ return;
1104
+ }
1105
+ for (const el of node.importClause.namedBindings.elements) {
1106
+ const imported = (el.propertyName ?? el.name).text;
1107
+ const local = el.name.text;
1108
+ if (imported === "defineSchema") defineSchema.add(local);
1109
+ if (imported === "SchemaType") schemaType.add(local);
1110
+ }
1111
+ });
1112
+ return { defineSchema, schemaType };
1113
+ }
1114
+ function staticPropName(name) {
1115
+ if (ts2.isIdentifier(name) || ts2.isStringLiteralLike(name)) return name.text;
1116
+ return void 0;
1117
+ }
1118
+ function unsafeTypeArg(node, sf, schemaType) {
1119
+ if (!ts2.isCallExpression(node) || !ts2.isPropertyAccessExpression(node.expression) || !ts2.isIdentifier(node.expression.expression) || !schemaType.has(node.expression.expression.text)) {
1120
+ return void 0;
1121
+ }
1122
+ const method = node.expression.name.text;
1123
+ if (method === "Unsafe" && node.typeArguments?.length) {
1124
+ return normalizeType(node.typeArguments[0].getText(sf));
1125
+ }
1126
+ if (method === "Array" && node.arguments.length) {
1127
+ const inner = unsafeTypeArg(node.arguments[0], sf, schemaType);
1128
+ if (inner) return `${inner}[]`;
1129
+ }
1130
+ return void 0;
1131
+ }
1132
+ function readSchemaCall(call, sf, schemaType) {
1133
+ const [propsArg, optsArg] = call.arguments;
1134
+ if (!propsArg || !optsArg || !ts2.isObjectLiteralExpression(propsArg) || !ts2.isObjectLiteralExpression(optsArg)) {
1135
+ return void 0;
1136
+ }
1137
+ let id;
1138
+ for (const p of optsArg.properties) {
1139
+ if (ts2.isPropertyAssignment(p) && staticPropName(p.name) === "$id" && ts2.isStringLiteralLike(p.initializer)) {
1140
+ id = p.initializer.text;
1141
+ }
1142
+ }
1143
+ if (!id) return void 0;
1144
+ const props = {};
1145
+ for (const p of propsArg.properties) {
1146
+ if (!ts2.isPropertyAssignment(p)) continue;
1147
+ const key = staticPropName(p.name);
1148
+ if (key === void 0) continue;
1149
+ const type = unsafeTypeArg(p.initializer, sf, schemaType);
1150
+ if (type) props[key] = type;
1151
+ }
1152
+ return { id, props };
1153
+ }
1154
+ function extractUnsafeTypesFromSource(fileName, code) {
1155
+ const out = /* @__PURE__ */ new Map();
1156
+ const sf = ts2.createSourceFile(fileName, code, ts2.ScriptTarget.Latest, true);
1157
+ const { defineSchema, schemaType } = resolveImports(sf);
1158
+ if (defineSchema.size === 0 || schemaType.size === 0) return out;
1159
+ const visit = (node) => {
1160
+ if (ts2.isCallExpression(node) && ts2.isIdentifier(node.expression) && defineSchema.has(node.expression.text)) {
1161
+ const result = readSchemaCall(node, sf, schemaType);
1162
+ if (result && Object.keys(result.props).length > 0) {
1163
+ if (out.has(result.id)) {
1164
+ throw new Error(
1165
+ `Duplicate schema $id "${result.id}" in ${fileName} \u2014 $id must be unique`
1166
+ );
1167
+ }
1168
+ out.set(result.id, result.props);
1169
+ }
1170
+ }
1171
+ ts2.forEachChild(node, visit);
1172
+ };
1173
+ visit(sf);
1174
+ return out;
1175
+ }
1176
+ function collectTsFiles2(dir) {
1177
+ const out = [];
1178
+ if (!fs4.existsSync(dir)) return out;
1179
+ for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
1180
+ const full = path4.join(dir, entry.name);
1181
+ if (entry.isDirectory()) {
1182
+ if (entry.name === "node_modules") continue;
1183
+ out.push(...collectTsFiles2(full));
1184
+ } else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts") && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".spec.ts")) {
1185
+ out.push(full);
1186
+ }
1187
+ }
1188
+ return out;
1189
+ }
1190
+ function extractUnsafeTypes(srcDir) {
1191
+ const merged = /* @__PURE__ */ new Map();
1192
+ for (const file of collectTsFiles2(srcDir).sort()) {
1193
+ const perFile = extractUnsafeTypesFromSource(
1194
+ file,
1195
+ fs4.readFileSync(file, "utf-8")
1196
+ );
1197
+ for (const [id, props] of perFile) {
1198
+ if (merged.has(id)) {
1199
+ throw new Error(
1200
+ `Duplicate schema $id "${id}" across ${srcDir} \u2014 $id must be unique`
1201
+ );
1202
+ }
1203
+ merged.set(id, props);
1204
+ }
1205
+ }
1206
+ return merged;
1207
+ }
1208
+
1045
1209
  // src/vite/client/plugins/help-generator.ts
1046
- function buildPropertyRow(name, schema, required, label) {
1210
+ function buildPropertyRow(name, schema, required, label, parsedType) {
1047
1211
  let type = "";
1048
1212
  if (schema["x-nrg-node-type"]) {
1049
1213
  type = `NodeRef \u2192 ${schema["x-nrg-node-type"]}`;
@@ -1051,6 +1215,8 @@ function buildPropertyRow(name, schema, required, label) {
1051
1215
  type = "TypedInput";
1052
1216
  } else if (schema.type) {
1053
1217
  type = String(schema.type);
1218
+ } else if (parsedType) {
1219
+ type = parsedType;
1054
1220
  }
1055
1221
  if (schema.enum) type += ` (${schema.enum.join(", ")})`;
1056
1222
  const constraints = [];
@@ -1086,16 +1252,19 @@ function generateSchemaSection(options) {
1086
1252
  t,
1087
1253
  labels,
1088
1254
  heading = "###",
1089
- includeDefault = true
1255
+ includeDefault = true,
1256
+ unsafeTypes
1090
1257
  } = options;
1091
1258
  if (!schema?.properties) return "";
1259
+ const parsed = unsafeTypes?.get(schema.$id);
1092
1260
  const required = new Set(schema.required ?? []);
1093
1261
  const rows = Object.entries(schema.properties).filter(([key]) => !SKIP_FIELDS.has(key)).map(
1094
1262
  ([key, propSchema]) => buildPropertyRow(
1095
1263
  key,
1096
1264
  propSchema,
1097
1265
  required.has(key),
1098
- labels?.[key]
1266
+ labels?.[key],
1267
+ parsed?.[key]
1099
1268
  )
1100
1269
  );
1101
1270
  if (rows.length === 0) return "";
@@ -1133,9 +1302,9 @@ ${table}
1133
1302
  `;
1134
1303
  }
1135
1304
  function loadNodeLabels(labelPath) {
1136
- if (!fs4.existsSync(labelPath)) return {};
1305
+ if (!fs5.existsSync(labelPath)) return {};
1137
1306
  try {
1138
- const raw = JSON.parse(fs4.readFileSync(labelPath, "utf-8"));
1307
+ const raw = JSON.parse(fs5.readFileSync(labelPath, "utf-8"));
1139
1308
  return {
1140
1309
  description: raw.description,
1141
1310
  configs: raw.configs,
@@ -1147,7 +1316,7 @@ function loadNodeLabels(labelPath) {
1147
1316
  return {};
1148
1317
  }
1149
1318
  }
1150
- function generateHelpDoc(nodeClass, labels, t) {
1319
+ function generateHelpDoc(nodeClass, labels, t, unsafeTypes) {
1151
1320
  const lines = [];
1152
1321
  if (labels.description) {
1153
1322
  lines.push(`<p>${labels.description}</p>`);
@@ -1156,14 +1325,16 @@ function generateHelpDoc(nodeClass, labels, t) {
1156
1325
  title: t.sections.properties,
1157
1326
  schema: nodeClass.configSchema,
1158
1327
  t,
1159
- labels: labels.configs
1328
+ labels: labels.configs,
1329
+ unsafeTypes
1160
1330
  });
1161
1331
  if (configSection) lines.push(configSection);
1162
1332
  const credsSection = generateSchemaSection({
1163
1333
  title: t.sections.credentials,
1164
1334
  schema: nodeClass.credentialsSchema,
1165
1335
  t,
1166
- labels: labels.credentials
1336
+ labels: labels.credentials,
1337
+ unsafeTypes
1167
1338
  });
1168
1339
  if (credsSection) lines.push(credsSection);
1169
1340
  if (nodeClass.inputSchema) {
@@ -1172,7 +1343,8 @@ function generateHelpDoc(nodeClass, labels, t) {
1172
1343
  schema: nodeClass.inputSchema,
1173
1344
  t,
1174
1345
  labels: labels.input,
1175
- includeDefault: false
1346
+ includeDefault: false,
1347
+ unsafeTypes
1176
1348
  });
1177
1349
  if (inputSection) lines.push(inputSection);
1178
1350
  }
@@ -1189,7 +1361,8 @@ function generateHelpDoc(nodeClass, labels, t) {
1189
1361
  t,
1190
1362
  labels: portPropLabels,
1191
1363
  heading: "####",
1192
- includeDefault: false
1364
+ includeDefault: false,
1365
+ unsafeTypes
1193
1366
  });
1194
1367
  if (section) portSections.push(section);
1195
1368
  });
@@ -1209,7 +1382,8 @@ ${portSections.join("\n")}`
1209
1382
  t,
1210
1383
  labels: portPropLabels,
1211
1384
  heading: "####",
1212
- includeDefault: false
1385
+ includeDefault: false,
1386
+ unsafeTypes
1213
1387
  });
1214
1388
  if (section) portSections.push(section);
1215
1389
  }
@@ -1226,7 +1400,8 @@ ${portSections.join("\n")}`
1226
1400
  schema: os3,
1227
1401
  t,
1228
1402
  labels: outputPropLabels,
1229
- includeDefault: false
1403
+ includeDefault: false,
1404
+ unsafeTypes
1230
1405
  });
1231
1406
  if (section) lines.push(section);
1232
1407
  }
@@ -1234,26 +1409,26 @@ ${portSections.join("\n")}`
1234
1409
  return lines.join("\n").trim();
1235
1410
  }
1236
1411
  function discoverLanguages(labelsDir, nodeType) {
1237
- const nodeLabelsDir = path4.join(labelsDir, nodeType);
1238
- if (!fs4.existsSync(nodeLabelsDir)) return [];
1239
- return fs4.readdirSync(nodeLabelsDir).filter((f) => f.endsWith(".json")).map((f) => path4.basename(f, ".json"));
1412
+ const nodeLabelsDir = path5.join(labelsDir, nodeType);
1413
+ if (!fs5.existsSync(nodeLabelsDir)) return [];
1414
+ return fs5.readdirSync(nodeLabelsDir).filter((f) => f.endsWith(".json")).map((f) => path5.basename(f, ".json"));
1240
1415
  }
1241
1416
  function helpGenerator(options) {
1242
- const { outDir, localesOutDir, docsDir, labelsDir } = options;
1417
+ const { outDir, localesOutDir, docsDir, labelsDir, srcDir } = options;
1243
1418
  return {
1244
1419
  name: "vite-plugin-node-red:client:help-generator",
1245
1420
  apply: "build",
1246
1421
  enforce: "post",
1247
1422
  async closeBundle() {
1248
- const esmPath = path4.resolve(outDir, "index.mjs");
1249
- const cjsPath = path4.resolve(outDir, "index.js");
1423
+ const esmPath = path5.resolve(outDir, "index.mjs");
1424
+ const cjsPath = path5.resolve(outDir, "index.js");
1250
1425
  let packageFn;
1251
1426
  try {
1252
- if (fs4.existsSync(esmPath)) {
1427
+ if (fs5.existsSync(esmPath)) {
1253
1428
  const fileUrl = pathToFileURL(esmPath).href + `?t=${Date.now()}`;
1254
1429
  const mod = await import(fileUrl);
1255
1430
  packageFn = mod?.default ?? mod;
1256
- } else if (fs4.existsSync(cjsPath)) {
1431
+ } else if (fs5.existsSync(cjsPath)) {
1257
1432
  const require2 = createRequire(import.meta.url);
1258
1433
  delete require2.cache[cjsPath];
1259
1434
  const rawMod = require2(cjsPath);
@@ -1263,6 +1438,7 @@ function helpGenerator(options) {
1263
1438
  return;
1264
1439
  }
1265
1440
  const nodeClasses = packageFn?.nodes ?? [];
1441
+ const unsafeTypes = srcDir ? extractUnsafeTypes(srcDir) : void 0;
1266
1442
  const helpByLang = /* @__PURE__ */ new Map();
1267
1443
  for (const NodeClass of nodeClasses) {
1268
1444
  const type = NodeClass.type;
@@ -1270,13 +1446,13 @@ function helpGenerator(options) {
1270
1446
  const languages = discoverLanguages(labelsDir, type);
1271
1447
  if (!languages.includes("en-US")) languages.push("en-US");
1272
1448
  for (const lang of languages) {
1273
- const manualMd = path4.join(docsDir, type, `${lang}.md`);
1274
- const manualHtml = path4.join(docsDir, type, `${lang}.html`);
1275
- if (fs4.existsSync(manualMd) || fs4.existsSync(manualHtml)) continue;
1276
- const labelPath = path4.join(labelsDir, type, `${lang}.json`);
1449
+ const manualMd = path5.join(docsDir, type, `${lang}.md`);
1450
+ const manualHtml = path5.join(docsDir, type, `${lang}.html`);
1451
+ if (fs5.existsSync(manualMd) || fs5.existsSync(manualHtml)) continue;
1452
+ const labelPath = path5.join(labelsDir, type, `${lang}.json`);
1277
1453
  const labels = loadNodeLabels(labelPath);
1278
1454
  const t = getHelpTranslations(lang);
1279
- const content = generateHelpDoc(NodeClass, labels, t);
1455
+ const content = generateHelpDoc(NodeClass, labels, t, unsafeTypes);
1280
1456
  if (!content) continue;
1281
1457
  if (!helpByLang.has(lang)) helpByLang.set(lang, []);
1282
1458
  helpByLang.get(lang).push(
@@ -1287,11 +1463,11 @@ ${content}
1287
1463
  }
1288
1464
  }
1289
1465
  for (const [lang, scripts] of helpByLang) {
1290
- const langDir = path4.join(localesOutDir, lang);
1291
- fs4.mkdirSync(langDir, { recursive: true });
1292
- const indexPath = path4.join(langDir, "index.html");
1293
- const existing = fs4.existsSync(indexPath) ? fs4.readFileSync(indexPath, "utf-8") : "";
1294
- fs4.writeFileSync(
1466
+ const langDir = path5.join(localesOutDir, lang);
1467
+ fs5.mkdirSync(langDir, { recursive: true });
1468
+ const indexPath = path5.join(langDir, "index.html");
1469
+ const existing = fs5.existsSync(indexPath) ? fs5.readFileSync(indexPath, "utf-8") : "";
1470
+ fs5.writeFileSync(
1295
1471
  indexPath,
1296
1472
  existing + (existing ? "\n" : "") + scripts.join("\n"),
1297
1473
  "utf-8"
@@ -1303,8 +1479,8 @@ ${content}
1303
1479
 
1304
1480
  // src/vite/client/plugins/html-generator.ts
1305
1481
  import mime from "mime-types";
1306
- import fs5 from "fs";
1307
- import path5 from "path";
1482
+ import fs6 from "fs";
1483
+ import path6 from "path";
1308
1484
  function htmlGenerator(options) {
1309
1485
  const { packageName, licensePath } = options;
1310
1486
  return {
@@ -1314,7 +1490,7 @@ function htmlGenerator(options) {
1314
1490
  generateBundle(_, bundle) {
1315
1491
  const resourcesTags = Object.keys(bundle).map((fileName) => {
1316
1492
  const asset = bundle[fileName];
1317
- const srcPath = path5.join(
1493
+ const srcPath = path6.join(
1318
1494
  "resources",
1319
1495
  packageName,
1320
1496
  fileName.replace(/^resources\/?/, "")
@@ -1342,8 +1518,8 @@ function htmlGenerator(options) {
1342
1518
  return null;
1343
1519
  }
1344
1520
  }).filter(Boolean).join("\n");
1345
- const licenseBanner = licensePath && fs5.existsSync(licensePath) ? `<!--
1346
- ${fs5.readFileSync(licensePath, "utf-8")}
1521
+ const licenseBanner = licensePath && fs6.existsSync(licensePath) ? `<!--
1522
+ ${fs6.readFileSync(licensePath, "utf-8")}
1347
1523
  -->` : "";
1348
1524
  this.emitFile({
1349
1525
  type: "asset",
@@ -1356,8 +1532,8 @@ ${resourcesTags}`
1356
1532
  }
1357
1533
 
1358
1534
  // src/vite/client/plugins/locales-generator.ts
1359
- import fs6 from "fs";
1360
- import path6 from "path";
1535
+ import fs7 from "fs";
1536
+ import path7 from "path";
1361
1537
  import { merge } from "es-toolkit";
1362
1538
  function localesGenerator(options) {
1363
1539
  const { outDir, docsDir, labelsDir } = options;
@@ -1740,17 +1916,17 @@ Supported: ${languages.join(", ")}`
1740
1916
  }
1741
1917
  function forEachFile(baseDir, fileExtensions, processFile) {
1742
1918
  const langMap = /* @__PURE__ */ new Map();
1743
- if (!fs6.existsSync(baseDir)) return langMap;
1744
- const nodeDirs = fs6.readdirSync(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory());
1919
+ if (!fs7.existsSync(baseDir)) return langMap;
1920
+ const nodeDirs = fs7.readdirSync(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory());
1745
1921
  for (const nodeDir of nodeDirs) {
1746
1922
  const nodeType = nodeDir.name;
1747
- const nodePath = path6.join(baseDir, nodeType);
1748
- const files = fs6.readdirSync(nodePath);
1923
+ const nodePath = path7.join(baseDir, nodeType);
1924
+ const files = fs7.readdirSync(nodePath);
1749
1925
  for (const file of files) {
1750
- const ext = path6.extname(file);
1926
+ const ext = path7.extname(file);
1751
1927
  if (!fileExtensions.includes(ext)) continue;
1752
- const lang = path6.basename(file, ext);
1753
- const filePath = path6.join(nodePath, file);
1928
+ const lang = path7.basename(file, ext);
1929
+ const filePath = path7.join(nodePath, file);
1754
1930
  validateLanguage(lang, filePath);
1755
1931
  const value = processFile({ ext, filePath, nodeType });
1756
1932
  if (value == null) continue;
@@ -1768,10 +1944,10 @@ Supported: ${languages.join(", ")}`
1768
1944
  }
1769
1945
  function writeOutput(langMap, fileName, serialize) {
1770
1946
  for (const [lang, data] of langMap.entries()) {
1771
- const langOutDir = path6.join(outDir, lang);
1772
- fs6.mkdirSync(langOutDir, { recursive: true });
1773
- fs6.writeFileSync(
1774
- path6.join(langOutDir, fileName),
1947
+ const langOutDir = path7.join(outDir, lang);
1948
+ fs7.mkdirSync(langOutDir, { recursive: true });
1949
+ fs7.writeFileSync(
1950
+ path7.join(langOutDir, fileName),
1775
1951
  serialize(data),
1776
1952
  "utf-8"
1777
1953
  );
@@ -1783,7 +1959,7 @@ Supported: ${languages.join(", ")}`
1783
1959
  ({ ext, filePath, nodeType }) => {
1784
1960
  const type = ext === ".html" ? "text/html" : ext === ".md" ? "text/markdown" : null;
1785
1961
  if (!type) return null;
1786
- const content = fs6.readFileSync(filePath, "utf-8");
1962
+ const content = fs7.readFileSync(filePath, "utf-8");
1787
1963
  return [
1788
1964
  `<script type="${type}" data-help-name="${nodeType}">
1789
1965
  ${content}
@@ -1800,7 +1976,7 @@ ${content}
1800
1976
  labelsDir,
1801
1977
  [".json"],
1802
1978
  ({ filePath, nodeType }) => {
1803
- const parsed = JSON.parse(fs6.readFileSync(filePath, "utf-8"));
1979
+ const parsed = JSON.parse(fs7.readFileSync(filePath, "utf-8"));
1804
1980
  if (parsed[nodeType] && typeof parsed[nodeType] === "object") {
1805
1981
  console.warn(
1806
1982
  `[locales] Warning: "${filePath}" uses nested format (root key "${nodeType}"). Label files should be flat \u2014 the node type is added automatically. See https://bonsaedev.github.io/nrg/guide/building-and-running`
@@ -1850,8 +2026,8 @@ function minifier() {
1850
2026
  // src/vite/client/plugins/node-definitions-inliner.ts
1851
2027
  import { createRequire as createRequire2 } from "module";
1852
2028
  import { pathToFileURL as pathToFileURL2 } from "url";
1853
- import path7 from "path";
1854
- import fs7 from "fs";
2029
+ import path8 from "path";
2030
+ import fs8 from "fs";
1855
2031
  import mime2 from "mime-types";
1856
2032
  var VIRTUAL_ID = "virtual:nrg/node-definitions";
1857
2033
  var RESOLVED_ID = "\0" + VIRTUAL_ID;
@@ -1882,17 +2058,68 @@ function getCredentialsFromSchema(schema) {
1882
2058
  return result;
1883
2059
  }
1884
2060
  function resolveIcon(iconsDir, type) {
1885
- if (!fs7.existsSync(iconsDir)) return void 0;
1886
- return fs7.readdirSync(iconsDir).find((f) => {
1887
- if (path7.basename(f, path7.extname(f)) !== type) return false;
2061
+ if (!fs8.existsSync(iconsDir)) return void 0;
2062
+ return fs8.readdirSync(iconsDir).find((f) => {
2063
+ if (path8.basename(f, path8.extname(f)) !== type) return false;
1888
2064
  const mimeType = mime2.lookup(f);
1889
2065
  return mimeType !== false && mimeType.startsWith("image/");
1890
2066
  });
1891
2067
  }
1892
- function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir, nodesDir, hasUserEntry = true) {
2068
+ var RUNTIME_SPECIFIER = "@bonsae/nrg-runtime/server";
2069
+ var RUNTIME_SPECIFIER_RE = /(['"])@bonsae\/nrg-runtime\/server\1/g;
2070
+ function resolveRuntimeServer(serverOutDir) {
2071
+ const roots = [path8.join(serverOutDir, "index.js"), import.meta.url];
2072
+ for (const root of roots) {
2073
+ try {
2074
+ return createRequire2(root).resolve(RUNTIME_SPECIFIER);
2075
+ } catch {
2076
+ }
2077
+ }
2078
+ return void 0;
2079
+ }
2080
+ async function loadServerPackageExport(serverOutDir) {
2081
+ const esmEntryPath = path8.resolve(serverOutDir, "index.mjs");
2082
+ const cjsEntryPath = path8.resolve(serverOutDir, "index.js");
2083
+ const isEsm = fs8.existsSync(esmEntryPath);
2084
+ const entryPath = isEsm ? esmEntryPath : fs8.existsSync(cjsEntryPath) ? cjsEntryPath : void 0;
2085
+ if (!entryPath) return void 0;
2086
+ const code = fs8.readFileSync(entryPath, "utf-8");
2087
+ const runtimeServer = code.includes(RUNTIME_SPECIFIER) ? resolveRuntimeServer(serverOutDir) : void 0;
2088
+ let tempPath;
2089
+ let loadPath = entryPath;
2090
+ if (runtimeServer) {
2091
+ const replacement = isEsm ? JSON.stringify(pathToFileURL2(runtimeServer).href) : JSON.stringify(runtimeServer);
2092
+ const rewritten = code.replace(RUNTIME_SPECIFIER_RE, replacement);
2093
+ tempPath = path8.resolve(
2094
+ serverOutDir,
2095
+ `.nrg-server-${Date.now()}${isEsm ? ".mjs" : ".cjs"}`
2096
+ );
2097
+ fs8.writeFileSync(tempPath, rewritten);
2098
+ loadPath = tempPath;
2099
+ }
2100
+ const require2 = createRequire2(import.meta.url);
2101
+ try {
2102
+ if (isEsm) {
2103
+ const fileUrl = pathToFileURL2(loadPath).href + `?t=${Date.now()}`;
2104
+ const mod = await import(fileUrl);
2105
+ return mod?.default ?? mod;
2106
+ }
2107
+ delete require2.cache[loadPath];
2108
+ const rawMod = require2(loadPath);
2109
+ return rawMod?.default ?? rawMod;
2110
+ } finally {
2111
+ if (tempPath) {
2112
+ try {
2113
+ delete require2.cache[tempPath];
2114
+ fs8.rmSync(tempPath);
2115
+ } catch {
2116
+ }
2117
+ }
2118
+ }
2119
+ }
2120
+ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir, nodesDir, hasUserEntry = true, cacheDir = path8.resolve("node_modules", ".nrg", "client")) {
1893
2121
  let _nodeTypes = [];
1894
2122
  let _definitions = {};
1895
- const cacheDir = path7.resolve("node_modules", ".nrg", "client");
1896
2123
  return {
1897
2124
  name: "vite-plugin-node-red:client:node-definitions-inliner",
1898
2125
  enforce: "pre",
@@ -1901,19 +2128,7 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
1901
2128
  async buildStart() {
1902
2129
  _nodeTypes = [];
1903
2130
  _definitions = {};
1904
- const esmEntryPath = path7.resolve(serverOutDir, "index.mjs");
1905
- const cjsEntryPath = path7.resolve(serverOutDir, "index.js");
1906
- let packageFn;
1907
- if (fs7.existsSync(esmEntryPath)) {
1908
- const fileUrl = pathToFileURL2(esmEntryPath).href + `?t=${Date.now()}`;
1909
- const mod = await import(fileUrl);
1910
- packageFn = mod?.default ?? mod;
1911
- } else if (fs7.existsSync(cjsEntryPath)) {
1912
- const require2 = createRequire2(import.meta.url);
1913
- delete require2.cache[cjsEntryPath];
1914
- const rawMod = require2(cjsEntryPath);
1915
- packageFn = rawMod?.default ?? rawMod;
1916
- }
2131
+ const packageFn = await loadServerPackageExport(serverOutDir);
1917
2132
  const nodeClasses = packageFn?.nodes ?? [];
1918
2133
  for (const NodeClass of nodeClasses) {
1919
2134
  const type = NodeClass.type;
@@ -1952,14 +2167,14 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
1952
2167
  };
1953
2168
  }
1954
2169
  if (!hasUserEntry) {
1955
- const nodesCache = path7.resolve(cacheDir, "nodes");
1956
- if (fs7.existsSync(nodesCache)) {
1957
- fs7.rmSync(nodesCache, { recursive: true });
2170
+ const nodesCache = path8.resolve(cacheDir, "nodes");
2171
+ if (fs8.existsSync(nodesCache)) {
2172
+ fs8.rmSync(nodesCache, { recursive: true });
1958
2173
  }
1959
- fs7.mkdirSync(nodesCache, { recursive: true });
2174
+ fs8.mkdirSync(nodesCache, { recursive: true });
1960
2175
  for (const type of _nodeTypes) {
1961
- const userTsPath = nodesDir ? path7.resolve(nodesDir, `${type}.ts`) : null;
1962
- if (userTsPath && fs7.existsSync(userTsPath)) continue;
2176
+ const userTsPath = nodesDir ? path8.resolve(nodesDir, `${type}.ts`) : null;
2177
+ if (userTsPath && fs8.existsSync(userTsPath)) continue;
1963
2178
  const content = [
1964
2179
  `// auto-generated by nrg`,
1965
2180
  `import { defineNode } from "@bonsae/nrg/client";`,
@@ -1969,13 +2184,13 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
1969
2184
  `});`,
1970
2185
  ``
1971
2186
  ].join("\n");
1972
- fs7.writeFileSync(path7.resolve(nodesCache, `${type}.ts`), content);
2187
+ fs8.writeFileSync(path8.resolve(nodesCache, `${type}.ts`), content);
1973
2188
  }
1974
2189
  const entryContent = generateEntryCode("");
1975
- fs7.mkdirSync(path7.dirname(path7.resolve(cacheDir, "index.ts")), {
2190
+ fs8.mkdirSync(path8.dirname(path8.resolve(cacheDir, "index.ts")), {
1976
2191
  recursive: true
1977
2192
  });
1978
- fs7.writeFileSync(path7.resolve(cacheDir, "index.ts"), entryContent);
2193
+ fs8.writeFileSync(path8.resolve(cacheDir, "index.ts"), entryContent);
1979
2194
  }
1980
2195
  },
1981
2196
  resolveId(id) {
@@ -1998,12 +2213,12 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
1998
2213
  const nrgImports = /* @__PURE__ */ new Set(["__setSchemas"]);
1999
2214
  const lines = [`import __nrgSchemas from "${VIRTUAL_ID}";`];
2000
2215
  const postLines = [`__setSchemas(__nrgSchemas);`];
2001
- if (componentsDir && fs7.existsSync(componentsDir)) {
2216
+ if (componentsDir && fs8.existsSync(componentsDir)) {
2002
2217
  const formImports = [];
2003
2218
  const formEntries = [];
2004
2219
  for (const type of _nodeTypes) {
2005
- const componentPath = path7.resolve(componentsDir, `${type}.vue`);
2006
- if (fs7.existsSync(componentPath)) {
2220
+ const componentPath = path8.resolve(componentsDir, `${type}.vue`);
2221
+ if (fs8.existsSync(componentPath)) {
2007
2222
  const varName = `__nrgForm_${type.replace(/-/g, "_")}`;
2008
2223
  formImports.push(
2009
2224
  `import ${varName} from ${JSON.stringify(componentPath)};`
@@ -2018,12 +2233,12 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2018
2233
  }
2019
2234
  }
2020
2235
  if (!hasUserEntry) {
2021
- const nodesCache = path7.resolve(cacheDir, "nodes");
2236
+ const nodesCache = path8.resolve(cacheDir, "nodes");
2022
2237
  const defVarNames = [];
2023
2238
  for (const type of _nodeTypes) {
2024
2239
  const varName = `__nrgNodeDef_${type.replace(/-/g, "_")}`;
2025
- const userTsPath = nodesDir ? path7.resolve(nodesDir, `${type}.ts`) : null;
2026
- const tsPath = userTsPath && fs7.existsSync(userTsPath) ? userTsPath : path7.resolve(nodesCache, `${type}.ts`);
2240
+ const userTsPath = nodesDir ? path8.resolve(nodesDir, `${type}.ts`) : null;
2241
+ const tsPath = userTsPath && fs8.existsSync(userTsPath) ? userTsPath : path8.resolve(nodesCache, `${type}.ts`);
2027
2242
  lines.push(`import ${varName} from ${JSON.stringify(tsPath)};`);
2028
2243
  defVarNames.push(varName);
2029
2244
  }
@@ -2041,8 +2256,8 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2041
2256
  }
2042
2257
 
2043
2258
  // src/vite/client/plugins/static-copy.ts
2044
- import fs8 from "fs";
2045
- import path8 from "path";
2259
+ import fs9 from "fs";
2260
+ import path9 from "path";
2046
2261
  function staticCopy(options) {
2047
2262
  const { targets } = options;
2048
2263
  return {
@@ -2051,23 +2266,23 @@ function staticCopy(options) {
2051
2266
  enforce: "post",
2052
2267
  closeBundle() {
2053
2268
  for (const { src, dest } of targets) {
2054
- if (!fs8.existsSync(src)) continue;
2055
- fs8.mkdirSync(dest, { recursive: true });
2056
- const stat = fs8.statSync(src);
2269
+ if (!fs9.existsSync(src)) continue;
2270
+ fs9.mkdirSync(dest, { recursive: true });
2271
+ const stat = fs9.statSync(src);
2057
2272
  if (stat.isDirectory()) {
2058
- const files = fs8.readdirSync(src);
2273
+ const files = fs9.readdirSync(src);
2059
2274
  for (const file of files) {
2060
- const srcFile = path8.join(src, file);
2061
- const destFile = path8.join(dest, file);
2062
- const fileStat = fs8.statSync(srcFile);
2275
+ const srcFile = path9.join(src, file);
2276
+ const destFile = path9.join(dest, file);
2277
+ const fileStat = fs9.statSync(srcFile);
2063
2278
  if (fileStat.isDirectory()) {
2064
- fs8.cpSync(srcFile, destFile, { recursive: true });
2279
+ fs9.cpSync(srcFile, destFile, { recursive: true });
2065
2280
  } else {
2066
- fs8.copyFileSync(srcFile, destFile);
2281
+ fs9.copyFileSync(srcFile, destFile);
2067
2282
  }
2068
2283
  }
2069
2284
  } else {
2070
- fs8.copyFileSync(src, dest);
2285
+ fs9.copyFileSync(src, dest);
2071
2286
  }
2072
2287
  }
2073
2288
  }
@@ -2075,6 +2290,11 @@ function staticCopy(options) {
2075
2290
  }
2076
2291
 
2077
2292
  // src/vite/client/build.ts
2293
+ function cacheKeyFor(outDir) {
2294
+ const abs = path10.resolve(outDir);
2295
+ const hash = crypto.createHash("sha1").update(abs).digest("hex").slice(0, 8);
2296
+ return `${path10.basename(abs) || "client"}-${hash}`;
2297
+ }
2078
2298
  async function build2(clientBuildOptions, buildContext) {
2079
2299
  const {
2080
2300
  srcDir = "./client",
@@ -2088,74 +2308,81 @@ async function build2(clientBuildOptions, buildContext) {
2088
2308
  globals = {},
2089
2309
  manualChunks
2090
2310
  } = clientBuildOptions;
2091
- const physicalEntryPath = path9.resolve(srcDir, entry);
2311
+ const cacheDir = path10.resolve(
2312
+ "node_modules",
2313
+ ".nrg",
2314
+ "client",
2315
+ cacheKeyFor(buildContext.outDir)
2316
+ );
2317
+ const physicalEntryPath = path10.resolve(srcDir, entry);
2092
2318
  let entryPath;
2093
2319
  let generatedEntry = false;
2094
- if (fs9.existsSync(physicalEntryPath)) {
2320
+ if (fs10.existsSync(physicalEntryPath)) {
2095
2321
  entryPath = physicalEntryPath;
2096
2322
  } else {
2097
- const cacheDir = path9.resolve("node_modules", ".nrg", "client");
2098
- const cachedEntryPath = path9.resolve(cacheDir, entry);
2099
- if (!fs9.existsSync(cacheDir)) {
2100
- fs9.mkdirSync(cacheDir, { recursive: true });
2323
+ const cachedEntryPath = path10.resolve(cacheDir, entry);
2324
+ if (!fs10.existsSync(cacheDir)) {
2325
+ fs10.mkdirSync(cacheDir, { recursive: true });
2101
2326
  }
2102
- fs9.writeFileSync(cachedEntryPath, "// auto-generated entry\n");
2327
+ fs10.writeFileSync(cachedEntryPath, "// auto-generated entry\n");
2103
2328
  entryPath = cachedEntryPath;
2104
2329
  generatedEntry = true;
2105
2330
  }
2106
- const iconsDir = path9.resolve(
2107
- staticDirs.icons ?? path9.join(path9.dirname(path9.resolve(srcDir)), "icons")
2331
+ const iconsDir = path10.resolve(
2332
+ staticDirs.icons ?? path10.join(path10.dirname(path10.resolve(srcDir)), "icons")
2108
2333
  );
2109
2334
  const plugins = [
2110
2335
  vue(),
2111
2336
  nodeDefinitionsInliner(
2112
2337
  buildContext.outDir,
2113
2338
  entryPath,
2114
- fs9.existsSync(iconsDir) ? iconsDir : void 0,
2115
- path9.resolve(srcDir, "components"),
2116
- path9.resolve(srcDir, "nodes"),
2117
- !generatedEntry
2339
+ fs10.existsSync(iconsDir) ? iconsDir : void 0,
2340
+ path10.resolve(srcDir, "components"),
2341
+ path10.resolve(srcDir, "nodes"),
2342
+ !generatedEntry,
2343
+ cacheDir
2118
2344
  )
2119
2345
  ];
2120
2346
  plugins.push(
2121
2347
  htmlGenerator({
2122
2348
  packageName: buildContext.packageName,
2123
- licensePath: licensePath ? path9.resolve(licensePath) : void 0
2349
+ licensePath: licensePath ? path10.resolve(licensePath) : void 0
2124
2350
  })
2125
2351
  );
2126
2352
  if (locales) {
2127
2353
  const { docsDir = "./locales/docs", labelsDir = "./locales/labels" } = locales;
2128
- const localesOutDir = path9.join(buildContext.outDir, "locales");
2354
+ const localesOutDir = path10.join(buildContext.outDir, "locales");
2129
2355
  plugins.push(
2130
2356
  localesGenerator({
2131
2357
  outDir: localesOutDir,
2132
- docsDir: path9.resolve(docsDir),
2133
- labelsDir: path9.resolve(labelsDir)
2358
+ docsDir: path10.resolve(docsDir),
2359
+ labelsDir: path10.resolve(labelsDir)
2134
2360
  })
2135
2361
  );
2136
2362
  plugins.push(
2137
2363
  helpGenerator({
2138
2364
  outDir: buildContext.outDir,
2139
2365
  localesOutDir,
2140
- docsDir: path9.resolve(docsDir),
2141
- labelsDir: path9.resolve(labelsDir)
2366
+ docsDir: path10.resolve(docsDir),
2367
+ labelsDir: path10.resolve(labelsDir),
2368
+ srcDir: buildContext.serverSrcDir
2142
2369
  })
2143
2370
  );
2144
2371
  }
2145
2372
  const copyTargets = [];
2146
- const publicDir = path9.resolve(
2147
- staticDirs.public ?? path9.join(srcDir, "public")
2373
+ const publicDir = path10.resolve(
2374
+ staticDirs.public ?? path10.join(srcDir, "public")
2148
2375
  );
2149
- if (fs9.existsSync(publicDir)) {
2376
+ if (fs10.existsSync(publicDir)) {
2150
2377
  copyTargets.push({
2151
2378
  src: publicDir,
2152
- dest: path9.join(buildContext.outDir, "resources")
2379
+ dest: path10.join(buildContext.outDir, "resources")
2153
2380
  });
2154
2381
  }
2155
- if (fs9.existsSync(iconsDir)) {
2382
+ if (fs10.existsSync(iconsDir)) {
2156
2383
  copyTargets.push({
2157
2384
  src: iconsDir,
2158
- dest: path9.join(buildContext.outDir, "icons")
2385
+ dest: path10.join(buildContext.outDir, "icons")
2159
2386
  });
2160
2387
  }
2161
2388
  if (copyTargets.length > 0) {
@@ -2183,10 +2410,10 @@ async function build2(clientBuildOptions, buildContext) {
2183
2410
  configFile: false,
2184
2411
  logLevel: "warn",
2185
2412
  base: `/resources/${buildContext.packageName}`,
2186
- publicDir: path9.resolve(srcDir, "public"),
2413
+ publicDir: path10.resolve(srcDir, "public"),
2187
2414
  resolve: {
2188
2415
  alias: {
2189
- "@": path9.resolve(srcDir)
2416
+ "@": path10.resolve(srcDir)
2190
2417
  }
2191
2418
  },
2192
2419
  plugins,
@@ -2240,15 +2467,15 @@ async function build2(clientBuildOptions, buildContext) {
2240
2467
  throw new BuildError("client", error);
2241
2468
  } finally {
2242
2469
  if (generatedEntry) {
2243
- if (fs9.existsSync(entryPath)) {
2244
- fs9.unlinkSync(entryPath);
2470
+ if (fs10.existsSync(entryPath)) {
2471
+ fs10.unlinkSync(entryPath);
2245
2472
  }
2246
2473
  }
2247
2474
  }
2248
2475
  }
2249
2476
 
2250
2477
  // src/vite/node-red-launcher/index.ts
2251
- import fs12 from "fs";
2478
+ import fs13 from "fs";
2252
2479
 
2253
2480
  // src/vite/async-utils.ts
2254
2481
  function withTimeout(promise, ms, fallback) {
@@ -2289,22 +2516,22 @@ async function retry(fn, options = {}) {
2289
2516
  import { exec } from "child_process";
2290
2517
  import { randomUUID } from "crypto";
2291
2518
  import { createRequire as createRequire3 } from "module";
2292
- import fs10 from "fs";
2519
+ import fs11 from "fs";
2293
2520
  import os from "os";
2294
- import path10 from "path";
2521
+ import path11 from "path";
2295
2522
  function getNodeRedCommand(version) {
2296
2523
  return version ? `node-red@${version}` : "node-red";
2297
2524
  }
2298
2525
  function resolveNodeRedFromLocalNodeModules() {
2299
2526
  try {
2300
- const require_ = createRequire3(path10.join(process.cwd(), "package.json"));
2527
+ const require_ = createRequire3(path11.join(process.cwd(), "package.json"));
2301
2528
  const pkgJsonPath = require_.resolve("node-red/package.json");
2302
- const pkgDir = path10.dirname(pkgJsonPath);
2303
- const pkg = JSON.parse(fs10.readFileSync(pkgJsonPath, "utf-8"));
2529
+ const pkgDir = path11.dirname(pkgJsonPath);
2530
+ const pkg = JSON.parse(fs11.readFileSync(pkgJsonPath, "utf-8"));
2304
2531
  const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.["node-red"];
2305
2532
  if (!bin) return null;
2306
- const entry = path10.resolve(pkgDir, bin);
2307
- return fs10.existsSync(entry) ? entry : null;
2533
+ const entry = path11.resolve(pkgDir, bin);
2534
+ return fs11.existsSync(entry) ? entry : null;
2308
2535
  } catch {
2309
2536
  return null;
2310
2537
  }
@@ -2329,11 +2556,11 @@ async function resolveNodeRed(options) {
2329
2556
  logger2.info(
2330
2557
  hasExplicitVersion ? `Using configured version (${version}), downloading via npx...` : `Not found locally, downloading via npx (this may take a while)...`
2331
2558
  );
2332
- const resolverScript = path10.join(
2559
+ const resolverScript = path11.join(
2333
2560
  os.tmpdir(),
2334
2561
  `nrg-resolve-node-red-${process.pid}-${randomUUID()}.cjs`
2335
2562
  );
2336
- fs10.writeFileSync(
2563
+ fs11.writeFileSync(
2337
2564
  resolverScript,
2338
2565
  `const fs = require("fs");
2339
2566
  const path = require("path");
@@ -2367,7 +2594,7 @@ for (const d of dirs) {
2367
2594
  );
2368
2595
  });
2369
2596
  const entryPoint = stdout.trim();
2370
- if (!entryPoint || !fs10.existsSync(entryPoint)) {
2597
+ if (!entryPoint || !fs11.existsSync(entryPoint)) {
2371
2598
  throw new NodeRedStartError(
2372
2599
  new Error(
2373
2600
  `Could not resolve node-red entry point: ${entryPoint || "(empty)"}`
@@ -2378,7 +2605,7 @@ for (const d of dirs) {
2378
2605
  return entryPoint;
2379
2606
  } finally {
2380
2607
  try {
2381
- fs10.unlinkSync(resolverScript);
2608
+ fs11.unlinkSync(resolverScript);
2382
2609
  } catch {
2383
2610
  }
2384
2611
  }
@@ -2386,28 +2613,28 @@ for (const d of dirs) {
2386
2613
 
2387
2614
  // src/vite/node-red-launcher/settings.ts
2388
2615
  import { builtinModules as builtinModules2 } from "module";
2389
- import fs11 from "fs";
2616
+ import fs12 from "fs";
2390
2617
  import os2 from "os";
2391
- import path11 from "path";
2618
+ import path12 from "path";
2392
2619
  import { pathToFileURL as pathToFileURL3 } from "url";
2393
2620
  import { build as esbuild } from "esbuild";
2394
2621
  function findUserRuntimeSettingsFilepath(settingsFilepath, logger2) {
2395
2622
  if (settingsFilepath) {
2396
- const resolved2 = path11.resolve(settingsFilepath);
2397
- if (fs11.existsSync(resolved2)) {
2623
+ const resolved2 = path12.resolve(settingsFilepath);
2624
+ if (fs12.existsSync(resolved2)) {
2398
2625
  return resolved2;
2399
2626
  }
2400
2627
  logger2.warn(`Settings file not found: ${settingsFilepath}`);
2401
2628
  return null;
2402
2629
  }
2403
- const resolved = path11.resolve("node-red.settings.ts");
2404
- if (fs11.existsSync(resolved)) {
2630
+ const resolved = path12.resolve("node-red.settings.ts");
2631
+ if (fs12.existsSync(resolved)) {
2405
2632
  return resolved;
2406
2633
  }
2407
2634
  return null;
2408
2635
  }
2409
2636
  async function compileRuntimeSettingsFile(runtimeSettingsFilepath, port) {
2410
- const compiledRuntimeSettingsFilepath = path11.join(
2637
+ const compiledRuntimeSettingsFilepath = path12.join(
2411
2638
  os2.tmpdir(),
2412
2639
  `node-red.settings.${process.pid}-${port}.cjs`
2413
2640
  );
@@ -2415,8 +2642,8 @@ async function compileRuntimeSettingsFile(runtimeSettingsFilepath, port) {
2415
2642
  ...builtinModules2,
2416
2643
  ...builtinModules2.map((m) => `node:${m}`)
2417
2644
  ];
2418
- const settingsDir = path11.dirname(runtimeSettingsFilepath).split(path11.sep).join("/");
2419
- const settingsFile = runtimeSettingsFilepath.split(path11.sep).join("/");
2645
+ const settingsDir = path12.dirname(runtimeSettingsFilepath).split(path12.sep).join("/");
2646
+ const settingsFile = runtimeSettingsFilepath.split(path12.sep).join("/");
2420
2647
  await esbuild({
2421
2648
  entryPoints: [runtimeSettingsFilepath],
2422
2649
  outfile: compiledRuntimeSettingsFilepath,
@@ -2448,14 +2675,14 @@ async function generateRuntimeSettings(options) {
2448
2675
  );
2449
2676
  tempFiles.push(compiledRuntimeSettingsFilepath);
2450
2677
  }
2451
- const normalizedOutDir = path11.resolve(outDir).split(path11.sep).join("/");
2452
- const cwd = process.cwd().split(path11.sep).join("/");
2453
- const userDir = path11.resolve(cwd, ".node-red").split(path11.sep).join("/");
2678
+ const normalizedOutDir = path12.resolve(outDir).split(path12.sep).join("/");
2679
+ const cwd = process.cwd().split(path12.sep).join("/");
2680
+ const userDir = path12.resolve(cwd, ".node-red").split(path12.sep).join("/");
2454
2681
  const userDirLiteral = JSON.stringify(userDir);
2455
2682
  const outDirLiteral = JSON.stringify(normalizedOutDir);
2456
2683
  const finalRuntimeSettingsFile = compiledRuntimeSettingsFilepath ? `
2457
2684
  const compiledRuntimeSettings = require(${JSON.stringify(
2458
- compiledRuntimeSettingsFilepath.split(path11.sep).join("/")
2685
+ compiledRuntimeSettingsFilepath.split(path12.sep).join("/")
2459
2686
  )});
2460
2687
  const settings = compiledRuntimeSettings.default || compiledRuntimeSettings;
2461
2688
  settings.uiPort = ${port};
@@ -2487,11 +2714,11 @@ const settings = {
2487
2714
  };
2488
2715
  module.exports = settings;
2489
2716
  `;
2490
- const finalRuntimeSettingsFilepath = path11.join(
2717
+ const finalRuntimeSettingsFilepath = path12.join(
2491
2718
  os2.tmpdir(),
2492
2719
  `node-red-settings-final-${process.pid}-${port}.cjs`
2493
2720
  );
2494
- fs11.writeFileSync(finalRuntimeSettingsFilepath, finalRuntimeSettingsFile);
2721
+ fs12.writeFileSync(finalRuntimeSettingsFilepath, finalRuntimeSettingsFile);
2495
2722
  tempFiles.push(finalRuntimeSettingsFilepath);
2496
2723
  return { filepath: finalRuntimeSettingsFilepath, tempFiles };
2497
2724
  }
@@ -2716,7 +2943,7 @@ var NodeRedLauncher = class {
2716
2943
  preferredPort: this.preferredPort,
2717
2944
  logger: this.logger
2718
2945
  });
2719
- if (!this.nodeRedEntryPoint || !fs12.existsSync(this.nodeRedEntryPoint)) {
2946
+ if (!this.nodeRedEntryPoint || !fs13.existsSync(this.nodeRedEntryPoint)) {
2720
2947
  this.nodeRedEntryPoint = await resolveNodeRed({
2721
2948
  version: this.options.runtime?.version,
2722
2949
  logger: this.logger
@@ -2790,7 +3017,7 @@ var NodeRedLauncher = class {
2790
3017
  cleanup() {
2791
3018
  for (const file of this.tempFiles) {
2792
3019
  try {
2793
- fs12.unlinkSync(file);
3020
+ fs13.unlinkSync(file);
2794
3021
  } catch {
2795
3022
  }
2796
3023
  }
@@ -2810,10 +3037,10 @@ var NodeRedTestEnvironment = class {
2810
3037
  options;
2811
3038
  constructor(options) {
2812
3039
  this.options = options;
2813
- this.projectDir = path12.resolve(options.projectDir ?? process.cwd());
2814
- this.outDir = path12.join(this.projectDir, "dist-e2e");
2815
- this.nodeRedDir = path12.join(this.projectDir, ".node-red");
2816
- this.installedPkgDir = path12.join(
3040
+ this.projectDir = path13.resolve(options.projectDir ?? process.cwd());
3041
+ this.outDir = path13.join(this.projectDir, "dist-e2e");
3042
+ this.nodeRedDir = path13.join(this.projectDir, ".node-red");
3043
+ this.installedPkgDir = path13.join(
2817
3044
  this.nodeRedDir,
2818
3045
  "node_modules",
2819
3046
  options.packageName
@@ -2825,15 +3052,15 @@ var NodeRedTestEnvironment = class {
2825
3052
  async setup() {
2826
3053
  this.originalCwd = process.cwd();
2827
3054
  process.chdir(this.projectDir);
2828
- if (fs13.existsSync(this.outDir)) fs13.rmSync(this.outDir, { recursive: true });
2829
- fs13.mkdirSync(this.outDir, { recursive: true });
3055
+ if (fs14.existsSync(this.outDir)) fs14.rmSync(this.outDir, { recursive: true });
3056
+ fs14.mkdirSync(this.outDir, { recursive: true });
2830
3057
  const buildContext = {
2831
3058
  outDir: this.outDir,
2832
3059
  packageName: this.options.packageName,
2833
3060
  isDev: false
2834
3061
  };
2835
3062
  const serverOpts = {
2836
- srcDir: path12.join(this.projectDir, "src/server"),
3063
+ srcDir: path13.join(this.projectDir, "src/server"),
2837
3064
  entry: "index.ts",
2838
3065
  format: "esm",
2839
3066
  bundled: [],
@@ -2843,7 +3070,7 @@ var NodeRedTestEnvironment = class {
2843
3070
  };
2844
3071
  await build(serverOpts, buildContext);
2845
3072
  const clientOpts = {
2846
- srcDir: path12.join(this.projectDir, "src/client"),
3073
+ srcDir: path13.join(this.projectDir, "src/client"),
2847
3074
  entry: "index.ts",
2848
3075
  name: this.options.clientName ?? "NodeRedNodes",
2849
3076
  format: "es",
@@ -2852,18 +3079,18 @@ var NodeRedTestEnvironment = class {
2852
3079
  ...this.options.client
2853
3080
  };
2854
3081
  await build2(clientOpts, buildContext);
2855
- fs13.mkdirSync(this.installedPkgDir, { recursive: true });
2856
- fs13.cpSync(this.outDir, this.installedPkgDir, { recursive: true });
2857
- fs13.mkdirSync(this.nodeRedDir, { recursive: true });
2858
- fs13.writeFileSync(
2859
- path12.join(this.nodeRedDir, ".config.runtime.json"),
3082
+ fs14.mkdirSync(this.installedPkgDir, { recursive: true });
3083
+ fs14.cpSync(this.outDir, this.installedPkgDir, { recursive: true });
3084
+ fs14.mkdirSync(this.nodeRedDir, { recursive: true });
3085
+ fs14.writeFileSync(
3086
+ path13.join(this.nodeRedDir, ".config.runtime.json"),
2860
3087
  JSON.stringify({ telemetryEnabled: false })
2861
3088
  );
2862
3089
  const launcherOpts = {
2863
3090
  runtime: { port: this.options.port ?? 1881 }
2864
3091
  };
2865
3092
  if (this.options.settingsFile) {
2866
- launcherOpts.runtime.settingsFilepath = path12.resolve(
3093
+ launcherOpts.runtime.settingsFilepath = path13.resolve(
2867
3094
  this.projectDir,
2868
3095
  this.options.settingsFile
2869
3096
  );
@@ -2898,25 +3125,19 @@ var NodeRedTestEnvironment = class {
2898
3125
  this.launcher.cleanup();
2899
3126
  this.launcher = null;
2900
3127
  }
2901
- if (fs13.existsSync(this.outDir)) fs13.rmSync(this.outDir, { recursive: true });
2902
- if (fs13.existsSync(this.nodeRedDir)) {
2903
- fs13.rmSync(this.nodeRedDir, { recursive: true });
3128
+ if (fs14.existsSync(this.outDir)) fs14.rmSync(this.outDir, { recursive: true });
3129
+ if (fs14.existsSync(this.nodeRedDir)) {
3130
+ fs14.rmSync(this.nodeRedDir, { recursive: true });
2904
3131
  }
2905
3132
  this.port = null;
2906
3133
  }
2907
3134
  };
2908
3135
 
2909
3136
  // src/test/client/e2e/index.ts
2910
- var defaultConfig = {
2911
- testTimeout: 6e4,
2912
- hookTimeout: 12e4,
2913
- globalSetup: ["@bonsae/nrg/test/client/e2e"],
2914
- include: ["tests/client/e2e/**/*.test.ts"]
2915
- };
2916
3137
  var _env = null;
2917
3138
  async function setup(options) {
2918
3139
  const packageName = JSON.parse(
2919
- fs14.readFileSync(path13.join(process.cwd(), "package.json"), "utf-8")
3140
+ fs15.readFileSync(path14.join(process.cwd(), "package.json"), "utf-8")
2920
3141
  ).name;
2921
3142
  _env = new NodeRedTestEnvironment({
2922
3143
  packageName,
@@ -2948,8 +3169,8 @@ var NodeRedEditor = class {
2948
3169
  errors = [];
2949
3170
  screenshotDir;
2950
3171
  async screenshot(name) {
2951
- fs14.mkdirSync(this.screenshotDir, { recursive: true });
2952
- const filePath = path13.join(this.screenshotDir, `${name}.png`);
3172
+ fs15.mkdirSync(this.screenshotDir, { recursive: true });
3173
+ const filePath = path14.join(this.screenshotDir, `${name}.png`);
2953
3174
  await this.page.screenshot({ path: filePath, fullPage: true });
2954
3175
  return filePath;
2955
3176
  }
@@ -3353,7 +3574,6 @@ export {
3353
3574
  NodeRedEditor,
3354
3575
  NodeRedField,
3355
3576
  NodeRedTestEnvironment,
3356
- defaultConfig,
3357
3577
  setup,
3358
3578
  teardown
3359
3579
  };