@bonsae/nrg 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  <p align="center">
6
6
  <a href="https://www.npmjs.com/package/@bonsae/nrg"><img src="https://img.shields.io/npm/v/@bonsae/nrg.svg" alt="npm package"></a>
7
7
  <a href="https://github.com/bonsaedev/nrg/actions/workflows/ci.yaml"><img src="https://github.com/bonsaedev/nrg/actions/workflows/ci.yaml/badge.svg?branch=main" alt="build status"></a>
8
+ <a href="https://socket.dev/npm/package/@bonsae/nrg"><img src="https://badge.socket.dev/npm/package/@bonsae/nrg" alt="Socket Badge"></a>
8
9
  </p>
9
10
 
10
11
  # nrg
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonsae/nrg",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
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",
@@ -0,0 +1,55 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://unpkg.com/@bonsae/nrg/schemas/labels.schema.json",
4
+ "title": "NRG Node Labels",
5
+ "description": "Label file for NRG Node-RED nodes. Provides human-readable labels for the editor UI and auto-generated help docs.",
6
+ "type": "object",
7
+ "required": ["label"],
8
+ "properties": {
9
+ "$schema": {
10
+ "type": "string",
11
+ "description": "JSON Schema reference for IDE validation"
12
+ },
13
+ "label": {
14
+ "type": "string",
15
+ "description": "Display name shown in the palette and workspace"
16
+ },
17
+ "description": {
18
+ "type": "string",
19
+ "description": "Node description for this language. Overrides the class-level description in auto-generated help docs."
20
+ },
21
+ "configs": {
22
+ "$ref": "#/$defs/labelMap",
23
+ "description": "Labels for config properties"
24
+ },
25
+ "credentials": {
26
+ "$ref": "#/$defs/labelMap",
27
+ "description": "Labels for credential properties"
28
+ },
29
+ "input": {
30
+ "$ref": "#/$defs/labelMap",
31
+ "description": "Labels for input schema properties"
32
+ },
33
+ "outputs": {
34
+ "type": "array",
35
+ "items": {
36
+ "$ref": "#/$defs/labelMap"
37
+ },
38
+ "description": "Per-port output labels, matching outputsSchema order. Single output uses an array with one element."
39
+ },
40
+ "errors": {
41
+ "$ref": "#/$defs/labelMap",
42
+ "description": "Custom error messages. Use __field__ for placeholder substitution."
43
+ }
44
+ },
45
+ "additionalProperties": false,
46
+ "$defs": {
47
+ "labelMap": {
48
+ "type": "object",
49
+ "additionalProperties": {
50
+ "type": "string"
51
+ },
52
+ "description": "Maps property keys to human-readable labels"
53
+ }
54
+ }
55
+ }
package/server/index.cjs CHANGED
@@ -750,45 +750,37 @@ function defineConfigNode(def) {
750
750
  return NodeClass;
751
751
  }
752
752
 
753
- // src/core/server/api/serve-nrg-resources.ts
753
+ // src/core/server/api/nrg-assets.ts
754
754
  var import_path = __toESM(require("path"), 1);
755
755
  var import_fs = __toESM(require("fs"), 1);
756
- var MIME = {
757
- ".js": "application/javascript",
758
- ".mjs": "application/javascript",
759
- ".css": "text/css",
760
- ".json": "application/json",
761
- ".map": "application/json",
762
- ".png": "image/png",
763
- ".svg": "image/svg+xml"
756
+ var RESOURCES_DIR = import_path.default.resolve(__dirname, "./resources");
757
+ var ALLOWED_FILES = /* @__PURE__ */ new Set([
758
+ "nrg-client.js",
759
+ "vue.esm-browser.prod.js",
760
+ "vue.esm-browser.js"
761
+ ]);
762
+ var handleNrgAsset = (req, res, next) => {
763
+ let fileName = req.params[0];
764
+ if (fileName === "vue.esm-browser.prod.js" && process.env.NODE_ENV !== "production") {
765
+ fileName = "vue.esm-browser.js";
766
+ }
767
+ if (!ALLOWED_FILES.has(fileName)) return next();
768
+ const filePath = import_path.default.join(RESOURCES_DIR, fileName);
769
+ if (!import_fs.default.existsSync(filePath)) return next();
770
+ res.setHeader("Content-Type", "application/javascript");
771
+ import_fs.default.createReadStream(filePath).pipe(res);
764
772
  };
765
- var _registered = false;
766
- function serveNrgResources(RED) {
767
- if (_registered) return;
768
- _registered = true;
769
- const clientDir = import_path.default.resolve(__dirname, "./resources");
770
- if (!import_fs.default.existsSync(clientDir)) return;
771
- const httpAdmin = RED.httpAdmin;
772
- if (!httpAdmin) return;
773
- httpAdmin.use(function(req, res, next) {
774
- const prefix = "/nrg/assets/";
775
- if (!req.path.startsWith(prefix)) return next();
776
- let reqPath = req.path.slice(prefix.length);
777
- if (reqPath === "vue.esm-browser.prod.js" && process.env.NODE_ENV !== "production") {
778
- const devPath = import_path.default.resolve(clientDir, "vue.esm-browser.js");
779
- if (import_fs.default.existsSync(devPath)) {
780
- reqPath = "vue.esm-browser.js";
781
- }
782
- }
783
- const filePath = import_path.default.resolve(clientDir, reqPath);
784
- const rel = import_path.default.relative(clientDir, filePath);
785
- if (rel.startsWith("..") || import_path.default.isAbsolute(rel)) return next();
786
- if (!import_fs.default.existsSync(filePath) || !import_fs.default.statSync(filePath).isFile())
787
- return next();
788
- const ext = import_path.default.extname(filePath);
789
- res.setHeader("Content-Type", MIME[ext] ?? "application/octet-stream");
790
- import_fs.default.createReadStream(filePath).pipe(res);
791
- });
773
+ function initNrgAssetsRoute(router) {
774
+ if (!import_fs.default.existsSync(RESOURCES_DIR)) return;
775
+ router.get("/nrg/assets/*", handleNrgAsset);
776
+ }
777
+
778
+ // src/core/server/api/routes.ts
779
+ var _initialized = false;
780
+ function initRoutes(RED) {
781
+ if (_initialized) return;
782
+ _initialized = true;
783
+ initNrgAssetsRoute(RED.httpAdmin);
792
784
  }
793
785
 
794
786
  // src/core/constants.ts
@@ -1022,7 +1014,7 @@ async function registerType(RED, NodeClass) {
1022
1014
  function registerTypes(nodes) {
1023
1015
  const fn = async function(RED) {
1024
1016
  initValidator(RED);
1025
- serveNrgResources(RED);
1017
+ initRoutes(RED);
1026
1018
  try {
1027
1019
  RED.log.info("Registering node types in series");
1028
1020
  for (const NodeClass of nodes) {
@@ -4,8 +4,8 @@
4
4
  "lib": ["ES2022", "DOM", "DOM.Iterable"]
5
5
  },
6
6
  "files": [
7
- "../shims/shims-vue.d.ts",
8
- "../shims/components.d.ts",
9
- "../shims/globals.d.ts"
7
+ "../types/shims/shims-vue.d.ts",
8
+ "../types/shims/components.d.ts",
9
+ "../types/shims/globals.d.ts"
10
10
  ]
11
11
  }
package/types/server.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
3
  import { EventEmitter } from 'events';
4
+ import { Express as Express$1 } from 'express';
4
5
  import { Http2ServerRequest } from 'http2';
5
6
 
6
7
  declare const TransformKind: unique symbol;
@@ -2006,18 +2007,7 @@ export interface NodeRedHooks {
2006
2007
  has(hookId: string): boolean;
2007
2008
  clear(): void;
2008
2009
  }
2009
- export type NodeRedRequestHandler = (req: any, res: any, next?: () => void) => void;
2010
- export interface NodeRedExpressApp {
2011
- get(path: string, ...handlers: NodeRedRequestHandler[]): void;
2012
- post(path: string, ...handlers: NodeRedRequestHandler[]): void;
2013
- put(path: string, ...handlers: NodeRedRequestHandler[]): void;
2014
- delete(path: string, ...handlers: NodeRedRequestHandler[]): void;
2015
- patch(path: string, ...handlers: NodeRedRequestHandler[]): void;
2016
- options(path: string, ...handlers: NodeRedRequestHandler[]): void;
2017
- head(path: string, ...handlers: NodeRedRequestHandler[]): void;
2018
- use(path: string | NodeRedRequestHandler, ...handlers: NodeRedRequestHandler[]): void;
2019
- all(path: string, ...handlers: NodeRedRequestHandler[]): void;
2020
- }
2010
+ export type NodeRedExpressApp = Express;
2021
2011
  export interface RED {
2022
2012
  /** Internationalization function */
2023
2013
  _(key: string, substitutions?: Record<string, string>): string;
package/vite/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/vite/plugin.ts
2
- import path13 from "path";
2
+ import path14 from "path";
3
3
 
4
4
  // src/vite/defaults.ts
5
5
  var DEFAULT_OUTPUT_DIR = "./dist";
@@ -18,19 +18,7 @@ var DEFAULT_CLIENT_BUILD_OPTIONS = {
18
18
  },
19
19
  locales: {
20
20
  docsDir: "./src/locales/docs",
21
- labelsDir: "./src/locales/labels",
22
- languages: [
23
- "en-US",
24
- "de",
25
- "es-ES",
26
- "fr",
27
- "ko",
28
- "pt-BR",
29
- "ru",
30
- "ja",
31
- "zh-CN",
32
- "zh-TW"
33
- ]
21
+ labelsDir: "./src/locales/labels"
34
22
  },
35
23
  staticDirs: {
36
24
  icons: "./src/icons",
@@ -535,7 +523,7 @@ module.exports = settings;
535
523
 
536
524
  // src/vite/plugins/server.ts
537
525
  import chokidar from "chokidar";
538
- import path11 from "path";
526
+ import path12 from "path";
539
527
 
540
528
  // src/vite/server/build.ts
541
529
  import { build as viteBuild } from "vite";
@@ -1122,13 +1110,230 @@ module.exports = function (RED) {
1122
1110
  // src/vite/client/build.ts
1123
1111
  import { build as viteBuild2 } from "vite";
1124
1112
  import vue from "@vitejs/plugin-vue";
1125
- import fs10 from "fs";
1126
- import path10 from "path";
1113
+ import fs11 from "fs";
1114
+ import path11 from "path";
1127
1115
 
1128
- // src/vite/client/plugins/html-generator.ts
1129
- import mime from "mime-types";
1116
+ // src/vite/client/plugins/help-generator.ts
1130
1117
  import fs6 from "fs";
1131
1118
  import path6 from "path";
1119
+ import { pathToFileURL } from "url";
1120
+ import { createRequire } from "module";
1121
+ function buildPropertyRow(name, schema, required, label) {
1122
+ let type = "";
1123
+ if (schema["x-nrg-node-type"]) {
1124
+ type = `NodeRef \u2192 ${schema["x-nrg-node-type"]}`;
1125
+ } else if (schema["x-nrg-typed-input"]) {
1126
+ type = "TypedInput";
1127
+ } else if (schema.type) {
1128
+ type = String(schema.type);
1129
+ }
1130
+ if (schema.enum) type += ` (${schema.enum.join(", ")})`;
1131
+ const constraints = [];
1132
+ if (schema.minLength !== void 0)
1133
+ constraints.push(`min: ${schema.minLength}`);
1134
+ if (schema.maxLength !== void 0)
1135
+ constraints.push(`max: ${schema.maxLength}`);
1136
+ if (schema.minimum !== void 0) constraints.push(`min: ${schema.minimum}`);
1137
+ if (schema.maximum !== void 0) constraints.push(`max: ${schema.maximum}`);
1138
+ if (schema.pattern) constraints.push(`pattern: \`${schema.pattern}\``);
1139
+ if (schema.format && schema.format !== "password")
1140
+ constraints.push(`format: ${schema.format}`);
1141
+ if (constraints.length) type += ` [${constraints.join(", ")}]`;
1142
+ const defaultVal = schema.default !== void 0 ? JSON.stringify(schema.default) : "";
1143
+ const description = schema.description ?? "";
1144
+ return { name, label: label ?? "", type, required, defaultVal, description };
1145
+ }
1146
+ var SKIP_FIELDS = /* @__PURE__ */ new Set([
1147
+ "id",
1148
+ "type",
1149
+ "z",
1150
+ "name",
1151
+ "wires",
1152
+ "x",
1153
+ "y",
1154
+ "g",
1155
+ "_users"
1156
+ ]);
1157
+ function generateSchemaSection(title, schema, labels, heading = "###") {
1158
+ if (!schema?.properties) return "";
1159
+ const required = new Set(schema.required ?? []);
1160
+ const rows = Object.entries(schema.properties).filter(([key]) => !SKIP_FIELDS.has(key)).map(
1161
+ ([key, propSchema]) => buildPropertyRow(
1162
+ key,
1163
+ propSchema,
1164
+ required.has(key),
1165
+ labels?.[key]
1166
+ )
1167
+ );
1168
+ if (rows.length === 0) return "";
1169
+ const hasLabels = rows.some((r) => r.label);
1170
+ const headerCells = hasLabels ? '<th>Property</th><th>Label</th><th>Type</th><th>Required</th><th>Default</th><th style="width:35%">Description</th>' : '<th>Property</th><th>Type</th><th>Required</th><th>Default</th><th style="width:40%">Description</th>';
1171
+ const tableRows = rows.map(
1172
+ (r) => hasLabels ? `<tr><td>${r.name}</td><td>${r.label}</td><td>${r.type}</td><td>${r.required ? "Yes" : "No"}</td><td>${r.defaultVal ? `<code>${r.defaultVal}</code>` : ""}</td><td>${r.description}</td></tr>` : `<tr><td>${r.name}</td><td>${r.type}</td><td>${r.required ? "Yes" : "No"}</td><td>${r.defaultVal ? `<code>${r.defaultVal}</code>` : ""}</td><td>${r.description}</td></tr>`
1173
+ ).join("\n");
1174
+ const table = `<div style="overflow-x:auto">
1175
+ <table width="100%" style="min-width:500px">
1176
+ <thead><tr>${headerCells}</tr></thead>
1177
+ <tbody>
1178
+ ${tableRows}
1179
+ </tbody>
1180
+ </table>
1181
+ </div>`;
1182
+ return `${heading} ${title}
1183
+
1184
+ ${table}
1185
+ `;
1186
+ }
1187
+ function loadNodeLabels(labelPath) {
1188
+ if (!fs6.existsSync(labelPath)) return {};
1189
+ try {
1190
+ const raw = JSON.parse(fs6.readFileSync(labelPath, "utf-8"));
1191
+ return {
1192
+ description: raw.description,
1193
+ configs: raw.configs,
1194
+ credentials: raw.credentials,
1195
+ input: raw.input,
1196
+ outputs: raw.outputs
1197
+ };
1198
+ } catch {
1199
+ return {};
1200
+ }
1201
+ }
1202
+ function generateHelpDoc(nodeClass, labels) {
1203
+ const lines = [];
1204
+ if (labels.description) {
1205
+ lines.push(labels.description);
1206
+ lines.push("");
1207
+ }
1208
+ const configSection = generateSchemaSection(
1209
+ "Properties",
1210
+ nodeClass.configSchema,
1211
+ labels.configs
1212
+ );
1213
+ if (configSection) lines.push(configSection);
1214
+ const credsSection = generateSchemaSection(
1215
+ "Credentials",
1216
+ nodeClass.credentialsSchema,
1217
+ labels.credentials
1218
+ );
1219
+ if (credsSection) lines.push(credsSection);
1220
+ if (nodeClass.inputSchema) {
1221
+ const inputLabels = nodeClass.inputLabels;
1222
+ const inputLabel = typeof inputLabels === "string" ? inputLabels : Array.isArray(inputLabels) ? inputLabels[0] : void 0;
1223
+ const inputTitle = inputLabel ? `Input (${inputLabel})` : "Input";
1224
+ const inputSection = generateSchemaSection(
1225
+ inputTitle,
1226
+ nodeClass.inputSchema,
1227
+ labels.input
1228
+ );
1229
+ if (inputSection) lines.push(inputSection);
1230
+ }
1231
+ if (nodeClass.outputsSchema) {
1232
+ const outputLabels = nodeClass.outputLabels;
1233
+ if (Array.isArray(nodeClass.outputsSchema)) {
1234
+ const portSections = [];
1235
+ nodeClass.outputsSchema.forEach((schema, i) => {
1236
+ const portLabel = Array.isArray(outputLabels) ? outputLabels[i] : void 0;
1237
+ const title = portLabel ? `Port ${i + 1} \u2014 ${portLabel}` : `Port ${i + 1}`;
1238
+ const portPropLabels = labels.outputs?.[i];
1239
+ const section = generateSchemaSection(
1240
+ title,
1241
+ schema,
1242
+ portPropLabels,
1243
+ "####"
1244
+ );
1245
+ if (section) portSections.push(section);
1246
+ });
1247
+ if (portSections.length) {
1248
+ lines.push(`### Outputs
1249
+
1250
+ ${portSections.join("\n")}`);
1251
+ }
1252
+ } else {
1253
+ const label = typeof outputLabels === "string" ? outputLabels : Array.isArray(outputLabels) ? outputLabels[0] : void 0;
1254
+ const title = label ? `Output (${label})` : "Output";
1255
+ const outputPropLabels = labels.outputs?.[0];
1256
+ const section = generateSchemaSection(
1257
+ title,
1258
+ nodeClass.outputsSchema,
1259
+ outputPropLabels
1260
+ );
1261
+ if (section) lines.push(section);
1262
+ }
1263
+ }
1264
+ return lines.join("\n").trim();
1265
+ }
1266
+ function discoverLanguages(labelsDir, nodeType) {
1267
+ const nodeLabelsDir = path6.join(labelsDir, nodeType);
1268
+ if (!fs6.existsSync(nodeLabelsDir)) return [];
1269
+ return fs6.readdirSync(nodeLabelsDir).filter((f) => f.endsWith(".json")).map((f) => path6.basename(f, ".json"));
1270
+ }
1271
+ function helpGenerator(options) {
1272
+ const { outDir, localesOutDir, docsDir, labelsDir } = options;
1273
+ return {
1274
+ name: "vite-plugin-node-red:client:help-generator",
1275
+ apply: "build",
1276
+ enforce: "post",
1277
+ async closeBundle() {
1278
+ const esmPath = path6.resolve(outDir, "index.mjs");
1279
+ const cjsPath = path6.resolve(outDir, "index.js");
1280
+ let packageFn;
1281
+ try {
1282
+ if (fs6.existsSync(esmPath)) {
1283
+ const fileUrl = pathToFileURL(esmPath).href + `?t=${Date.now()}`;
1284
+ const mod = await import(fileUrl);
1285
+ packageFn = mod?.default ?? mod;
1286
+ } else if (fs6.existsSync(cjsPath)) {
1287
+ const require2 = createRequire(import.meta.url);
1288
+ delete require2.cache[cjsPath];
1289
+ const rawMod = require2(cjsPath);
1290
+ packageFn = rawMod?.default ?? rawMod;
1291
+ }
1292
+ } catch {
1293
+ return;
1294
+ }
1295
+ const nodeClasses = packageFn?.nodes ?? [];
1296
+ const helpByLang = /* @__PURE__ */ new Map();
1297
+ for (const NodeClass of nodeClasses) {
1298
+ const type = NodeClass.type;
1299
+ if (!type) continue;
1300
+ const languages = discoverLanguages(labelsDir, type);
1301
+ if (!languages.includes("en-US")) languages.push("en-US");
1302
+ for (const lang of languages) {
1303
+ const manualMd = path6.join(docsDir, type, `${lang}.md`);
1304
+ const manualHtml = path6.join(docsDir, type, `${lang}.html`);
1305
+ if (fs6.existsSync(manualMd) || fs6.existsSync(manualHtml)) continue;
1306
+ const labelPath = path6.join(labelsDir, type, `${lang}.json`);
1307
+ const labels = loadNodeLabels(labelPath);
1308
+ const content = generateHelpDoc(NodeClass, labels);
1309
+ if (!content) continue;
1310
+ if (!helpByLang.has(lang)) helpByLang.set(lang, []);
1311
+ helpByLang.get(lang).push(
1312
+ `<script type="text/markdown" data-help-name="${type}">
1313
+ ${content}
1314
+ </script>`
1315
+ );
1316
+ }
1317
+ }
1318
+ for (const [lang, scripts] of helpByLang) {
1319
+ const langDir = path6.join(localesOutDir, lang);
1320
+ fs6.mkdirSync(langDir, { recursive: true });
1321
+ const indexPath = path6.join(langDir, "index.html");
1322
+ const existing = fs6.existsSync(indexPath) ? fs6.readFileSync(indexPath, "utf-8") : "";
1323
+ fs6.writeFileSync(
1324
+ indexPath,
1325
+ existing + (existing ? "\n" : "") + scripts.join("\n"),
1326
+ "utf-8"
1327
+ );
1328
+ }
1329
+ }
1330
+ };
1331
+ }
1332
+
1333
+ // src/vite/client/plugins/html-generator.ts
1334
+ import mime from "mime-types";
1335
+ import fs7 from "fs";
1336
+ import path7 from "path";
1132
1337
  function htmlGenerator(options) {
1133
1338
  const { packageName, licensePath } = options;
1134
1339
  return {
@@ -1138,7 +1343,7 @@ function htmlGenerator(options) {
1138
1343
  generateBundle(_, bundle) {
1139
1344
  const resourcesTags = Object.keys(bundle).map((fileName) => {
1140
1345
  const asset = bundle[fileName];
1141
- const srcPath = path6.join(
1346
+ const srcPath = path7.join(
1142
1347
  "resources",
1143
1348
  packageName,
1144
1349
  fileName.replace(/^resources\/?/, "")
@@ -1166,8 +1371,8 @@ function htmlGenerator(options) {
1166
1371
  return null;
1167
1372
  }
1168
1373
  }).filter(Boolean).join("\n");
1169
- const licenseBanner = licensePath && fs6.existsSync(licensePath) ? `<!--
1170
- ${fs6.readFileSync(licensePath, "utf-8")}
1374
+ const licenseBanner = licensePath && fs7.existsSync(licensePath) ? `<!--
1375
+ ${fs7.readFileSync(licensePath, "utf-8")}
1171
1376
  -->` : "";
1172
1377
  this.emitFile({
1173
1378
  type: "asset",
@@ -1180,10 +1385,22 @@ ${resourcesTags}`
1180
1385
  }
1181
1386
 
1182
1387
  // src/vite/client/plugins/locales-generator.ts
1183
- import fs7 from "fs";
1184
- import path7 from "path";
1388
+ import fs8 from "fs";
1389
+ import path8 from "path";
1185
1390
  function localesGenerator(options) {
1186
- const { outDir, docsDir, labelsDir, languages } = options;
1391
+ const { outDir, docsDir, labelsDir } = options;
1392
+ const languages = [
1393
+ "en-US",
1394
+ "de",
1395
+ "es-ES",
1396
+ "fr",
1397
+ "ko",
1398
+ "pt-BR",
1399
+ "ru",
1400
+ "ja",
1401
+ "zh-CN",
1402
+ "zh-TW"
1403
+ ];
1187
1404
  return {
1188
1405
  name: "vite-plugin-node-red:client:locales-generator",
1189
1406
  apply: "build",
@@ -1199,17 +1416,17 @@ Supported: ${languages.join(", ")}`
1199
1416
  }
1200
1417
  function forEachFile(baseDir, fileExtensions, processFile) {
1201
1418
  const langMap = /* @__PURE__ */ new Map();
1202
- if (!fs7.existsSync(baseDir)) return langMap;
1203
- const nodeDirs = fs7.readdirSync(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory());
1419
+ if (!fs8.existsSync(baseDir)) return langMap;
1420
+ const nodeDirs = fs8.readdirSync(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory());
1204
1421
  for (const nodeDir of nodeDirs) {
1205
1422
  const nodeType = nodeDir.name;
1206
- const nodePath = path7.join(baseDir, nodeType);
1207
- const files = fs7.readdirSync(nodePath);
1423
+ const nodePath = path8.join(baseDir, nodeType);
1424
+ const files = fs8.readdirSync(nodePath);
1208
1425
  for (const file of files) {
1209
- const ext = path7.extname(file);
1426
+ const ext = path8.extname(file);
1210
1427
  if (!fileExtensions.includes(ext)) continue;
1211
- const lang = path7.basename(file, ext);
1212
- const filePath = path7.join(nodePath, file);
1428
+ const lang = path8.basename(file, ext);
1429
+ const filePath = path8.join(nodePath, file);
1213
1430
  validateLanguage(lang, filePath);
1214
1431
  const value = processFile({ ext, filePath, nodeType });
1215
1432
  if (value == null) continue;
@@ -1227,10 +1444,10 @@ Supported: ${languages.join(", ")}`
1227
1444
  }
1228
1445
  function writeOutput(langMap, fileName, serialize) {
1229
1446
  for (const [lang, data] of langMap.entries()) {
1230
- const langOutDir = path7.join(outDir, lang);
1231
- fs7.mkdirSync(langOutDir, { recursive: true });
1232
- fs7.writeFileSync(
1233
- path7.join(langOutDir, fileName),
1447
+ const langOutDir = path8.join(outDir, lang);
1448
+ fs8.mkdirSync(langOutDir, { recursive: true });
1449
+ fs8.writeFileSync(
1450
+ path8.join(langOutDir, fileName),
1234
1451
  serialize(data),
1235
1452
  "utf-8"
1236
1453
  );
@@ -1242,7 +1459,7 @@ Supported: ${languages.join(", ")}`
1242
1459
  ({ ext, filePath, nodeType }) => {
1243
1460
  const type = ext === ".html" ? "text/html" : ext === ".md" ? "text/markdown" : null;
1244
1461
  if (!type) return null;
1245
- const content = fs7.readFileSync(filePath, "utf-8");
1462
+ const content = fs8.readFileSync(filePath, "utf-8");
1246
1463
  return [
1247
1464
  `<script type="${type}" data-help-name="${nodeType}">
1248
1465
  ${content}
@@ -1258,7 +1475,15 @@ ${content}
1258
1475
  const labelLangs = forEachFile(
1259
1476
  labelsDir,
1260
1477
  [".json"],
1261
- ({ filePath }) => JSON.parse(fs7.readFileSync(filePath, "utf-8"))
1478
+ ({ filePath, nodeType }) => {
1479
+ const parsed = JSON.parse(fs8.readFileSync(filePath, "utf-8"));
1480
+ if (parsed[nodeType] && typeof parsed[nodeType] === "object") {
1481
+ console.warn(
1482
+ `[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`
1483
+ );
1484
+ }
1485
+ return parsed;
1486
+ }
1262
1487
  );
1263
1488
  writeOutput(
1264
1489
  labelLangs,
@@ -1290,10 +1515,10 @@ function minifier() {
1290
1515
  }
1291
1516
 
1292
1517
  // src/vite/client/plugins/node-definitions-inliner.ts
1293
- import { createRequire } from "module";
1294
- import { pathToFileURL } from "url";
1295
- import path8 from "path";
1296
- import fs8 from "fs";
1518
+ import { createRequire as createRequire2 } from "module";
1519
+ import { pathToFileURL as pathToFileURL2 } from "url";
1520
+ import path9 from "path";
1521
+ import fs9 from "fs";
1297
1522
  import mime2 from "mime-types";
1298
1523
  var VIRTUAL_ID = "virtual:nrg/node-definitions";
1299
1524
  var RESOLVED_ID = "\0" + VIRTUAL_ID;
@@ -1324,9 +1549,9 @@ function getCredentialsFromSchema(schema) {
1324
1549
  return result;
1325
1550
  }
1326
1551
  function resolveIcon(iconsDir, type) {
1327
- if (!fs8.existsSync(iconsDir)) return void 0;
1328
- return fs8.readdirSync(iconsDir).find((f) => {
1329
- if (path8.basename(f, path8.extname(f)) !== type) return false;
1552
+ if (!fs9.existsSync(iconsDir)) return void 0;
1553
+ return fs9.readdirSync(iconsDir).find((f) => {
1554
+ if (path9.basename(f, path9.extname(f)) !== type) return false;
1330
1555
  const mimeType = mime2.lookup(f);
1331
1556
  return mimeType !== false && mimeType.startsWith("image/");
1332
1557
  });
@@ -1334,7 +1559,7 @@ function resolveIcon(iconsDir, type) {
1334
1559
  function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir, nodesDir, hasUserEntry = true) {
1335
1560
  let _nodeTypes = [];
1336
1561
  let _definitions = {};
1337
- const cacheDir = path8.resolve("node_modules", ".nrg", "client");
1562
+ const cacheDir = path9.resolve("node_modules", ".nrg", "client");
1338
1563
  return {
1339
1564
  name: "vite-plugin-node-red:client:node-definitions-inliner",
1340
1565
  enforce: "pre",
@@ -1343,15 +1568,15 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
1343
1568
  async buildStart() {
1344
1569
  _nodeTypes = [];
1345
1570
  _definitions = {};
1346
- const esmEntryPath = path8.resolve(serverOutDir, "index.mjs");
1347
- const cjsEntryPath = path8.resolve(serverOutDir, "index.js");
1571
+ const esmEntryPath = path9.resolve(serverOutDir, "index.mjs");
1572
+ const cjsEntryPath = path9.resolve(serverOutDir, "index.js");
1348
1573
  let packageFn;
1349
- if (fs8.existsSync(esmEntryPath)) {
1350
- const fileUrl = pathToFileURL(esmEntryPath).href + `?t=${Date.now()}`;
1574
+ if (fs9.existsSync(esmEntryPath)) {
1575
+ const fileUrl = pathToFileURL2(esmEntryPath).href + `?t=${Date.now()}`;
1351
1576
  const mod = await import(fileUrl);
1352
1577
  packageFn = mod?.default ?? mod;
1353
- } else if (fs8.existsSync(cjsEntryPath)) {
1354
- const require2 = createRequire(import.meta.url);
1578
+ } else if (fs9.existsSync(cjsEntryPath)) {
1579
+ const require2 = createRequire2(import.meta.url);
1355
1580
  delete require2.cache[cjsEntryPath];
1356
1581
  const rawMod = require2(cjsEntryPath);
1357
1582
  packageFn = rawMod?.default ?? rawMod;
@@ -1395,14 +1620,14 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
1395
1620
  };
1396
1621
  }
1397
1622
  if (!hasUserEntry) {
1398
- const nodesCache = path8.resolve(cacheDir, "nodes");
1399
- if (fs8.existsSync(nodesCache)) {
1400
- fs8.rmSync(nodesCache, { recursive: true });
1623
+ const nodesCache = path9.resolve(cacheDir, "nodes");
1624
+ if (fs9.existsSync(nodesCache)) {
1625
+ fs9.rmSync(nodesCache, { recursive: true });
1401
1626
  }
1402
- fs8.mkdirSync(nodesCache, { recursive: true });
1627
+ fs9.mkdirSync(nodesCache, { recursive: true });
1403
1628
  for (const type of _nodeTypes) {
1404
- const userTsPath = nodesDir ? path8.resolve(nodesDir, `${type}.ts`) : null;
1405
- if (userTsPath && fs8.existsSync(userTsPath)) continue;
1629
+ const userTsPath = nodesDir ? path9.resolve(nodesDir, `${type}.ts`) : null;
1630
+ if (userTsPath && fs9.existsSync(userTsPath)) continue;
1406
1631
  const content = [
1407
1632
  `// auto-generated by nrg`,
1408
1633
  `import { defineNode } from "@bonsae/nrg/client";`,
@@ -1412,13 +1637,13 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
1412
1637
  `});`,
1413
1638
  ``
1414
1639
  ].join("\n");
1415
- fs8.writeFileSync(path8.resolve(nodesCache, `${type}.ts`), content);
1640
+ fs9.writeFileSync(path9.resolve(nodesCache, `${type}.ts`), content);
1416
1641
  }
1417
1642
  const entryContent = generateEntryCode("");
1418
- fs8.mkdirSync(path8.dirname(path8.resolve(cacheDir, "index.ts")), {
1643
+ fs9.mkdirSync(path9.dirname(path9.resolve(cacheDir, "index.ts")), {
1419
1644
  recursive: true
1420
1645
  });
1421
- fs8.writeFileSync(path8.resolve(cacheDir, "index.ts"), entryContent);
1646
+ fs9.writeFileSync(path9.resolve(cacheDir, "index.ts"), entryContent);
1422
1647
  }
1423
1648
  },
1424
1649
  resolveId(id) {
@@ -1441,12 +1666,12 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
1441
1666
  const nrgImports = /* @__PURE__ */ new Set(["__setSchemas"]);
1442
1667
  const lines = [`import __nrgSchemas from "${VIRTUAL_ID}";`];
1443
1668
  const postLines = [`__setSchemas(__nrgSchemas);`];
1444
- if (componentsDir && fs8.existsSync(componentsDir)) {
1669
+ if (componentsDir && fs9.existsSync(componentsDir)) {
1445
1670
  const formImports = [];
1446
1671
  const formEntries = [];
1447
1672
  for (const type of _nodeTypes) {
1448
- const componentPath = path8.resolve(componentsDir, `${type}.vue`);
1449
- if (fs8.existsSync(componentPath)) {
1673
+ const componentPath = path9.resolve(componentsDir, `${type}.vue`);
1674
+ if (fs9.existsSync(componentPath)) {
1450
1675
  const varName = `__nrgForm_${type.replace(/-/g, "_")}`;
1451
1676
  formImports.push(
1452
1677
  `import ${varName} from ${JSON.stringify(componentPath)};`
@@ -1461,12 +1686,12 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
1461
1686
  }
1462
1687
  }
1463
1688
  if (!hasUserEntry) {
1464
- const nodesCache = path8.resolve(cacheDir, "nodes");
1689
+ const nodesCache = path9.resolve(cacheDir, "nodes");
1465
1690
  const defVarNames = [];
1466
1691
  for (const type of _nodeTypes) {
1467
1692
  const varName = `__nrgNodeDef_${type.replace(/-/g, "_")}`;
1468
- const userTsPath = nodesDir ? path8.resolve(nodesDir, `${type}.ts`) : null;
1469
- const tsPath = userTsPath && fs8.existsSync(userTsPath) ? userTsPath : path8.resolve(nodesCache, `${type}.ts`);
1693
+ const userTsPath = nodesDir ? path9.resolve(nodesDir, `${type}.ts`) : null;
1694
+ const tsPath = userTsPath && fs9.existsSync(userTsPath) ? userTsPath : path9.resolve(nodesCache, `${type}.ts`);
1470
1695
  lines.push(`import ${varName} from ${JSON.stringify(tsPath)};`);
1471
1696
  defVarNames.push(varName);
1472
1697
  }
@@ -1484,8 +1709,8 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
1484
1709
  }
1485
1710
 
1486
1711
  // src/vite/client/plugins/static-copy.ts
1487
- import fs9 from "fs";
1488
- import path9 from "path";
1712
+ import fs10 from "fs";
1713
+ import path10 from "path";
1489
1714
  function staticCopy(options) {
1490
1715
  const { targets } = options;
1491
1716
  return {
@@ -1494,23 +1719,23 @@ function staticCopy(options) {
1494
1719
  enforce: "post",
1495
1720
  closeBundle() {
1496
1721
  for (const { src, dest } of targets) {
1497
- if (!fs9.existsSync(src)) continue;
1498
- fs9.mkdirSync(dest, { recursive: true });
1499
- const stat = fs9.statSync(src);
1722
+ if (!fs10.existsSync(src)) continue;
1723
+ fs10.mkdirSync(dest, { recursive: true });
1724
+ const stat = fs10.statSync(src);
1500
1725
  if (stat.isDirectory()) {
1501
- const files = fs9.readdirSync(src);
1726
+ const files = fs10.readdirSync(src);
1502
1727
  for (const file of files) {
1503
- const srcFile = path9.join(src, file);
1504
- const destFile = path9.join(dest, file);
1505
- const fileStat = fs9.statSync(srcFile);
1728
+ const srcFile = path10.join(src, file);
1729
+ const destFile = path10.join(dest, file);
1730
+ const fileStat = fs10.statSync(srcFile);
1506
1731
  if (fileStat.isDirectory()) {
1507
- fs9.cpSync(srcFile, destFile, { recursive: true });
1732
+ fs10.cpSync(srcFile, destFile, { recursive: true });
1508
1733
  } else {
1509
- fs9.copyFileSync(srcFile, destFile);
1734
+ fs10.copyFileSync(srcFile, destFile);
1510
1735
  }
1511
1736
  }
1512
1737
  } else {
1513
- fs9.copyFileSync(src, dest);
1738
+ fs10.copyFileSync(src, dest);
1514
1739
  }
1515
1740
  }
1516
1741
  }
@@ -1532,32 +1757,32 @@ async function build2(clientBuildOptions, buildContext) {
1532
1757
  manualChunks,
1533
1758
  plugins: userPlugins = []
1534
1759
  } = clientBuildOptions;
1535
- const physicalEntryPath = path10.resolve(srcDir, entry);
1760
+ const physicalEntryPath = path11.resolve(srcDir, entry);
1536
1761
  let entryPath;
1537
1762
  let generatedEntry = false;
1538
- if (fs10.existsSync(physicalEntryPath)) {
1763
+ if (fs11.existsSync(physicalEntryPath)) {
1539
1764
  entryPath = physicalEntryPath;
1540
1765
  } else {
1541
- const cacheDir = path10.resolve("node_modules", ".nrg", "client");
1542
- const cachedEntryPath = path10.resolve(cacheDir, entry);
1543
- if (!fs10.existsSync(cacheDir)) {
1544
- fs10.mkdirSync(cacheDir, { recursive: true });
1766
+ const cacheDir = path11.resolve("node_modules", ".nrg", "client");
1767
+ const cachedEntryPath = path11.resolve(cacheDir, entry);
1768
+ if (!fs11.existsSync(cacheDir)) {
1769
+ fs11.mkdirSync(cacheDir, { recursive: true });
1545
1770
  }
1546
- fs10.writeFileSync(cachedEntryPath, "// auto-generated entry\n");
1771
+ fs11.writeFileSync(cachedEntryPath, "// auto-generated entry\n");
1547
1772
  entryPath = cachedEntryPath;
1548
1773
  generatedEntry = true;
1549
1774
  }
1550
- const iconsDir = path10.resolve(
1551
- staticDirs.icons ?? path10.join(path10.dirname(path10.resolve(srcDir)), "icons")
1775
+ const iconsDir = path11.resolve(
1776
+ staticDirs.icons ?? path11.join(path11.dirname(path11.resolve(srcDir)), "icons")
1552
1777
  );
1553
1778
  const plugins = [
1554
1779
  vue(),
1555
1780
  nodeDefinitionsInliner(
1556
1781
  buildContext.outDir,
1557
1782
  entryPath,
1558
- fs10.existsSync(iconsDir) ? iconsDir : void 0,
1559
- path10.resolve(srcDir, "components"),
1560
- path10.resolve(srcDir, "nodes"),
1783
+ fs11.existsSync(iconsDir) ? iconsDir : void 0,
1784
+ path11.resolve(srcDir, "components"),
1785
+ path11.resolve(srcDir, "nodes"),
1561
1786
  !generatedEntry
1562
1787
  ),
1563
1788
  ...userPlugins
@@ -1565,49 +1790,42 @@ async function build2(clientBuildOptions, buildContext) {
1565
1790
  plugins.push(
1566
1791
  htmlGenerator({
1567
1792
  packageName: buildContext.packageName,
1568
- licensePath: licensePath ? path10.resolve(licensePath) : void 0
1793
+ licensePath: licensePath ? path11.resolve(licensePath) : void 0
1569
1794
  })
1570
1795
  );
1571
1796
  if (locales) {
1572
- const {
1573
- docsDir = "./locales/docs",
1574
- labelsDir = "./locales/labels",
1575
- languages = [
1576
- "en-US",
1577
- "de",
1578
- "es-ES",
1579
- "fr",
1580
- "ko",
1581
- "pt-BR",
1582
- "ru",
1583
- "ja",
1584
- "zh-CN",
1585
- "zh-TW"
1586
- ]
1587
- } = locales;
1797
+ const { docsDir = "./locales/docs", labelsDir = "./locales/labels" } = locales;
1798
+ const localesOutDir = path11.join(buildContext.outDir, "locales");
1588
1799
  plugins.push(
1589
1800
  localesGenerator({
1590
- outDir: path10.join(buildContext.outDir, "locales"),
1591
- docsDir: path10.resolve(docsDir),
1592
- labelsDir: path10.resolve(labelsDir),
1593
- languages
1801
+ outDir: localesOutDir,
1802
+ docsDir: path11.resolve(docsDir),
1803
+ labelsDir: path11.resolve(labelsDir)
1804
+ })
1805
+ );
1806
+ plugins.push(
1807
+ helpGenerator({
1808
+ outDir: buildContext.outDir,
1809
+ localesOutDir,
1810
+ docsDir: path11.resolve(docsDir),
1811
+ labelsDir: path11.resolve(labelsDir)
1594
1812
  })
1595
1813
  );
1596
1814
  }
1597
1815
  const copyTargets = [];
1598
- const publicDir = path10.resolve(
1599
- staticDirs.public ?? path10.join(srcDir, "public")
1816
+ const publicDir = path11.resolve(
1817
+ staticDirs.public ?? path11.join(srcDir, "public")
1600
1818
  );
1601
- if (fs10.existsSync(publicDir)) {
1819
+ if (fs11.existsSync(publicDir)) {
1602
1820
  copyTargets.push({
1603
1821
  src: publicDir,
1604
- dest: path10.join(buildContext.outDir, "resources")
1822
+ dest: path11.join(buildContext.outDir, "resources")
1605
1823
  });
1606
1824
  }
1607
- if (fs10.existsSync(iconsDir)) {
1825
+ if (fs11.existsSync(iconsDir)) {
1608
1826
  copyTargets.push({
1609
1827
  src: iconsDir,
1610
- dest: path10.join(buildContext.outDir, "icons")
1828
+ dest: path11.join(buildContext.outDir, "icons")
1611
1829
  });
1612
1830
  }
1613
1831
  if (copyTargets.length > 0) {
@@ -1634,11 +1852,11 @@ async function build2(clientBuildOptions, buildContext) {
1634
1852
  const config = {
1635
1853
  configFile: false,
1636
1854
  logLevel: "warn",
1637
- base: `/${path10.join("resources", buildContext.packageName)}`,
1638
- publicDir: path10.resolve(srcDir, "public"),
1855
+ base: `/${path11.join("resources", buildContext.packageName)}`,
1856
+ publicDir: path11.resolve(srcDir, "public"),
1639
1857
  resolve: {
1640
1858
  alias: {
1641
- "@": path10.resolve(srcDir)
1859
+ "@": path11.resolve(srcDir)
1642
1860
  }
1643
1861
  },
1644
1862
  plugins,
@@ -1661,9 +1879,9 @@ async function build2(clientBuildOptions, buildContext) {
1661
1879
  external,
1662
1880
  treeshake: false,
1663
1881
  output: {
1664
- entryFileNames: path10.join("resources", "index.[hash].js"),
1665
- chunkFileNames: path10.join("resources", "vendor.[hash].js"),
1666
- assetFileNames: path10.join("resources", "[name].[hash].[ext]"),
1882
+ entryFileNames: path11.join("resources", "index.[hash].js"),
1883
+ chunkFileNames: path11.join("resources", "vendor.[hash].js"),
1884
+ assetFileNames: path11.join("resources", "[name].[hash].[ext]"),
1667
1885
  globals,
1668
1886
  paths: {
1669
1887
  vue: "/nrg/assets/vue.esm-browser.prod.js",
@@ -1689,8 +1907,8 @@ async function build2(clientBuildOptions, buildContext) {
1689
1907
  throw new BuildError("client", error);
1690
1908
  } finally {
1691
1909
  if (generatedEntry) {
1692
- if (fs10.existsSync(entryPath)) {
1693
- fs10.unlinkSync(entryPath);
1910
+ if (fs11.existsSync(entryPath)) {
1911
+ fs11.unlinkSync(entryPath);
1694
1912
  }
1695
1913
  }
1696
1914
  }
@@ -1841,20 +2059,20 @@ function serverPlugin(options) {
1841
2059
  });
1842
2060
  logger.startGroup("Node-RED");
1843
2061
  nodeRedLauncher.flushLogs();
1844
- const serverSrcDir = path11.resolve(
2062
+ const serverSrcDir = path12.resolve(
1845
2063
  serverBuildOptions.srcDir ?? "./server"
1846
2064
  );
1847
- const clientSrcDir = path11.resolve(
2065
+ const clientSrcDir = path12.resolve(
1848
2066
  clientBuildOptions.srcDir ?? "./client"
1849
2067
  );
1850
- const localesDocsDir = path11.resolve(
2068
+ const localesDocsDir = path12.resolve(
1851
2069
  clientBuildOptions.locales?.docsDir ?? "./locales/docs"
1852
2070
  );
1853
- const localesLabelsDir = path11.resolve(
2071
+ const localesLabelsDir = path12.resolve(
1854
2072
  clientBuildOptions.locales?.labelsDir ?? "./locales/labels"
1855
2073
  );
1856
- const iconsDir = path11.resolve(
1857
- clientBuildOptions.staticDirs?.icons ?? path11.join(path11.dirname(clientSrcDir), "icons")
2074
+ const iconsDir = path12.resolve(
2075
+ clientBuildOptions.staticDirs?.icons ?? path12.join(path12.dirname(clientSrcDir), "icons")
1858
2076
  );
1859
2077
  const watchPaths = [
1860
2078
  serverSrcDir,
@@ -1874,7 +2092,7 @@ function serverPlugin(options) {
1874
2092
  );
1875
2093
  const handleFileChange = (file, event) => {
1876
2094
  if (!initialStartDone) return;
1877
- logger.info(`${event}: ${path11.relative(process.cwd(), file)}`);
2095
+ logger.info(`${event}: ${path12.relative(process.cwd(), file)}`);
1878
2096
  debounceBeforeStart();
1879
2097
  };
1880
2098
  watcher.on("change", (file) => handleFileChange(file, "Changed"));
@@ -1913,8 +2131,8 @@ function serverPlugin(options) {
1913
2131
 
1914
2132
  // src/vite/plugins/build.ts
1915
2133
  import { execSync } from "child_process";
1916
- import fs11 from "fs";
1917
- import path12 from "path";
2134
+ import fs12 from "fs";
2135
+ import path13 from "path";
1918
2136
  function buildPlugin(options) {
1919
2137
  const {
1920
2138
  serverBuildOptions,
@@ -1929,16 +2147,16 @@ function buildPlugin(options) {
1929
2147
  try {
1930
2148
  logger.intro();
1931
2149
  logger.startSpinner("Type checking");
1932
- const serverTsconfig = path12.resolve(
2150
+ const serverTsconfig = path13.resolve(
1933
2151
  serverBuildOptions.srcDir ?? "./src/server",
1934
2152
  "tsconfig.json"
1935
2153
  );
1936
- const clientTsconfig = path12.resolve(
2154
+ const clientTsconfig = path13.resolve(
1937
2155
  clientBuildOptions.srcDir ?? "./src/client",
1938
2156
  "tsconfig.json"
1939
2157
  );
1940
2158
  const tsconfigsToCheck = [serverTsconfig, clientTsconfig].filter(
1941
- (p) => fs11.existsSync(p)
2159
+ (p) => fs12.existsSync(p)
1942
2160
  );
1943
2161
  try {
1944
2162
  for (const tsconfig of tsconfigsToCheck) {
@@ -1998,7 +2216,7 @@ function nodeRed(options = {}) {
1998
2216
  options.nodeRedLauncherOptions
1999
2217
  );
2000
2218
  const extraFilesCopyTargets = options.extraFilesCopyTargets ?? DEFAULT_EXTRA_FILES_COPY_TARGETS;
2001
- const resolvedOutDir = path13.resolve(outDir);
2219
+ const resolvedOutDir = path14.resolve(outDir);
2002
2220
  const buildContext = {
2003
2221
  outDir: resolvedOutDir,
2004
2222
  packageName: getPackageName(),
File without changes
File without changes
File without changes