@bonsae/nrg 0.18.4 → 0.19.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 (37) hide show
  1. package/README.md +38 -45
  2. package/package.json +1 -1
  3. package/server/index.cjs +96 -10
  4. package/server/resources/nrg-client.js +2269 -2233
  5. package/test/client/component/config.js +11 -0
  6. package/test/client/component/index.js +218 -235
  7. package/test/client/component/nrg.css +1 -0
  8. package/test/client/component/setup.js +1549 -140
  9. package/test/client/e2e/index.js +721 -367
  10. package/test/client/unit/index.js +204 -16
  11. package/test/client/unit/setup.js +209 -19
  12. package/test/server/unit/index.js +25 -4
  13. package/tsconfig/core/client.json +1 -1
  14. package/tsconfig/test/client/component.json +1 -1
  15. package/types/client.d.ts +98 -18
  16. package/types/server.d.ts +50 -12
  17. package/types/shims/brands.d.ts +32 -0
  18. package/types/shims/{form → client/form}/components/node-red-editor-input.vue.d.ts +1 -1
  19. package/types/shims/{form → client/form}/components/node-red-json-schema-form.vue.d.ts +21 -2
  20. package/types/shims/{form → client/form}/components/node-red-select-input.vue.d.ts +1 -0
  21. package/types/shims/{form → client/form}/components/node-red-typed-input.vue.d.ts +1 -0
  22. package/types/shims/client/types.d.ts +206 -0
  23. package/types/shims/components.d.ts +8 -8
  24. package/types/shims/constants.d.ts +4 -0
  25. package/types/shims/schema-options.d.ts +23 -10
  26. package/types/shims/typebox.d.ts +2 -2
  27. package/types/test-client-component.d.ts +170 -55
  28. package/types/test-client-e2e.d.ts +50 -0
  29. package/types/test-client-unit.d.ts +86 -22
  30. package/types/test-server-unit.d.ts +3 -1
  31. package/types/vite.d.ts +38 -9
  32. package/vite/index.js +733 -528
  33. /package/types/shims/{form → client/form}/components/node-red-config-input.vue.d.ts +0 -0
  34. /package/types/shims/{form → client/form}/components/node-red-input-label.vue.d.ts +0 -0
  35. /package/types/shims/{form → client/form}/components/node-red-input.vue.d.ts +0 -0
  36. /package/types/shims/{form → client/form}/components/node-red-toggle.vue.d.ts +0 -0
  37. /package/types/shims/{globals.d.ts → client/globals.d.ts} +0 -0
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";
@@ -52,6 +52,40 @@ var DEFAULT_EXTRA_FILES_COPY_TARGETS = [
52
52
  // src/vite/utils.ts
53
53
  import fs from "fs";
54
54
  import path from "path";
55
+
56
+ // src/vite/errors.ts
57
+ var PluginError = class extends Error {
58
+ constructor(message, code, cause) {
59
+ super(message);
60
+ this.code = code;
61
+ this.cause = cause;
62
+ this.name = "PluginError";
63
+ }
64
+ };
65
+ var NodeRedStartError = class extends PluginError {
66
+ constructor(cause) {
67
+ super("Failed to start Node-RED", "NODE_RED_START_FAILED", cause);
68
+ this.name = "NodeRedStartError";
69
+ }
70
+ };
71
+ var BuildError = class extends PluginError {
72
+ constructor(phase, cause) {
73
+ super(
74
+ `Failed to build ${phase}`,
75
+ `BUILD_${phase.toUpperCase()}_FAILED`,
76
+ cause
77
+ );
78
+ this.name = "BuildError";
79
+ }
80
+ };
81
+ var ConfigError = class extends PluginError {
82
+ constructor(message) {
83
+ super(message, "CONFIG_INVALID");
84
+ this.name = "ConfigError";
85
+ }
86
+ };
87
+
88
+ // src/vite/utils.ts
55
89
  function cleanDir(dir) {
56
90
  if (fs.existsSync(dir)) {
57
91
  fs.rmSync(dir, { recursive: true });
@@ -86,6 +120,23 @@ function getPackageName() {
86
120
  }
87
121
  return "node-red-nodes";
88
122
  }
123
+ var SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
124
+ function slugify(input) {
125
+ return input.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
126
+ }
127
+ function resolveSlug(userSlug) {
128
+ if (userSlug !== void 0) {
129
+ const trimmed = userSlug.trim();
130
+ if (!SLUG_PATTERN.test(trimmed)) {
131
+ const suggestion = slugify(trimmed);
132
+ throw new ConfigError(
133
+ `Invalid dev server slug ${JSON.stringify(userSlug)}. A slug must be URL-safe: lowercase letters, digits and single hyphens (matching ${String(SLUG_PATTERN)}).` + (suggestion ? ` Did you mean ${JSON.stringify(suggestion)}?` : "")
134
+ );
135
+ }
136
+ return trimmed;
137
+ }
138
+ return slugify(path.basename(process.cwd())) || "app";
139
+ }
89
140
  function mergeOptions(defaults, overrides) {
90
141
  if (!overrides) return { ...defaults };
91
142
  const result = { ...defaults };
@@ -106,17 +157,8 @@ function mergeOptions(defaults, overrides) {
106
157
  return result;
107
158
  }
108
159
 
109
- // src/vite/node-red-launcher.ts
110
- import { spawn, execSync } from "child_process";
111
- import getPort from "get-port";
112
- import detect from "detect-port";
113
- import { builtinModules, createRequire } from "module";
114
- import treeKill from "tree-kill";
115
- import fs2 from "fs";
116
- import os from "os";
117
- import path2 from "path";
118
- import { pathToFileURL } from "url";
119
- import { build as esbuild } from "esbuild";
160
+ // src/vite/node-red-launcher/index.ts
161
+ import fs4 from "fs";
120
162
 
121
163
  // src/vite/async-utils.ts
122
164
  function debounce(fn, delay) {
@@ -160,32 +202,6 @@ async function retry(fn, options = {}) {
160
202
  throw lastError;
161
203
  }
162
204
 
163
- // src/vite/errors.ts
164
- var PluginError = class extends Error {
165
- constructor(message, code, cause) {
166
- super(message);
167
- this.code = code;
168
- this.cause = cause;
169
- this.name = "PluginError";
170
- }
171
- };
172
- var NodeRedStartError = class extends PluginError {
173
- constructor(cause) {
174
- super("Failed to start Node-RED", "NODE_RED_START_FAILED", cause);
175
- this.name = "NodeRedStartError";
176
- }
177
- };
178
- var BuildError = class extends PluginError {
179
- constructor(phase, cause) {
180
- super(
181
- `Failed to build ${phase}`,
182
- `BUILD_${phase.toUpperCase()}_FAILED`,
183
- cause
184
- );
185
- this.name = "BuildError";
186
- }
187
- };
188
-
189
205
  // src/vite/logger.ts
190
206
  import * as clackLogger from "@clack/prompts";
191
207
  var color = {
@@ -259,200 +275,388 @@ var Logger = class _Logger {
259
275
  };
260
276
  var logger = new Logger({ name: "vite-plugin-node-red" });
261
277
 
262
- // src/vite/node-red-launcher.ts
263
- var NodeRedLauncher = class {
264
- compiledRuntimeSettingsFilepath = null;
265
- process = null;
266
- bufferedLogs = [];
267
- isReady = false;
268
- port = null;
269
- outDir;
270
- options;
271
- logger;
272
- constructor(outDir, options) {
273
- this.outDir = outDir;
274
- this.options = options;
275
- this.logger = new Logger({
276
- name: "vite-plugin-node-red",
277
- prefix: "node-red"
278
- });
278
+ // src/vite/node-red-launcher/entry-point.ts
279
+ import { exec } from "child_process";
280
+ import { randomUUID } from "crypto";
281
+ import { createRequire } from "module";
282
+ import fs2 from "fs";
283
+ import os from "os";
284
+ import path2 from "path";
285
+ function getNodeRedCommand(version) {
286
+ return version ? `node-red@${version}` : "node-red";
287
+ }
288
+ function resolveNodeRedFromLocalNodeModules() {
289
+ try {
290
+ const require_ = createRequire(path2.join(process.cwd(), "package.json"));
291
+ const pkgJsonPath = require_.resolve("node-red/package.json");
292
+ const pkgDir = path2.dirname(pkgJsonPath);
293
+ const pkg = JSON.parse(fs2.readFileSync(pkgJsonPath, "utf-8"));
294
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.["node-red"];
295
+ if (!bin) return null;
296
+ const entry = path2.resolve(pkgDir, bin);
297
+ return fs2.existsSync(entry) ? entry : null;
298
+ } catch {
299
+ return null;
279
300
  }
280
- get preferredPort() {
281
- return this.options.runtime?.port ?? 1880;
301
+ }
302
+ async function resolveNodeRed(options) {
303
+ const { version, npxTimeoutMs = 3e5, logger: logger2 } = options;
304
+ if (version && !/^[\w.^~<>=*-]+$/.test(version)) {
305
+ throw new NodeRedStartError(
306
+ new Error(`Invalid node-red version "${version}"`)
307
+ );
282
308
  }
283
- get restartDelay() {
284
- return this.options.restartDelay ?? 1e3;
309
+ const nodeRedCommand = getNodeRedCommand(version);
310
+ logger2.info(`Resolving ${nodeRedCommand} entry point...`);
311
+ const hasExplicitVersion = version !== void 0 && version !== "latest";
312
+ if (!hasExplicitVersion) {
313
+ const localEntry = resolveNodeRedFromLocalNodeModules();
314
+ if (localEntry) {
315
+ logger2.info(`Resolved from local node_modules: ${localEntry}`);
316
+ return localEntry;
317
+ }
285
318
  }
286
- get pid() {
287
- return this.process?.pid ?? null;
319
+ logger2.info(
320
+ hasExplicitVersion ? `Using configured version (${version}), downloading via npx...` : `Not found locally, downloading via npx (this may take a while)...`
321
+ );
322
+ const resolverScript = path2.join(
323
+ os.tmpdir(),
324
+ `nrg-resolve-node-red-${process.pid}-${randomUUID()}.cjs`
325
+ );
326
+ fs2.writeFileSync(
327
+ resolverScript,
328
+ `const fs = require("fs");
329
+ const path = require("path");
330
+ const isWin = process.platform === "win32";
331
+ const binName = isWin ? "node-red.cmd" : "node-red";
332
+ const dirs = process.env.PATH.split(path.delimiter);
333
+ for (const d of dirs) {
334
+ const f = path.join(d, binName);
335
+ if (fs.existsSync(f)) {
336
+ if (isWin) {
337
+ const nodeRedDir = path.resolve(d, "..", "node-red");
338
+ const pkg = JSON.parse(fs.readFileSync(path.join(nodeRedDir, "package.json"), "utf-8"));
339
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin["node-red"];
340
+ process.stdout.write(path.resolve(nodeRedDir, bin));
341
+ } else {
342
+ process.stdout.write(fs.realpathSync(f));
343
+ }
344
+ break;
288
345
  }
289
- get nodeRedCommand() {
290
- const version = this.options.runtime?.version;
291
- if (version === "latest") {
292
- return "node-red@latest";
346
+ }`
347
+ );
348
+ try {
349
+ const stdout = await new Promise((resolve, reject) => {
350
+ exec(
351
+ `npx --yes -p ${nodeRedCommand} node "${resolverScript}"`,
352
+ { timeout: npxTimeoutMs },
353
+ (error, stdout2) => {
354
+ if (error) reject(error);
355
+ else resolve(stdout2);
356
+ }
357
+ );
358
+ });
359
+ const entryPoint = stdout.trim();
360
+ if (!entryPoint || !fs2.existsSync(entryPoint)) {
361
+ throw new NodeRedStartError(
362
+ new Error(
363
+ `Could not resolve node-red entry point: ${entryPoint || "(empty)"}`
364
+ )
365
+ );
293
366
  }
294
- if (version) {
295
- return `node-red@${version}`;
367
+ logger2.info(`Resolved via npx: ${entryPoint}`);
368
+ return entryPoint;
369
+ } finally {
370
+ try {
371
+ fs2.unlinkSync(resolverScript);
372
+ } catch {
296
373
  }
297
- return "node-red";
298
374
  }
299
- findRuntimeSettingsFilepath() {
300
- const runtimeSettingsFilepath = this.options.runtime?.settingsFilepath;
301
- if (runtimeSettingsFilepath) {
302
- const resolved2 = path2.resolve(runtimeSettingsFilepath);
303
- if (fs2.existsSync(resolved2)) {
304
- return resolved2;
305
- }
306
- this.logger.warn(`Settings file not found: ${runtimeSettingsFilepath}`);
307
- return null;
308
- }
309
- const resolved = path2.resolve("node-red.settings.ts");
310
- if (fs2.existsSync(resolved)) {
311
- return resolved;
375
+ }
376
+
377
+ // src/vite/node-red-launcher/settings.ts
378
+ import { builtinModules } from "module";
379
+ import fs3 from "fs";
380
+ import os2 from "os";
381
+ import path3 from "path";
382
+ import { pathToFileURL } from "url";
383
+ import { build as esbuild } from "esbuild";
384
+ function findUserRuntimeSettingsFilepath(settingsFilepath, logger2) {
385
+ if (settingsFilepath) {
386
+ const resolved2 = path3.resolve(settingsFilepath);
387
+ if (fs3.existsSync(resolved2)) {
388
+ return resolved2;
312
389
  }
390
+ logger2.warn(`Settings file not found: ${settingsFilepath}`);
313
391
  return null;
314
392
  }
315
- async compileRuntimeSettingsFile(runtimeSettingsFilepath) {
316
- const compiledRuntimeSettingsFilepath = path2.join(
317
- os.tmpdir(),
318
- `node-red.settings.${process.pid}.cjs`
393
+ const resolved = path3.resolve("node-red.settings.ts");
394
+ if (fs3.existsSync(resolved)) {
395
+ return resolved;
396
+ }
397
+ return null;
398
+ }
399
+ async function compileRuntimeSettingsFile(runtimeSettingsFilepath, port) {
400
+ const compiledRuntimeSettingsFilepath = path3.join(
401
+ os2.tmpdir(),
402
+ `node-red.settings.${process.pid}-${port}.cjs`
403
+ );
404
+ const nodeBuiltins2 = [
405
+ ...builtinModules,
406
+ ...builtinModules.map((m) => `node:${m}`)
407
+ ];
408
+ const settingsDir = path3.dirname(runtimeSettingsFilepath).split(path3.sep).join("/");
409
+ const settingsFile = runtimeSettingsFilepath.split(path3.sep).join("/");
410
+ await esbuild({
411
+ entryPoints: [runtimeSettingsFilepath],
412
+ outfile: compiledRuntimeSettingsFilepath,
413
+ format: "cjs",
414
+ platform: "node",
415
+ target: "node18",
416
+ bundle: true,
417
+ define: {
418
+ "import.meta.dirname": JSON.stringify(settingsDir),
419
+ "import.meta.filename": JSON.stringify(settingsFile),
420
+ "import.meta.url": JSON.stringify(pathToFileURL(settingsFile).href)
421
+ },
422
+ external: [...nodeBuiltins2, "node-red", "@node-red/*"]
423
+ });
424
+ return compiledRuntimeSettingsFilepath;
425
+ }
426
+ async function generateRuntimeSettings(options) {
427
+ const { outDir, port, settingsFilepath, httpAdminRoot, logger: logger2 } = options;
428
+ const tempFiles = [];
429
+ const userRuntimeSettingsFilepath = findUserRuntimeSettingsFilepath(
430
+ settingsFilepath,
431
+ logger2
432
+ );
433
+ let compiledRuntimeSettingsFilepath = null;
434
+ if (userRuntimeSettingsFilepath) {
435
+ compiledRuntimeSettingsFilepath = await compileRuntimeSettingsFile(
436
+ userRuntimeSettingsFilepath,
437
+ port
319
438
  );
320
- const nodeBuiltins2 = [
321
- ...builtinModules,
322
- ...builtinModules.map((m) => `node:${m}`)
323
- ];
324
- const settingsDir = path2.dirname(runtimeSettingsFilepath).split(path2.sep).join("/");
325
- const settingsFile = runtimeSettingsFilepath.split(path2.sep).join("/");
326
- await esbuild({
327
- entryPoints: [runtimeSettingsFilepath],
328
- outfile: compiledRuntimeSettingsFilepath,
329
- format: "cjs",
330
- platform: "node",
331
- target: "node18",
332
- bundle: true,
333
- define: {
334
- "import.meta.dirname": JSON.stringify(settingsDir),
335
- "import.meta.filename": JSON.stringify(settingsFile),
336
- "import.meta.url": JSON.stringify(pathToFileURL(settingsFile).href)
337
- },
338
- external: [...nodeBuiltins2, "node-red", "@node-red/*"]
339
- });
340
- this.compiledRuntimeSettingsFilepath = compiledRuntimeSettingsFilepath;
341
- return compiledRuntimeSettingsFilepath;
342
- }
343
- async generateRuntimeSettingsFile() {
344
- const userRuntimeSettingsFilepath = this.findRuntimeSettingsFilepath();
345
- let compiledRuntimeSettingsFilepath = null;
346
- if (userRuntimeSettingsFilepath) {
347
- compiledRuntimeSettingsFilepath = await this.compileRuntimeSettingsFile(
348
- userRuntimeSettingsFilepath
349
- );
350
- }
351
- const outDir = path2.resolve(this.outDir).split(path2.sep).join("/");
352
- const cwd = process.cwd().split(path2.sep).join("/");
353
- const userDir = path2.resolve(cwd, ".node-red").split(path2.sep).join("/");
354
- const finalRuntimeSettingsFile = compiledRuntimeSettingsFilepath ? `
355
- const compiledRuntimeSettings = require("${compiledRuntimeSettingsFilepath.split(path2.sep).join("/")}");
439
+ tempFiles.push(compiledRuntimeSettingsFilepath);
440
+ }
441
+ const normalizedOutDir = path3.resolve(outDir).split(path3.sep).join("/");
442
+ const cwd = process.cwd().split(path3.sep).join("/");
443
+ const userDir = path3.resolve(cwd, ".node-red").split(path3.sep).join("/");
444
+ const userDirLiteral = JSON.stringify(userDir);
445
+ const outDirLiteral = JSON.stringify(normalizedOutDir);
446
+ const httpAdminRootAssignment = httpAdminRoot ? `settings.httpAdminRoot = ${JSON.stringify(httpAdminRoot)};
447
+ ` : "";
448
+ const httpAdminRootEntry = httpAdminRoot ? `
449
+ httpAdminRoot: ${JSON.stringify(httpAdminRoot)},` : "";
450
+ const finalRuntimeSettingsFile = compiledRuntimeSettingsFilepath ? `
451
+ const compiledRuntimeSettings = require(${JSON.stringify(
452
+ compiledRuntimeSettingsFilepath.split(path3.sep).join("/")
453
+ )});
356
454
  const settings = compiledRuntimeSettings.default || compiledRuntimeSettings;
357
- settings.uiPort = ${this.port};
358
- if(!settings.userDir){
359
- settings.userDir = "${userDir}";
455
+ settings.uiPort = ${port};
456
+ ${httpAdminRootAssignment}if(!settings.userDir){
457
+ settings.userDir = ${userDirLiteral};
360
458
  }
361
459
  settings.nodesDir = settings.nodesDir || [];
362
- if (!settings.nodesDir.includes("${outDir}")) {
363
- settings.nodesDir.push("${outDir}");
460
+ if (!settings.nodesDir.includes(${outDirLiteral})) {
461
+ settings.nodesDir.push(${outDirLiteral});
364
462
  }
365
463
  if(!settings.flowFile){
366
464
  settings.flowFile = "flows.json";
367
465
  }
466
+ // the welcome tour overlay intercepts pointer events \u2014 fatal for e2e and
467
+ // noise for dev; explicit user settings still win
468
+ settings.editorTheme = settings.editorTheme || {};
469
+ if (settings.editorTheme.tours === undefined) {
470
+ settings.editorTheme.tours = false;
471
+ }
368
472
  module.exports = settings;
369
473
  ` : `
370
474
  const settings = {
371
- uiPort: ${this.port},
372
- userDir: "${userDir}",
475
+ uiPort: ${port},
476
+ userDir: ${userDirLiteral},
373
477
  flowFile: "flows.json",
374
- nodesDir: ["${outDir}"],
478
+ nodesDir: [${outDirLiteral}],${httpAdminRootEntry}
479
+ // the welcome tour overlay intercepts pointer events \u2014 fatal for e2e
480
+ editorTheme: { tours: false },
375
481
  };
376
482
  module.exports = settings;
377
483
  `;
378
- const finalRuntimeSettingsFilepath = path2.join(
379
- os.tmpdir(),
380
- `node-red-settings-final-${process.pid}.cjs`
381
- );
382
- fs2.writeFileSync(finalRuntimeSettingsFilepath, finalRuntimeSettingsFile);
383
- this.compiledRuntimeSettingsFilepath = finalRuntimeSettingsFilepath;
384
- return finalRuntimeSettingsFilepath;
385
- }
386
- resolveFromLocalNodeModules() {
387
- try {
388
- const require_ = createRequire(path2.join(process.cwd(), "package.json"));
389
- const pkgJsonPath = require_.resolve("node-red/package.json");
390
- const pkgDir = path2.dirname(pkgJsonPath);
391
- const pkg = JSON.parse(fs2.readFileSync(pkgJsonPath, "utf-8"));
392
- const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.["node-red"];
393
- if (!bin) return null;
394
- const entry = path2.resolve(pkgDir, bin);
395
- return fs2.existsSync(entry) ? entry : null;
396
- } catch {
397
- return null;
484
+ const finalRuntimeSettingsFilepath = path3.join(
485
+ os2.tmpdir(),
486
+ `node-red-settings-final-${process.pid}-${port}.cjs`
487
+ );
488
+ fs3.writeFileSync(finalRuntimeSettingsFilepath, finalRuntimeSettingsFile);
489
+ tempFiles.push(finalRuntimeSettingsFilepath);
490
+ return { filepath: finalRuntimeSettingsFilepath, tempFiles };
491
+ }
492
+
493
+ // src/vite/node-red-launcher/process.ts
494
+ import { spawn } from "child_process";
495
+ import detect from "detect-port";
496
+ import getPort from "get-port";
497
+ import treeKill from "tree-kill";
498
+ var READY_MARKERS = ["Started flows", "Server now running"];
499
+ function start(options) {
500
+ const { entryPoint, settingsPath, args, onLine } = options;
501
+ const child = spawn(
502
+ process.execPath,
503
+ [entryPoint, "-s", settingsPath, ...args],
504
+ {
505
+ stdio: ["ignore", "pipe", "pipe"]
398
506
  }
399
- }
400
- resolveNodeRedEntryPoint() {
401
- this.logger.info(`Resolving ${this.nodeRedCommand} entry point...`);
402
- const localEntry = this.resolveFromLocalNodeModules();
403
- if (localEntry) {
404
- this.logger.info(`Resolved from local node_modules: ${localEntry}`);
405
- return localEntry;
507
+ );
508
+ let isReady = false;
509
+ let resolveReady;
510
+ let rejectReady;
511
+ const ready = new Promise((resolve, reject) => {
512
+ resolveReady = resolve;
513
+ rejectReady = reject;
514
+ });
515
+ const emitLine = (rawLine, source) => {
516
+ const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
517
+ if (!line) return;
518
+ onLine(line, source, isReady);
519
+ if (source === "stdout" && READY_MARKERS.some((marker) => line.includes(marker))) {
520
+ isReady = true;
521
+ resolveReady();
406
522
  }
407
- this.logger.info(
408
- `Not found locally, downloading via npx (this may take a while)...`
409
- );
410
- const resolverScript = path2.join(
411
- os.tmpdir(),
412
- `nrg-resolve-node-red-${process.pid}.cjs`
413
- );
414
- fs2.writeFileSync(
415
- resolverScript,
416
- `const fs = require("fs");
417
- const path = require("path");
418
- const isWin = process.platform === "win32";
419
- const binName = isWin ? "node-red.cmd" : "node-red";
420
- const dirs = process.env.PATH.split(path.delimiter);
421
- for (const d of dirs) {
422
- const f = path.join(d, binName);
423
- if (fs.existsSync(f)) {
424
- if (isWin) {
425
- const nodeRedDir = path.resolve(d, "..", "node-red");
426
- const pkg = JSON.parse(fs.readFileSync(path.join(nodeRedDir, "package.json"), "utf-8"));
427
- const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin["node-red"];
428
- process.stdout.write(path.resolve(nodeRedDir, bin));
429
- } else {
430
- process.stdout.write(fs.realpathSync(f));
523
+ };
524
+ const remainders = { stdout: "", stderr: "" };
525
+ const handleData = (data, source) => {
526
+ const lines = (remainders[source] + data.toString()).split("\n");
527
+ remainders[source] = lines.pop() ?? "";
528
+ for (const line of lines) {
529
+ emitLine(line, source);
431
530
  }
432
- break;
433
- }
434
- }`
435
- );
436
- try {
437
- const entryPoint = execSync(
438
- `npx --yes -p ${this.nodeRedCommand} -c "node ${resolverScript}"`,
439
- { encoding: "utf-8", timeout: 3e5 }
440
- ).trim();
441
- if (!entryPoint || !fs2.existsSync(entryPoint)) {
442
- throw new NodeRedStartError(
443
- new Error(
444
- `Could not resolve node-red entry point: ${entryPoint || "(empty)"}`
445
- )
446
- );
531
+ };
532
+ const flushRemainders = () => {
533
+ for (const source of ["stdout", "stderr"]) {
534
+ const rest = remainders[source];
535
+ remainders[source] = "";
536
+ if (rest) {
537
+ emitLine(rest, source);
447
538
  }
448
- this.logger.info(`Resolved via npx: ${entryPoint}`);
449
- return entryPoint;
450
- } finally {
451
- try {
452
- fs2.unlinkSync(resolverScript);
453
- } catch {
539
+ }
540
+ };
541
+ child.stdout?.on("data", (data) => handleData(data, "stdout"));
542
+ child.stderr?.on("data", (data) => handleData(data, "stderr"));
543
+ child.on("error", (error) => {
544
+ rejectReady(new NodeRedStartError(error));
545
+ });
546
+ child.on("exit", (code) => {
547
+ flushRemainders();
548
+ if (!isReady && code !== 0 && code !== null) {
549
+ rejectReady(
550
+ new NodeRedStartError(new Error(`Process exited with code ${code}`))
551
+ );
552
+ return;
553
+ }
554
+ resolveReady();
555
+ });
556
+ return { child, ready };
557
+ }
558
+ function kill(pid) {
559
+ return new Promise((resolve) => {
560
+ treeKill(pid, "SIGKILL", () => resolve());
561
+ });
562
+ }
563
+ async function stop(options) {
564
+ const { child, pid, gracefulTimeoutMs = 1e4, logger: logger2 } = options;
565
+ if (child.exitCode !== null || child.signalCode !== null) {
566
+ return;
567
+ }
568
+ const exited = new Promise((resolve) => {
569
+ child.once("exit", () => resolve());
570
+ treeKill(pid, "SIGTERM", (error) => {
571
+ if (error) {
572
+ try {
573
+ process.kill(pid, "SIGTERM");
574
+ } catch {
575
+ resolve();
576
+ }
454
577
  }
578
+ });
579
+ });
580
+ try {
581
+ await withTimeout(exited, gracefulTimeoutMs);
582
+ } catch {
583
+ logger2.warn("Graceful shutdown timed out, force killing...");
584
+ await kill(pid);
585
+ }
586
+ }
587
+ async function acquirePort(options) {
588
+ const { preferredPort, retryDelay = 2e3, logger: logger2 } = options;
589
+ const available = await detect(preferredPort);
590
+ if (available === preferredPort) {
591
+ return preferredPort;
592
+ }
593
+ logger2.warn(`Port ${preferredPort} is still in use, waiting...`);
594
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
595
+ const retryAvailable = await detect(preferredPort);
596
+ if (retryAvailable === preferredPort) {
597
+ return preferredPort;
598
+ }
599
+ const fallbackPort = await getPort({ port: preferredPort });
600
+ logger2.warn(
601
+ `Port ${preferredPort} still occupied, using port ${fallbackPort}`
602
+ );
603
+ return fallbackPort;
604
+ }
605
+ async function waitForPortRelease(port, options = {}) {
606
+ const { attempts = 10, delay = 300 } = options;
607
+ const checkPortUsage = async () => {
608
+ const availablePort = await detect(port);
609
+ if (availablePort !== port) {
610
+ throw new Error("Port still in use");
455
611
  }
612
+ };
613
+ try {
614
+ await retry(checkPortUsage, { attempts, delay });
615
+ return true;
616
+ } catch {
617
+ return false;
618
+ }
619
+ }
620
+
621
+ // src/vite/node-red-launcher/index.ts
622
+ var NodeRedLauncher = class {
623
+ operationQueue = Promise.resolve();
624
+ process = null;
625
+ unwatchExit = null;
626
+ nodeRedEntryPoint = null;
627
+ tempFiles = [];
628
+ bufferedLogs = [];
629
+ port = null;
630
+ outDir;
631
+ options;
632
+ _slug;
633
+ logger;
634
+ constructor(outDir, options, slug = "") {
635
+ this.outDir = outDir;
636
+ this.options = options;
637
+ this._slug = slug;
638
+ this.logger = new Logger({
639
+ name: "vite-plugin-node-red",
640
+ prefix: "node-red"
641
+ });
642
+ }
643
+ get preferredPort() {
644
+ return this.options.runtime?.port ?? 1880;
645
+ }
646
+ get slug() {
647
+ return this._slug;
648
+ }
649
+ get basePath() {
650
+ return this._slug ? `/${this._slug}/` : "/";
651
+ }
652
+ get restartDelay() {
653
+ return this.options.restartDelay ?? 1e3;
654
+ }
655
+ get pid() {
656
+ return this.process?.child.pid ?? null;
657
+ }
658
+ get nodeRedCommand() {
659
+ return getNodeRedCommand(this.options.runtime?.version);
456
660
  }
457
661
  log(line) {
458
662
  if (line.includes("Server now running at")) {
@@ -460,150 +664,122 @@ for (const d of dirs) {
460
664
  }
461
665
  this.logger.raw(line);
462
666
  }
463
- async start() {
464
- const available = await detect(this.preferredPort);
465
- if (available === this.preferredPort) {
466
- this.port = this.preferredPort;
667
+ handleProcessLine(line, source, ready) {
668
+ if (!ready) {
669
+ this.bufferedLogs.push(line);
670
+ } else if (source === "stderr") {
671
+ this.logger.error(line);
467
672
  } else {
673
+ this.log(line);
674
+ }
675
+ }
676
+ async killProcess() {
677
+ if (!this.process) return;
678
+ this.stopWatchingExit();
679
+ const pid = this.process.child.pid;
680
+ if (pid) {
681
+ await kill(pid);
682
+ }
683
+ this.process = null;
684
+ }
685
+ watchForUnexpectedExit(managed) {
686
+ const onExit = (code, signal) => {
687
+ this.unwatchExit = null;
688
+ this.process = null;
468
689
  this.logger.warn(
469
- `Port ${this.preferredPort} is still in use, waiting...`
690
+ `Node-RED exited unexpectedly (${signal ?? `code ${code}`})`
470
691
  );
471
- await new Promise((resolve) => setTimeout(resolve, 2e3));
472
- const retryAvailable = await detect(this.preferredPort);
473
- if (retryAvailable === this.preferredPort) {
474
- this.port = this.preferredPort;
475
- } else {
476
- this.logger.warn(
477
- `Port ${this.preferredPort} still occupied, using port ${retryAvailable}`
478
- );
479
- this.port = await getPort({ port: this.preferredPort });
480
- }
481
- }
482
- const nodeRedEntryPoint = this.resolveNodeRedEntryPoint();
483
- const startProcess = () => {
484
- return new Promise(async (resolve, reject) => {
485
- try {
486
- const settingsPath = await this.generateRuntimeSettingsFile();
487
- const args = this.options.args ?? [];
488
- this.bufferedLogs = [];
489
- this.isReady = false;
490
- this.process = spawn(
491
- process.execPath,
492
- [nodeRedEntryPoint, "-s", settingsPath, ...args],
493
- {
494
- stdio: ["ignore", "pipe", "pipe"]
495
- }
496
- );
497
- this.process.stdout?.on("data", (data) => {
498
- const lines = data.toString().split("\n").filter(Boolean);
499
- for (const line of lines) {
500
- if (this.isReady) {
501
- this.log(line);
502
- } else {
503
- this.bufferedLogs.push(line);
504
- }
505
- if (line.includes("Started flows") || line.includes("Server now running")) {
506
- this.isReady = true;
507
- resolve(void 0);
508
- }
509
- }
510
- });
511
- this.process.stderr?.on("data", (data) => {
512
- const lines = data.toString().split("\n").filter(Boolean);
513
- for (const line of lines) {
514
- if (this.isReady) {
515
- this.logger.error(`${line}`);
516
- } else {
517
- this.bufferedLogs.push(line);
518
- }
519
- }
520
- });
521
- this.process.on("error", (error) => {
522
- reject(new NodeRedStartError(error));
523
- });
524
- this.process.on("exit", (code) => {
525
- if (!this.isReady && code !== 0 && code !== null) {
526
- reject(
527
- new NodeRedStartError(
528
- new Error(`Process exited with code ${code}`)
529
- )
530
- );
531
- }
532
- resolve(void 0);
533
- });
534
- } catch (error) {
535
- reject(new NodeRedStartError(error));
536
- }
537
- });
538
692
  };
693
+ managed.child.once("exit", onExit);
694
+ this.unwatchExit = () => managed.child.off("exit", onExit);
695
+ }
696
+ stopWatchingExit() {
697
+ this.unwatchExit?.();
698
+ this.unwatchExit = null;
699
+ }
700
+ // start/stop interleaving (e.g. SIGINT while Node-RED is booting) would
701
+ // race on process/port state and could leak a spawned process, so all
702
+ // lifecycle operations run strictly one at a time
703
+ enqueue(operation) {
704
+ const result = this.operationQueue.then(operation, operation);
705
+ this.operationQueue = result.catch(() => {
706
+ });
707
+ return result;
708
+ }
709
+ async start() {
710
+ return this.enqueue(() => this.doStart());
711
+ }
712
+ async stop(skipPortUsageCheck = false) {
713
+ return this.enqueue(() => this.doStop(skipPortUsageCheck));
714
+ }
715
+ async doStart() {
539
716
  try {
717
+ this.port = await acquirePort({
718
+ preferredPort: this.preferredPort,
719
+ logger: this.logger
720
+ });
721
+ if (!this.nodeRedEntryPoint || !fs4.existsSync(this.nodeRedEntryPoint)) {
722
+ this.nodeRedEntryPoint = await resolveNodeRed({
723
+ version: this.options.runtime?.version,
724
+ logger: this.logger
725
+ });
726
+ }
727
+ const nodeRedEntryPoint = this.nodeRedEntryPoint;
728
+ const settings = await generateRuntimeSettings({
729
+ outDir: this.outDir,
730
+ port: this.port,
731
+ settingsFilepath: this.options.runtime?.settingsFilepath,
732
+ httpAdminRoot: this._slug ? this.basePath : void 0,
733
+ logger: this.logger
734
+ });
735
+ for (const file of settings.tempFiles) {
736
+ if (!this.tempFiles.includes(file)) {
737
+ this.tempFiles.push(file);
738
+ }
739
+ }
740
+ const startProcess = async () => {
741
+ await this.killProcess();
742
+ this.bufferedLogs = [];
743
+ this.process = start({
744
+ entryPoint: nodeRedEntryPoint,
745
+ settingsPath: settings.filepath,
746
+ args: this.options.args ?? [],
747
+ onLine: (line, source, ready) => this.handleProcessLine(line, source, ready)
748
+ });
749
+ await this.process.ready;
750
+ };
540
751
  await retry(startProcess, { attempts: 3, delay: 100 });
752
+ this.watchForUnexpectedExit(this.process);
541
753
  return this.port;
542
754
  } catch (error) {
543
- if (this.process) {
544
- const pid = this.process.pid;
545
- if (pid) {
546
- treeKill(pid, "SIGKILL");
547
- }
548
- this.process = null;
549
- }
550
- throw new NodeRedStartError(error);
755
+ await this.killProcess();
756
+ throw error instanceof NodeRedStartError ? error : new NodeRedStartError(error);
551
757
  }
552
758
  }
553
- async stop(skipPortUsageCheck = false) {
759
+ async doStop(skipPortUsageCheck) {
554
760
  if (!this.process) return;
555
- const pid = this.process.pid;
761
+ this.stopWatchingExit();
762
+ const pid = this.process.child.pid;
556
763
  const currentPort = this.port;
557
764
  if (!pid) {
558
765
  this.process = null;
766
+ this.port = null;
559
767
  return;
560
768
  }
561
- const stopProcess = new Promise((resolve) => {
562
- this.process.once("exit", () => {
563
- this.process = null;
564
- resolve(void 0);
565
- });
566
- treeKill(pid, "SIGTERM", (error) => {
567
- if (error) {
568
- try {
569
- process.kill(pid, "SIGTERM");
570
- } catch {
571
- this.process = null;
572
- resolve(void 0);
573
- }
574
- }
575
- });
769
+ await stop({
770
+ child: this.process.child,
771
+ pid,
772
+ logger: this.logger
576
773
  });
577
- try {
578
- await withTimeout(stopProcess, 1e4);
579
- } catch {
580
- this.logger.warn("Graceful shutdown timed out, force killing...");
581
- await new Promise((resolve) => {
582
- treeKill(pid, "SIGKILL", () => {
583
- this.process = null;
584
- resolve(void 0);
585
- });
586
- });
587
- }
774
+ this.process = null;
588
775
  if (!skipPortUsageCheck && currentPort) {
589
- const checkPortUsage = async () => {
590
- const availablePort = await detect(currentPort);
591
- if (availablePort !== currentPort) {
592
- throw new Error("Port still in use");
593
- }
594
- };
595
- try {
596
- await retry(checkPortUsage, { attempts: 10, delay: 300 });
597
- } catch {
776
+ const released = await waitForPortRelease(currentPort);
777
+ if (!released) {
598
778
  this.logger.warn(
599
779
  `Port ${currentPort} still in use after stop. Force killing...`
600
780
  );
601
- if (pid) {
602
- await new Promise((resolve) => {
603
- treeKill(pid, "SIGKILL", () => resolve());
604
- });
605
- await new Promise((resolve) => setTimeout(resolve, 1e3));
606
- }
781
+ await kill(pid);
782
+ await waitForPortRelease(currentPort);
607
783
  }
608
784
  }
609
785
  this.port = null;
@@ -615,32 +791,35 @@ for (const d of dirs) {
615
791
  this.bufferedLogs = [];
616
792
  }
617
793
  cleanup() {
618
- if (this.compiledRuntimeSettingsFilepath && fs2.existsSync(this.compiledRuntimeSettingsFilepath)) {
619
- fs2.unlinkSync(this.compiledRuntimeSettingsFilepath);
620
- this.compiledRuntimeSettingsFilepath = null;
794
+ for (const file of this.tempFiles) {
795
+ try {
796
+ fs4.unlinkSync(file);
797
+ } catch {
798
+ }
621
799
  }
800
+ this.tempFiles = [];
622
801
  }
623
802
  };
624
803
 
625
804
  // src/vite/plugins/server.ts
626
805
  import chokidar from "chokidar";
627
806
  import treeKill2 from "tree-kill";
628
- import path12 from "path";
807
+ import path13 from "path";
629
808
 
630
809
  // src/vite/server/build.ts
631
810
  import { build as viteBuild } from "vite";
632
- import fs5 from "fs";
633
- import path5 from "path";
811
+ import fs7 from "fs";
812
+ import path6 from "path";
634
813
 
635
814
  // src/vite/server/plugins/type-generator.ts
636
815
  import dts from "vite-plugin-dts";
637
- import fs3 from "fs";
638
- import path3 from "path";
816
+ import fs5 from "fs";
817
+ import path4 from "path";
639
818
  import ts from "typescript";
640
819
  function collectTsFiles(dir) {
641
- if (!fs3.existsSync(dir)) return [];
642
- return fs3.readdirSync(dir, { withFileTypes: true }).flatMap((dirent) => {
643
- const full = path3.join(dir, dirent.name);
820
+ if (!fs5.existsSync(dir)) return [];
821
+ return fs5.readdirSync(dir, { withFileTypes: true }).flatMap((dirent) => {
822
+ const full = path4.join(dir, dirent.name);
644
823
  if (dirent.isDirectory()) return collectTsFiles(full);
645
824
  if (dirent.isFile() && dirent.name.endsWith(".ts") && !dirent.name.endsWith(".d.ts"))
646
825
  return [full];
@@ -655,7 +834,7 @@ var BASE_CLASS_SLOTS = {
655
834
  ConfigNode: ["Config", "Credentials", "Settings"]
656
835
  };
657
836
  function getNodeTypeExports(filePath) {
658
- const content = fs3.readFileSync(filePath, "utf-8");
837
+ const content = fs5.readFileSync(filePath, "utf-8");
659
838
  const source = ts.createSourceFile(
660
839
  filePath,
661
840
  content,
@@ -717,7 +896,7 @@ var SCHEMA_PROP_SEMANTICS = {
717
896
  settingsSchema: "SettingsSchema"
718
897
  };
719
898
  function getSchemaReferences(filePath) {
720
- const content = fs3.readFileSync(filePath, "utf-8");
899
+ const content = fs5.readFileSync(filePath, "utf-8");
721
900
  const source = ts.createSourceFile(
722
901
  filePath,
723
902
  content,
@@ -809,7 +988,7 @@ function getSchemaReferences(filePath) {
809
988
  return result;
810
989
  }
811
990
  function getFactoryInfo(filePath) {
812
- const content = fs3.readFileSync(filePath, "utf-8");
991
+ const content = fs5.readFileSync(filePath, "utf-8");
813
992
  const source = ts.createSourceFile(
814
993
  filePath,
815
994
  content,
@@ -843,13 +1022,13 @@ function buildTypeArg(schemaMap, semanticName, portNameMap) {
843
1022
  return `[${names.map((n) => `Infer<typeof ${n}>`).join(", ")}]`;
844
1023
  }
845
1024
  function buildNodeReexports(srcDir, entryFile) {
846
- const nodesDir = path3.join(srcDir, "nodes");
1025
+ const nodesDir = path4.join(srcDir, "nodes");
847
1026
  const nodeFiles = collectTsFiles(nodesDir);
848
1027
  return nodeFiles.map((file) => {
849
- const rel = path3.relative(path3.dirname(entryFile), file).replace(/\\/g, "/");
1028
+ const rel = path4.relative(path4.dirname(entryFile), file).replace(/\\/g, "/");
850
1029
  const relPath = rel.startsWith(".") ? rel : `./${rel}`;
851
1030
  const specifier = relPath.replace(/\.ts$/, "");
852
- const ns = toPascalCase(path3.basename(file, ".ts"));
1031
+ const ns = toPascalCase(path4.basename(file, ".ts"));
853
1032
  const schemaRefs = getSchemaReferences(file);
854
1033
  const factoryInfo = getFactoryInfo(file);
855
1034
  const lines = [];
@@ -876,9 +1055,9 @@ function buildNodeReexports(srcDir, entryFile) {
876
1055
  if (hasSchemas) {
877
1056
  const bySource2 = /* @__PURE__ */ new Map();
878
1057
  for (const ref of schemaRefs) {
879
- const resolvedSource = path3.relative(
880
- path3.dirname(entryFile),
881
- path3.resolve(path3.dirname(file), ref.importSource)
1058
+ const resolvedSource = path4.relative(
1059
+ path4.dirname(entryFile),
1060
+ path4.resolve(path4.dirname(file), ref.importSource)
882
1061
  ).replace(/\\/g, "/");
883
1062
  const sourceSpecifier = resolvedSource.startsWith(".") ? resolvedSource : `./${resolvedSource}`;
884
1063
  if (!bySource2.has(sourceSpecifier))
@@ -924,9 +1103,9 @@ function buildNodeReexports(srcDir, entryFile) {
924
1103
  }
925
1104
  const bySource = /* @__PURE__ */ new Map();
926
1105
  for (const ref of schemaRefs) {
927
- const resolvedSource = path3.relative(
928
- path3.dirname(entryFile),
929
- path3.resolve(path3.dirname(file), ref.importSource)
1106
+ const resolvedSource = path4.relative(
1107
+ path4.dirname(entryFile),
1108
+ path4.resolve(path4.dirname(file), ref.importSource)
930
1109
  ).replace(/\\/g, "/");
931
1110
  const sourceSpecifier = resolvedSource.startsWith(".") ? resolvedSource : `./${resolvedSource}`;
932
1111
  if (!bySource.has(sourceSpecifier)) bySource.set(sourceSpecifier, []);
@@ -940,12 +1119,12 @@ function buildNodeReexports(srcDir, entryFile) {
940
1119
  }
941
1120
  function typeGenerator(options) {
942
1121
  const { srcDir, outDir, entryFiles } = options;
943
- const serverTsconfig = path3.resolve(srcDir, "tsconfig.json");
944
- const rootTsconfig = path3.resolve("tsconfig.json");
945
- const userTsconfig = fs3.existsSync(serverTsconfig) ? serverTsconfig : rootTsconfig;
946
- const clientDir = path3.relative(
1122
+ const serverTsconfig = path4.resolve(srcDir, "tsconfig.json");
1123
+ const rootTsconfig = path4.resolve("tsconfig.json");
1124
+ const userTsconfig = fs5.existsSync(serverTsconfig) ? serverTsconfig : rootTsconfig;
1125
+ const clientDir = path4.relative(
947
1126
  process.cwd(),
948
- path3.join(path3.dirname(srcDir), "client")
1127
+ path4.join(path4.dirname(srcDir), "client")
949
1128
  );
950
1129
  const augmentedContents = /* @__PURE__ */ new Map();
951
1130
  let origReadFile = null;
@@ -957,9 +1136,9 @@ function typeGenerator(options) {
957
1136
  for (const entryFile of entryFiles) {
958
1137
  const reexports = buildNodeReexports(srcDir, entryFile);
959
1138
  if (!reexports) continue;
960
- const original = fs3.readFileSync(entryFile, "utf-8");
1139
+ const original = fs5.readFileSync(entryFile, "utf-8");
961
1140
  augmentedContents.set(
962
- path3.resolve(entryFile),
1141
+ path4.resolve(entryFile),
963
1142
  `${original}
964
1143
  ${reexports}
965
1144
  `
@@ -968,7 +1147,7 @@ ${reexports}
968
1147
  if (augmentedContents.size === 0) return;
969
1148
  origReadFile = ts.sys.readFile.bind(ts.sys);
970
1149
  ts.sys.readFile = (fileName, encoding) => {
971
- const resolved = path3.resolve(fileName);
1150
+ const resolved = path4.resolve(fileName);
972
1151
  return augmentedContents.get(resolved) ?? origReadFile(fileName, encoding);
973
1152
  };
974
1153
  },
@@ -992,7 +1171,7 @@ ${reexports}
992
1171
  }
993
1172
  },
994
1173
  outDir,
995
- ...fs3.existsSync(userTsconfig) && { tsconfigPath: userTsconfig },
1174
+ ...fs5.existsSync(userTsconfig) && { tsconfigPath: userTsconfig },
996
1175
  compilerOptions: {
997
1176
  noEmit: false,
998
1177
  declaration: true,
@@ -1065,8 +1244,8 @@ function esmWrapper() {
1065
1244
  }
1066
1245
 
1067
1246
  // src/vite/server/plugins/package-json-generator.ts
1068
- import fs4 from "fs";
1069
- import path4 from "path";
1247
+ import fs6 from "fs";
1248
+ import path5 from "path";
1070
1249
  import { builtinModules as builtinModules2 } from "node:module";
1071
1250
  var nodeBuiltins = /* @__PURE__ */ new Set([
1072
1251
  ...builtinModules2,
@@ -1155,13 +1334,13 @@ function packageJsonGenerator(options) {
1155
1334
  }
1156
1335
  },
1157
1336
  closeBundle() {
1158
- const rootPackageJsonPath = path4.resolve("./package.json");
1159
- if (!fs4.existsSync(rootPackageJsonPath)) {
1337
+ const rootPackageJsonPath = path5.resolve("./package.json");
1338
+ if (!fs6.existsSync(rootPackageJsonPath)) {
1160
1339
  logger.warn(`package.json not found: ${rootPackageJsonPath}`);
1161
1340
  return;
1162
1341
  }
1163
1342
  const rootPackageJson = JSON.parse(
1164
- fs4.readFileSync(rootPackageJsonPath, "utf-8")
1343
+ fs6.readFileSync(rootPackageJsonPath, "utf-8")
1165
1344
  );
1166
1345
  const sourceDeps = rootPackageJson.dependencies ?? {};
1167
1346
  const peerDeps = rootPackageJson.peerDependencies ?? {};
@@ -1173,12 +1352,12 @@ function packageJsonGenerator(options) {
1173
1352
  if (sourceDeps[dep]) {
1174
1353
  distDependencies[dep] = sourceDeps[dep];
1175
1354
  } else {
1176
- const dependencyPackageJsonPath = path4.resolve(
1355
+ const dependencyPackageJsonPath = path5.resolve(
1177
1356
  `./node_modules/${dep}/package.json`
1178
1357
  );
1179
- if (fs4.existsSync(dependencyPackageJsonPath)) {
1358
+ if (fs6.existsSync(dependencyPackageJsonPath)) {
1180
1359
  const dependencyPackageJson = JSON.parse(
1181
- fs4.readFileSync(dependencyPackageJsonPath, "utf-8")
1360
+ fs6.readFileSync(dependencyPackageJsonPath, "utf-8")
1182
1361
  );
1183
1362
  distDependencies[dep] = `^${dependencyPackageJson.version}`;
1184
1363
  }
@@ -1215,11 +1394,11 @@ function packageJsonGenerator(options) {
1215
1394
  }
1216
1395
  }
1217
1396
  }
1218
- if (!fs4.existsSync(outDir)) {
1219
- fs4.mkdirSync(outDir, { recursive: true });
1397
+ if (!fs6.existsSync(outDir)) {
1398
+ fs6.mkdirSync(outDir, { recursive: true });
1220
1399
  }
1221
- fs4.writeFileSync(
1222
- path4.join(outDir, "package.json"),
1400
+ fs6.writeFileSync(
1401
+ path5.join(outDir, "package.json"),
1223
1402
  JSON.stringify(distPackageJson, null, 2)
1224
1403
  );
1225
1404
  }
@@ -1238,11 +1417,11 @@ async function build(serverOpts, buildContext) {
1238
1417
  nodeTarget = "node22"
1239
1418
  } = serverOpts;
1240
1419
  const entries = Array.isArray(entry) ? entry : [entry];
1241
- const resolvedSrcDir = path5.resolve(srcDir);
1420
+ const resolvedSrcDir = path6.resolve(srcDir);
1242
1421
  const entryPoints = {};
1243
1422
  for (const entry2 of entries) {
1244
- const entryFilePath = path5.join(resolvedSrcDir, entry2);
1245
- if (fs5.existsSync(entryFilePath)) {
1423
+ const entryFilePath = path6.join(resolvedSrcDir, entry2);
1424
+ if (fs7.existsSync(entryFilePath)) {
1246
1425
  const fileName = entry2.replace(/\.ts$/, "");
1247
1426
  entryPoints[fileName] = entryFilePath;
1248
1427
  }
@@ -1318,7 +1497,7 @@ module.exports = function (RED) {
1318
1497
  });
1319
1498
  };
1320
1499
  `;
1321
- fs5.writeFileSync(path5.join(buildContext.outDir, "index.js"), bridgeCode);
1500
+ fs7.writeFileSync(path6.join(buildContext.outDir, "index.js"), bridgeCode);
1322
1501
  }
1323
1502
  } catch (error) {
1324
1503
  throw new BuildError("server", error);
@@ -1328,12 +1507,12 @@ module.exports = function (RED) {
1328
1507
  // src/vite/client/build.ts
1329
1508
  import { build as viteBuild2 } from "vite";
1330
1509
  import vue from "@vitejs/plugin-vue";
1331
- import fs11 from "fs";
1332
- import path11 from "path";
1510
+ import fs13 from "fs";
1511
+ import path12 from "path";
1333
1512
 
1334
1513
  // src/vite/client/plugins/help-generator.ts
1335
- import fs6 from "fs";
1336
- import path6 from "path";
1514
+ import fs8 from "fs";
1515
+ import path7 from "path";
1337
1516
  import { pathToFileURL as pathToFileURL2 } from "url";
1338
1517
  import { createRequire as createRequire2 } from "module";
1339
1518
 
@@ -1655,9 +1834,9 @@ ${table}
1655
1834
  `;
1656
1835
  }
1657
1836
  function loadNodeLabels(labelPath) {
1658
- if (!fs6.existsSync(labelPath)) return {};
1837
+ if (!fs8.existsSync(labelPath)) return {};
1659
1838
  try {
1660
- const raw = JSON.parse(fs6.readFileSync(labelPath, "utf-8"));
1839
+ const raw = JSON.parse(fs8.readFileSync(labelPath, "utf-8"));
1661
1840
  return {
1662
1841
  description: raw.description,
1663
1842
  configs: raw.configs,
@@ -1699,10 +1878,10 @@ function generateHelpDoc(nodeClass, labels, t) {
1699
1878
  if (inputSection) lines.push(inputSection);
1700
1879
  }
1701
1880
  if (nodeClass.outputsSchema) {
1702
- const os2 = nodeClass.outputsSchema;
1703
- if (Array.isArray(os2)) {
1881
+ const os3 = nodeClass.outputsSchema;
1882
+ if (Array.isArray(os3)) {
1704
1883
  const portSections = [];
1705
- os2.forEach((schema, i) => {
1884
+ os3.forEach((schema, i) => {
1706
1885
  const title = `${t.sections.port} ${i + 1}`;
1707
1886
  const portPropLabels = labels.outputs?.[i];
1708
1887
  const section = generateSchemaSection({
@@ -1721,9 +1900,9 @@ function generateHelpDoc(nodeClass, labels, t) {
1721
1900
  ${portSections.join("\n")}`
1722
1901
  );
1723
1902
  }
1724
- } else if (!("type" in os2 || "properties" in os2)) {
1903
+ } else if (!("type" in os3 || "properties" in os3)) {
1725
1904
  const portSections = [];
1726
- for (const [portName, schema] of Object.entries(os2)) {
1905
+ for (const [portName, schema] of Object.entries(os3)) {
1727
1906
  const portPropLabels = labels.outputs?.[portName];
1728
1907
  const section = generateSchemaSection({
1729
1908
  title: portName,
@@ -1745,7 +1924,7 @@ ${portSections.join("\n")}`
1745
1924
  const outputPropLabels = labels.outputs?.[0];
1746
1925
  const section = generateSchemaSection({
1747
1926
  title: t.sections.output,
1748
- schema: os2,
1927
+ schema: os3,
1749
1928
  t,
1750
1929
  labels: outputPropLabels,
1751
1930
  includeDefault: false
@@ -1756,9 +1935,9 @@ ${portSections.join("\n")}`
1756
1935
  return lines.join("\n").trim();
1757
1936
  }
1758
1937
  function discoverLanguages(labelsDir, nodeType) {
1759
- const nodeLabelsDir = path6.join(labelsDir, nodeType);
1760
- if (!fs6.existsSync(nodeLabelsDir)) return [];
1761
- return fs6.readdirSync(nodeLabelsDir).filter((f) => f.endsWith(".json")).map((f) => path6.basename(f, ".json"));
1938
+ const nodeLabelsDir = path7.join(labelsDir, nodeType);
1939
+ if (!fs8.existsSync(nodeLabelsDir)) return [];
1940
+ return fs8.readdirSync(nodeLabelsDir).filter((f) => f.endsWith(".json")).map((f) => path7.basename(f, ".json"));
1762
1941
  }
1763
1942
  function helpGenerator(options) {
1764
1943
  const { outDir, localesOutDir, docsDir, labelsDir } = options;
@@ -1767,15 +1946,15 @@ function helpGenerator(options) {
1767
1946
  apply: "build",
1768
1947
  enforce: "post",
1769
1948
  async closeBundle() {
1770
- const esmPath = path6.resolve(outDir, "index.mjs");
1771
- const cjsPath = path6.resolve(outDir, "index.js");
1949
+ const esmPath = path7.resolve(outDir, "index.mjs");
1950
+ const cjsPath = path7.resolve(outDir, "index.js");
1772
1951
  let packageFn;
1773
1952
  try {
1774
- if (fs6.existsSync(esmPath)) {
1953
+ if (fs8.existsSync(esmPath)) {
1775
1954
  const fileUrl = pathToFileURL2(esmPath).href + `?t=${Date.now()}`;
1776
1955
  const mod = await import(fileUrl);
1777
1956
  packageFn = mod?.default ?? mod;
1778
- } else if (fs6.existsSync(cjsPath)) {
1957
+ } else if (fs8.existsSync(cjsPath)) {
1779
1958
  const require2 = createRequire2(import.meta.url);
1780
1959
  delete require2.cache[cjsPath];
1781
1960
  const rawMod = require2(cjsPath);
@@ -1792,10 +1971,10 @@ function helpGenerator(options) {
1792
1971
  const languages = discoverLanguages(labelsDir, type);
1793
1972
  if (!languages.includes("en-US")) languages.push("en-US");
1794
1973
  for (const lang of languages) {
1795
- const manualMd = path6.join(docsDir, type, `${lang}.md`);
1796
- const manualHtml = path6.join(docsDir, type, `${lang}.html`);
1797
- if (fs6.existsSync(manualMd) || fs6.existsSync(manualHtml)) continue;
1798
- const labelPath = path6.join(labelsDir, type, `${lang}.json`);
1974
+ const manualMd = path7.join(docsDir, type, `${lang}.md`);
1975
+ const manualHtml = path7.join(docsDir, type, `${lang}.html`);
1976
+ if (fs8.existsSync(manualMd) || fs8.existsSync(manualHtml)) continue;
1977
+ const labelPath = path7.join(labelsDir, type, `${lang}.json`);
1799
1978
  const labels = loadNodeLabels(labelPath);
1800
1979
  const t = getHelpTranslations(lang);
1801
1980
  const content = generateHelpDoc(NodeClass, labels, t);
@@ -1809,11 +1988,11 @@ ${content}
1809
1988
  }
1810
1989
  }
1811
1990
  for (const [lang, scripts] of helpByLang) {
1812
- const langDir = path6.join(localesOutDir, lang);
1813
- fs6.mkdirSync(langDir, { recursive: true });
1814
- const indexPath = path6.join(langDir, "index.html");
1815
- const existing = fs6.existsSync(indexPath) ? fs6.readFileSync(indexPath, "utf-8") : "";
1816
- fs6.writeFileSync(
1991
+ const langDir = path7.join(localesOutDir, lang);
1992
+ fs8.mkdirSync(langDir, { recursive: true });
1993
+ const indexPath = path7.join(langDir, "index.html");
1994
+ const existing = fs8.existsSync(indexPath) ? fs8.readFileSync(indexPath, "utf-8") : "";
1995
+ fs8.writeFileSync(
1817
1996
  indexPath,
1818
1997
  existing + (existing ? "\n" : "") + scripts.join("\n"),
1819
1998
  "utf-8"
@@ -1825,8 +2004,8 @@ ${content}
1825
2004
 
1826
2005
  // src/vite/client/plugins/html-generator.ts
1827
2006
  import mime from "mime-types";
1828
- import fs7 from "fs";
1829
- import path7 from "path";
2007
+ import fs9 from "fs";
2008
+ import path8 from "path";
1830
2009
  function htmlGenerator(options) {
1831
2010
  const { packageName, licensePath } = options;
1832
2011
  return {
@@ -1836,7 +2015,7 @@ function htmlGenerator(options) {
1836
2015
  generateBundle(_, bundle) {
1837
2016
  const resourcesTags = Object.keys(bundle).map((fileName) => {
1838
2017
  const asset = bundle[fileName];
1839
- const srcPath = path7.join(
2018
+ const srcPath = path8.join(
1840
2019
  "resources",
1841
2020
  packageName,
1842
2021
  fileName.replace(/^resources\/?/, "")
@@ -1864,8 +2043,8 @@ function htmlGenerator(options) {
1864
2043
  return null;
1865
2044
  }
1866
2045
  }).filter(Boolean).join("\n");
1867
- const licenseBanner = licensePath && fs7.existsSync(licensePath) ? `<!--
1868
- ${fs7.readFileSync(licensePath, "utf-8")}
2046
+ const licenseBanner = licensePath && fs9.existsSync(licensePath) ? `<!--
2047
+ ${fs9.readFileSync(licensePath, "utf-8")}
1869
2048
  -->` : "";
1870
2049
  this.emitFile({
1871
2050
  type: "asset",
@@ -1878,8 +2057,8 @@ ${resourcesTags}`
1878
2057
  }
1879
2058
 
1880
2059
  // src/vite/client/plugins/locales-generator.ts
1881
- import fs8 from "fs";
1882
- import path8 from "path";
2060
+ import fs10 from "fs";
2061
+ import path9 from "path";
1883
2062
  import { merge } from "es-toolkit";
1884
2063
  function localesGenerator(options) {
1885
2064
  const { outDir, docsDir, labelsDir } = options;
@@ -1897,103 +2076,113 @@ function localesGenerator(options) {
1897
2076
  ];
1898
2077
  const frameworkLabels = {
1899
2078
  "en-US": {
1900
- configs: { name: "Name" },
2079
+ configs: { name: "Name", returnProperty: "Return key" },
1901
2080
  toggles: {
1902
2081
  validateInput: "Validate Input",
1903
2082
  validateOutput: "Validate Output",
1904
2083
  errorPort: "Error Port",
1905
2084
  completePort: "Complete Port",
1906
- statusPort: "Status Port"
2085
+ statusPort: "Status Port",
2086
+ returnPropertyOverride: "Override return prop key"
1907
2087
  }
1908
2088
  },
1909
2089
  de: {
1910
- configs: { name: "Name" },
2090
+ configs: { name: "Name", returnProperty: "R\xFCckgabe-Schl\xFCssel" },
1911
2091
  toggles: {
1912
2092
  validateInput: "Eingabe validieren",
1913
2093
  validateOutput: "Ausgabe validieren",
1914
2094
  errorPort: "Fehler-Port",
1915
2095
  completePort: "Abschluss-Port",
1916
- statusPort: "Status-Port"
2096
+ statusPort: "Status-Port",
2097
+ returnPropertyOverride: "R\xFCckgabe-Schl\xFCssel \xFCberschreiben"
1917
2098
  }
1918
2099
  },
1919
2100
  "es-ES": {
1920
- configs: { name: "Nombre" },
2101
+ configs: { name: "Nombre", returnProperty: "Clave de retorno" },
1921
2102
  toggles: {
1922
2103
  validateInput: "Validar entrada",
1923
2104
  validateOutput: "Validar salida",
1924
2105
  errorPort: "Puerto de error",
1925
2106
  completePort: "Puerto de completado",
1926
- statusPort: "Puerto de estado"
2107
+ statusPort: "Puerto de estado",
2108
+ returnPropertyOverride: "Sobrescribir clave de retorno"
1927
2109
  }
1928
2110
  },
1929
2111
  fr: {
1930
- configs: { name: "Nom" },
2112
+ configs: { name: "Nom", returnProperty: "Cl\xE9 de retour" },
1931
2113
  toggles: {
1932
2114
  validateInput: "Valider l'entr\xE9e",
1933
2115
  validateOutput: "Valider la sortie",
1934
2116
  errorPort: "Port d'erreur",
1935
2117
  completePort: "Port de compl\xE9tion",
1936
- statusPort: "Port de statut"
2118
+ statusPort: "Port de statut",
2119
+ returnPropertyOverride: "Remplacer la cl\xE9 de retour"
1937
2120
  }
1938
2121
  },
1939
2122
  ko: {
1940
- configs: { name: "\uC774\uB984" },
2123
+ configs: { name: "\uC774\uB984", returnProperty: "\uBC18\uD658 \uD0A4" },
1941
2124
  toggles: {
1942
2125
  validateInput: "\uC785\uB825 \uAC80\uC99D",
1943
2126
  validateOutput: "\uCD9C\uB825 \uAC80\uC99D",
1944
2127
  errorPort: "\uC624\uB958 \uD3EC\uD2B8",
1945
2128
  completePort: "\uC644\uB8CC \uD3EC\uD2B8",
1946
- statusPort: "\uC0C1\uD0DC \uD3EC\uD2B8"
2129
+ statusPort: "\uC0C1\uD0DC \uD3EC\uD2B8",
2130
+ returnPropertyOverride: "\uBC18\uD658 \uD0A4 \uC7AC\uC815\uC758"
1947
2131
  }
1948
2132
  },
1949
2133
  "pt-BR": {
1950
- configs: { name: "Nome" },
2134
+ configs: { name: "Nome", returnProperty: "Chave de retorno" },
1951
2135
  toggles: {
1952
2136
  validateInput: "Validar Entrada",
1953
2137
  validateOutput: "Validar Sa\xEDda",
1954
2138
  errorPort: "Porta de Erro",
1955
2139
  completePort: "Porta de Conclus\xE3o",
1956
- statusPort: "Porta de Status"
2140
+ statusPort: "Porta de Status",
2141
+ returnPropertyOverride: "Sobrescrever chave de retorno"
1957
2142
  }
1958
2143
  },
1959
2144
  ru: {
1960
- configs: { name: "\u0418\u043C\u044F" },
2145
+ configs: { name: "\u0418\u043C\u044F", returnProperty: "\u041A\u043B\u044E\u0447 \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0430" },
1961
2146
  toggles: {
1962
2147
  validateInput: "\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0432\u0445\u043E\u0434",
1963
2148
  validateOutput: "\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0432\u044B\u0445\u043E\u0434",
1964
2149
  errorPort: "\u041F\u043E\u0440\u0442 \u043E\u0448\u0438\u0431\u043A\u0438",
1965
2150
  completePort: "\u041F\u043E\u0440\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F",
1966
- statusPort: "\u041F\u043E\u0440\u0442 \u0441\u0442\u0430\u0442\u0443\u0441\u0430"
2151
+ statusPort: "\u041F\u043E\u0440\u0442 \u0441\u0442\u0430\u0442\u0443\u0441\u0430",
2152
+ returnPropertyOverride: "\u041F\u0435\u0440\u0435\u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C \u043A\u043B\u044E\u0447 \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0430"
1967
2153
  }
1968
2154
  },
1969
2155
  ja: {
1970
- configs: { name: "\u540D\u524D" },
2156
+ configs: { name: "\u540D\u524D", returnProperty: "\u623B\u308A\u30AD\u30FC" },
1971
2157
  toggles: {
1972
2158
  validateInput: "\u5165\u529B\u691C\u8A3C",
1973
2159
  validateOutput: "\u51FA\u529B\u691C\u8A3C",
1974
2160
  errorPort: "\u30A8\u30E9\u30FC\u30DD\u30FC\u30C8",
1975
2161
  completePort: "\u5B8C\u4E86\u30DD\u30FC\u30C8",
1976
- statusPort: "\u30B9\u30C6\u30FC\u30BF\u30B9\u30DD\u30FC\u30C8"
2162
+ statusPort: "\u30B9\u30C6\u30FC\u30BF\u30B9\u30DD\u30FC\u30C8",
2163
+ returnPropertyOverride: "\u623B\u308A\u30AD\u30FC\u3092\u4E0A\u66F8\u304D"
1977
2164
  }
1978
2165
  },
1979
2166
  "zh-CN": {
1980
- configs: { name: "\u540D\u79F0" },
2167
+ configs: { name: "\u540D\u79F0", returnProperty: "\u8FD4\u56DE\u952E" },
1981
2168
  toggles: {
1982
2169
  validateInput: "\u9A8C\u8BC1\u8F93\u5165",
1983
2170
  validateOutput: "\u9A8C\u8BC1\u8F93\u51FA",
1984
2171
  errorPort: "\u9519\u8BEF\u7AEF\u53E3",
1985
2172
  completePort: "\u5B8C\u6210\u7AEF\u53E3",
1986
- statusPort: "\u72B6\u6001\u7AEF\u53E3"
2173
+ statusPort: "\u72B6\u6001\u7AEF\u53E3",
2174
+ returnPropertyOverride: "\u8986\u76D6\u8FD4\u56DE\u952E"
1987
2175
  }
1988
2176
  },
1989
2177
  "zh-TW": {
1990
- configs: { name: "\u540D\u7A31" },
2178
+ configs: { name: "\u540D\u7A31", returnProperty: "\u8FD4\u56DE\u9375" },
1991
2179
  toggles: {
1992
2180
  validateInput: "\u9A57\u8B49\u8F38\u5165",
1993
2181
  validateOutput: "\u9A57\u8B49\u8F38\u51FA",
1994
2182
  errorPort: "\u932F\u8AA4\u7AEF\u53E3",
1995
2183
  completePort: "\u5B8C\u6210\u7AEF\u53E3",
1996
- statusPort: "\u72C0\u614B\u7AEF\u53E3"
2184
+ statusPort: "\u72C0\u614B\u7AEF\u53E3",
2185
+ returnPropertyOverride: "\u8986\u84CB\u8FD4\u56DE\u9375"
1997
2186
  }
1998
2187
  }
1999
2188
  };
@@ -2012,17 +2201,17 @@ Supported: ${languages.join(", ")}`
2012
2201
  }
2013
2202
  function forEachFile(baseDir, fileExtensions, processFile) {
2014
2203
  const langMap = /* @__PURE__ */ new Map();
2015
- if (!fs8.existsSync(baseDir)) return langMap;
2016
- const nodeDirs = fs8.readdirSync(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory());
2204
+ if (!fs10.existsSync(baseDir)) return langMap;
2205
+ const nodeDirs = fs10.readdirSync(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory());
2017
2206
  for (const nodeDir of nodeDirs) {
2018
2207
  const nodeType = nodeDir.name;
2019
- const nodePath = path8.join(baseDir, nodeType);
2020
- const files = fs8.readdirSync(nodePath);
2208
+ const nodePath = path9.join(baseDir, nodeType);
2209
+ const files = fs10.readdirSync(nodePath);
2021
2210
  for (const file of files) {
2022
- const ext = path8.extname(file);
2211
+ const ext = path9.extname(file);
2023
2212
  if (!fileExtensions.includes(ext)) continue;
2024
- const lang = path8.basename(file, ext);
2025
- const filePath = path8.join(nodePath, file);
2213
+ const lang = path9.basename(file, ext);
2214
+ const filePath = path9.join(nodePath, file);
2026
2215
  validateLanguage(lang, filePath);
2027
2216
  const value = processFile({ ext, filePath, nodeType });
2028
2217
  if (value == null) continue;
@@ -2040,10 +2229,10 @@ Supported: ${languages.join(", ")}`
2040
2229
  }
2041
2230
  function writeOutput(langMap, fileName, serialize) {
2042
2231
  for (const [lang, data] of langMap.entries()) {
2043
- const langOutDir = path8.join(outDir, lang);
2044
- fs8.mkdirSync(langOutDir, { recursive: true });
2045
- fs8.writeFileSync(
2046
- path8.join(langOutDir, fileName),
2232
+ const langOutDir = path9.join(outDir, lang);
2233
+ fs10.mkdirSync(langOutDir, { recursive: true });
2234
+ fs10.writeFileSync(
2235
+ path9.join(langOutDir, fileName),
2047
2236
  serialize(data),
2048
2237
  "utf-8"
2049
2238
  );
@@ -2055,7 +2244,7 @@ Supported: ${languages.join(", ")}`
2055
2244
  ({ ext, filePath, nodeType }) => {
2056
2245
  const type = ext === ".html" ? "text/html" : ext === ".md" ? "text/markdown" : null;
2057
2246
  if (!type) return null;
2058
- const content = fs8.readFileSync(filePath, "utf-8");
2247
+ const content = fs10.readFileSync(filePath, "utf-8");
2059
2248
  return [
2060
2249
  `<script type="${type}" data-help-name="${nodeType}">
2061
2250
  ${content}
@@ -2072,7 +2261,7 @@ ${content}
2072
2261
  labelsDir,
2073
2262
  [".json"],
2074
2263
  ({ filePath, nodeType }) => {
2075
- const parsed = JSON.parse(fs8.readFileSync(filePath, "utf-8"));
2264
+ const parsed = JSON.parse(fs10.readFileSync(filePath, "utf-8"));
2076
2265
  if (parsed[nodeType] && typeof parsed[nodeType] === "object") {
2077
2266
  console.warn(
2078
2267
  `[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`
@@ -2122,8 +2311,8 @@ function minifier() {
2122
2311
  // src/vite/client/plugins/node-definitions-inliner.ts
2123
2312
  import { createRequire as createRequire3 } from "module";
2124
2313
  import { pathToFileURL as pathToFileURL3 } from "url";
2125
- import path9 from "path";
2126
- import fs9 from "fs";
2314
+ import path10 from "path";
2315
+ import fs11 from "fs";
2127
2316
  import mime2 from "mime-types";
2128
2317
  var VIRTUAL_ID = "virtual:nrg/node-definitions";
2129
2318
  var RESOLVED_ID = "\0" + VIRTUAL_ID;
@@ -2154,9 +2343,9 @@ function getCredentialsFromSchema(schema) {
2154
2343
  return result;
2155
2344
  }
2156
2345
  function resolveIcon(iconsDir, type) {
2157
- if (!fs9.existsSync(iconsDir)) return void 0;
2158
- return fs9.readdirSync(iconsDir).find((f) => {
2159
- if (path9.basename(f, path9.extname(f)) !== type) return false;
2346
+ if (!fs11.existsSync(iconsDir)) return void 0;
2347
+ return fs11.readdirSync(iconsDir).find((f) => {
2348
+ if (path10.basename(f, path10.extname(f)) !== type) return false;
2160
2349
  const mimeType = mime2.lookup(f);
2161
2350
  return mimeType !== false && mimeType.startsWith("image/");
2162
2351
  });
@@ -2164,7 +2353,7 @@ function resolveIcon(iconsDir, type) {
2164
2353
  function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir, nodesDir, hasUserEntry = true) {
2165
2354
  let _nodeTypes = [];
2166
2355
  let _definitions = {};
2167
- const cacheDir = path9.resolve("node_modules", ".nrg", "client");
2356
+ const cacheDir = path10.resolve("node_modules", ".nrg", "client");
2168
2357
  return {
2169
2358
  name: "vite-plugin-node-red:client:node-definitions-inliner",
2170
2359
  enforce: "pre",
@@ -2173,14 +2362,14 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2173
2362
  async buildStart() {
2174
2363
  _nodeTypes = [];
2175
2364
  _definitions = {};
2176
- const esmEntryPath = path9.resolve(serverOutDir, "index.mjs");
2177
- const cjsEntryPath = path9.resolve(serverOutDir, "index.js");
2365
+ const esmEntryPath = path10.resolve(serverOutDir, "index.mjs");
2366
+ const cjsEntryPath = path10.resolve(serverOutDir, "index.js");
2178
2367
  let packageFn;
2179
- if (fs9.existsSync(esmEntryPath)) {
2368
+ if (fs11.existsSync(esmEntryPath)) {
2180
2369
  const fileUrl = pathToFileURL3(esmEntryPath).href + `?t=${Date.now()}`;
2181
2370
  const mod = await import(fileUrl);
2182
2371
  packageFn = mod?.default ?? mod;
2183
- } else if (fs9.existsSync(cjsEntryPath)) {
2372
+ } else if (fs11.existsSync(cjsEntryPath)) {
2184
2373
  const require2 = createRequire3(import.meta.url);
2185
2374
  delete require2.cache[cjsEntryPath];
2186
2375
  const rawMod = require2(cjsEntryPath);
@@ -2221,14 +2410,14 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2221
2410
  };
2222
2411
  }
2223
2412
  if (!hasUserEntry) {
2224
- const nodesCache = path9.resolve(cacheDir, "nodes");
2225
- if (fs9.existsSync(nodesCache)) {
2226
- fs9.rmSync(nodesCache, { recursive: true });
2413
+ const nodesCache = path10.resolve(cacheDir, "nodes");
2414
+ if (fs11.existsSync(nodesCache)) {
2415
+ fs11.rmSync(nodesCache, { recursive: true });
2227
2416
  }
2228
- fs9.mkdirSync(nodesCache, { recursive: true });
2417
+ fs11.mkdirSync(nodesCache, { recursive: true });
2229
2418
  for (const type of _nodeTypes) {
2230
- const userTsPath = nodesDir ? path9.resolve(nodesDir, `${type}.ts`) : null;
2231
- if (userTsPath && fs9.existsSync(userTsPath)) continue;
2419
+ const userTsPath = nodesDir ? path10.resolve(nodesDir, `${type}.ts`) : null;
2420
+ if (userTsPath && fs11.existsSync(userTsPath)) continue;
2232
2421
  const content = [
2233
2422
  `// auto-generated by nrg`,
2234
2423
  `import { defineNode } from "@bonsae/nrg/client";`,
@@ -2238,13 +2427,13 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2238
2427
  `});`,
2239
2428
  ``
2240
2429
  ].join("\n");
2241
- fs9.writeFileSync(path9.resolve(nodesCache, `${type}.ts`), content);
2430
+ fs11.writeFileSync(path10.resolve(nodesCache, `${type}.ts`), content);
2242
2431
  }
2243
2432
  const entryContent = generateEntryCode("");
2244
- fs9.mkdirSync(path9.dirname(path9.resolve(cacheDir, "index.ts")), {
2433
+ fs11.mkdirSync(path10.dirname(path10.resolve(cacheDir, "index.ts")), {
2245
2434
  recursive: true
2246
2435
  });
2247
- fs9.writeFileSync(path9.resolve(cacheDir, "index.ts"), entryContent);
2436
+ fs11.writeFileSync(path10.resolve(cacheDir, "index.ts"), entryContent);
2248
2437
  }
2249
2438
  },
2250
2439
  resolveId(id) {
@@ -2267,12 +2456,12 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2267
2456
  const nrgImports = /* @__PURE__ */ new Set(["__setSchemas"]);
2268
2457
  const lines = [`import __nrgSchemas from "${VIRTUAL_ID}";`];
2269
2458
  const postLines = [`__setSchemas(__nrgSchemas);`];
2270
- if (componentsDir && fs9.existsSync(componentsDir)) {
2459
+ if (componentsDir && fs11.existsSync(componentsDir)) {
2271
2460
  const formImports = [];
2272
2461
  const formEntries = [];
2273
2462
  for (const type of _nodeTypes) {
2274
- const componentPath = path9.resolve(componentsDir, `${type}.vue`);
2275
- if (fs9.existsSync(componentPath)) {
2463
+ const componentPath = path10.resolve(componentsDir, `${type}.vue`);
2464
+ if (fs11.existsSync(componentPath)) {
2276
2465
  const varName = `__nrgForm_${type.replace(/-/g, "_")}`;
2277
2466
  formImports.push(
2278
2467
  `import ${varName} from ${JSON.stringify(componentPath)};`
@@ -2287,12 +2476,12 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2287
2476
  }
2288
2477
  }
2289
2478
  if (!hasUserEntry) {
2290
- const nodesCache = path9.resolve(cacheDir, "nodes");
2479
+ const nodesCache = path10.resolve(cacheDir, "nodes");
2291
2480
  const defVarNames = [];
2292
2481
  for (const type of _nodeTypes) {
2293
2482
  const varName = `__nrgNodeDef_${type.replace(/-/g, "_")}`;
2294
- const userTsPath = nodesDir ? path9.resolve(nodesDir, `${type}.ts`) : null;
2295
- const tsPath = userTsPath && fs9.existsSync(userTsPath) ? userTsPath : path9.resolve(nodesCache, `${type}.ts`);
2483
+ const userTsPath = nodesDir ? path10.resolve(nodesDir, `${type}.ts`) : null;
2484
+ const tsPath = userTsPath && fs11.existsSync(userTsPath) ? userTsPath : path10.resolve(nodesCache, `${type}.ts`);
2296
2485
  lines.push(`import ${varName} from ${JSON.stringify(tsPath)};`);
2297
2486
  defVarNames.push(varName);
2298
2487
  }
@@ -2310,8 +2499,8 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2310
2499
  }
2311
2500
 
2312
2501
  // src/vite/client/plugins/static-copy.ts
2313
- import fs10 from "fs";
2314
- import path10 from "path";
2502
+ import fs12 from "fs";
2503
+ import path11 from "path";
2315
2504
  function staticCopy(options) {
2316
2505
  const { targets } = options;
2317
2506
  return {
@@ -2320,23 +2509,23 @@ function staticCopy(options) {
2320
2509
  enforce: "post",
2321
2510
  closeBundle() {
2322
2511
  for (const { src, dest } of targets) {
2323
- if (!fs10.existsSync(src)) continue;
2324
- fs10.mkdirSync(dest, { recursive: true });
2325
- const stat = fs10.statSync(src);
2512
+ if (!fs12.existsSync(src)) continue;
2513
+ fs12.mkdirSync(dest, { recursive: true });
2514
+ const stat = fs12.statSync(src);
2326
2515
  if (stat.isDirectory()) {
2327
- const files = fs10.readdirSync(src);
2516
+ const files = fs12.readdirSync(src);
2328
2517
  for (const file of files) {
2329
- const srcFile = path10.join(src, file);
2330
- const destFile = path10.join(dest, file);
2331
- const fileStat = fs10.statSync(srcFile);
2518
+ const srcFile = path11.join(src, file);
2519
+ const destFile = path11.join(dest, file);
2520
+ const fileStat = fs12.statSync(srcFile);
2332
2521
  if (fileStat.isDirectory()) {
2333
- fs10.cpSync(srcFile, destFile, { recursive: true });
2522
+ fs12.cpSync(srcFile, destFile, { recursive: true });
2334
2523
  } else {
2335
- fs10.copyFileSync(srcFile, destFile);
2524
+ fs12.copyFileSync(srcFile, destFile);
2336
2525
  }
2337
2526
  }
2338
2527
  } else {
2339
- fs10.copyFileSync(src, dest);
2528
+ fs12.copyFileSync(src, dest);
2340
2529
  }
2341
2530
  }
2342
2531
  }
@@ -2357,74 +2546,74 @@ async function build2(clientBuildOptions, buildContext) {
2357
2546
  globals = {},
2358
2547
  manualChunks
2359
2548
  } = clientBuildOptions;
2360
- const physicalEntryPath = path11.resolve(srcDir, entry);
2549
+ const physicalEntryPath = path12.resolve(srcDir, entry);
2361
2550
  let entryPath;
2362
2551
  let generatedEntry = false;
2363
- if (fs11.existsSync(physicalEntryPath)) {
2552
+ if (fs13.existsSync(physicalEntryPath)) {
2364
2553
  entryPath = physicalEntryPath;
2365
2554
  } else {
2366
- const cacheDir = path11.resolve("node_modules", ".nrg", "client");
2367
- const cachedEntryPath = path11.resolve(cacheDir, entry);
2368
- if (!fs11.existsSync(cacheDir)) {
2369
- fs11.mkdirSync(cacheDir, { recursive: true });
2555
+ const cacheDir = path12.resolve("node_modules", ".nrg", "client");
2556
+ const cachedEntryPath = path12.resolve(cacheDir, entry);
2557
+ if (!fs13.existsSync(cacheDir)) {
2558
+ fs13.mkdirSync(cacheDir, { recursive: true });
2370
2559
  }
2371
- fs11.writeFileSync(cachedEntryPath, "// auto-generated entry\n");
2560
+ fs13.writeFileSync(cachedEntryPath, "// auto-generated entry\n");
2372
2561
  entryPath = cachedEntryPath;
2373
2562
  generatedEntry = true;
2374
2563
  }
2375
- const iconsDir = path11.resolve(
2376
- staticDirs.icons ?? path11.join(path11.dirname(path11.resolve(srcDir)), "icons")
2564
+ const iconsDir = path12.resolve(
2565
+ staticDirs.icons ?? path12.join(path12.dirname(path12.resolve(srcDir)), "icons")
2377
2566
  );
2378
2567
  const plugins = [
2379
2568
  vue(),
2380
2569
  nodeDefinitionsInliner(
2381
2570
  buildContext.outDir,
2382
2571
  entryPath,
2383
- fs11.existsSync(iconsDir) ? iconsDir : void 0,
2384
- path11.resolve(srcDir, "components"),
2385
- path11.resolve(srcDir, "nodes"),
2572
+ fs13.existsSync(iconsDir) ? iconsDir : void 0,
2573
+ path12.resolve(srcDir, "components"),
2574
+ path12.resolve(srcDir, "nodes"),
2386
2575
  !generatedEntry
2387
2576
  )
2388
2577
  ];
2389
2578
  plugins.push(
2390
2579
  htmlGenerator({
2391
2580
  packageName: buildContext.packageName,
2392
- licensePath: licensePath ? path11.resolve(licensePath) : void 0
2581
+ licensePath: licensePath ? path12.resolve(licensePath) : void 0
2393
2582
  })
2394
2583
  );
2395
2584
  if (locales) {
2396
2585
  const { docsDir = "./locales/docs", labelsDir = "./locales/labels" } = locales;
2397
- const localesOutDir = path11.join(buildContext.outDir, "locales");
2586
+ const localesOutDir = path12.join(buildContext.outDir, "locales");
2398
2587
  plugins.push(
2399
2588
  localesGenerator({
2400
2589
  outDir: localesOutDir,
2401
- docsDir: path11.resolve(docsDir),
2402
- labelsDir: path11.resolve(labelsDir)
2590
+ docsDir: path12.resolve(docsDir),
2591
+ labelsDir: path12.resolve(labelsDir)
2403
2592
  })
2404
2593
  );
2405
2594
  plugins.push(
2406
2595
  helpGenerator({
2407
2596
  outDir: buildContext.outDir,
2408
2597
  localesOutDir,
2409
- docsDir: path11.resolve(docsDir),
2410
- labelsDir: path11.resolve(labelsDir)
2598
+ docsDir: path12.resolve(docsDir),
2599
+ labelsDir: path12.resolve(labelsDir)
2411
2600
  })
2412
2601
  );
2413
2602
  }
2414
2603
  const copyTargets = [];
2415
- const publicDir = path11.resolve(
2416
- staticDirs.public ?? path11.join(srcDir, "public")
2604
+ const publicDir = path12.resolve(
2605
+ staticDirs.public ?? path12.join(srcDir, "public")
2417
2606
  );
2418
- if (fs11.existsSync(publicDir)) {
2607
+ if (fs13.existsSync(publicDir)) {
2419
2608
  copyTargets.push({
2420
2609
  src: publicDir,
2421
- dest: path11.join(buildContext.outDir, "resources")
2610
+ dest: path12.join(buildContext.outDir, "resources")
2422
2611
  });
2423
2612
  }
2424
- if (fs11.existsSync(iconsDir)) {
2613
+ if (fs13.existsSync(iconsDir)) {
2425
2614
  copyTargets.push({
2426
2615
  src: iconsDir,
2427
- dest: path11.join(buildContext.outDir, "icons")
2616
+ dest: path12.join(buildContext.outDir, "icons")
2428
2617
  });
2429
2618
  }
2430
2619
  if (copyTargets.length > 0) {
@@ -2452,10 +2641,10 @@ async function build2(clientBuildOptions, buildContext) {
2452
2641
  configFile: false,
2453
2642
  logLevel: "warn",
2454
2643
  base: `/resources/${buildContext.packageName}`,
2455
- publicDir: path11.resolve(srcDir, "public"),
2644
+ publicDir: path12.resolve(srcDir, "public"),
2456
2645
  resolve: {
2457
2646
  alias: {
2458
- "@": path11.resolve(srcDir)
2647
+ "@": path12.resolve(srcDir)
2459
2648
  }
2460
2649
  },
2461
2650
  plugins,
@@ -2509,8 +2698,8 @@ async function build2(clientBuildOptions, buildContext) {
2509
2698
  throw new BuildError("client", error);
2510
2699
  } finally {
2511
2700
  if (generatedEntry) {
2512
- if (fs11.existsSync(entryPath)) {
2513
- fs11.unlinkSync(entryPath);
2701
+ if (fs13.existsSync(entryPath)) {
2702
+ fs13.unlinkSync(entryPath);
2514
2703
  }
2515
2704
  }
2516
2705
  }
@@ -2531,6 +2720,8 @@ function serverPlugin(options) {
2531
2720
  extraFilesCopyTargets,
2532
2721
  buildContext
2533
2722
  } = options;
2723
+ const { slug, basePath } = nodeRedLauncher;
2724
+ const proxyKey = slug ? `^/${slug}(?:/|\\?|$)` : "^/.*";
2534
2725
  let nodeRedPort;
2535
2726
  let initialStartDone = false;
2536
2727
  let isStarting = false;
@@ -2555,7 +2746,7 @@ function serverPlugin(options) {
2555
2746
  logger.stopSpinner("Copied extra files");
2556
2747
  }
2557
2748
  };
2558
- const start = async (clean = false) => {
2749
+ const start2 = async (clean = false) => {
2559
2750
  if (isStarting) {
2560
2751
  pendingStart = true;
2561
2752
  return;
@@ -2570,7 +2761,7 @@ function serverPlugin(options) {
2570
2761
  logger.stopSpinner("Node-RED started");
2571
2762
  const proxyConfig = server.config.server.proxy;
2572
2763
  if (proxyConfig && typeof proxyConfig === "object") {
2573
- const rule = proxyConfig["^/.*"];
2764
+ const rule = proxyConfig[proxyKey];
2574
2765
  if (rule && typeof rule === "object") {
2575
2766
  rule.target = `http://127.0.0.1:${nodeRedPort}`;
2576
2767
  }
@@ -2588,14 +2779,14 @@ function serverPlugin(options) {
2588
2779
  } finally {
2589
2780
  isStarting = false;
2590
2781
  if (pendingStart) {
2591
- start(false);
2782
+ start2(false);
2592
2783
  }
2593
2784
  }
2594
2785
  };
2595
2786
  const printServerUrls = (vitePort, nodeRedPorts) => {
2596
2787
  console.log();
2597
2788
  console.log(
2598
- ` ${color2.cyan("Vite")} ${color2.dim("\u279C")} ${color2.cyan(`http://127.0.0.1:${vitePort}/`)}`
2789
+ ` ${color2.cyan("Vite")} ${color2.dim("\u279C")} ${color2.cyan(`http://127.0.0.1:${vitePort}${basePath}`)}`
2599
2790
  );
2600
2791
  if (nodeRedPorts.actual != nodeRedPorts.preferred) {
2601
2792
  console.log(
@@ -2617,7 +2808,7 @@ function serverPlugin(options) {
2617
2808
  server: {
2618
2809
  host: "127.0.0.1",
2619
2810
  proxy: {
2620
- "^/.*": {
2811
+ [proxyKey]: {
2621
2812
  target: `http://127.0.0.1:${nodeRedLauncher.preferredPort}`,
2622
2813
  changeOrigin: true,
2623
2814
  ws: true,
@@ -2660,8 +2851,19 @@ function serverPlugin(options) {
2660
2851
  },
2661
2852
  async configureServer(viteServer) {
2662
2853
  server = viteServer;
2854
+ if (slug) {
2855
+ server.middlewares.use((req, res, next) => {
2856
+ if (req.url === "/" || req.url === "") {
2857
+ res.statusCode = 302;
2858
+ res.setHeader("Location", basePath);
2859
+ res.end();
2860
+ return;
2861
+ }
2862
+ next();
2863
+ });
2864
+ }
2663
2865
  logger.intro();
2664
- await start(true);
2866
+ await start2(true);
2665
2867
  initialStartDone = true;
2666
2868
  printServerUrls(server.config.server.port ?? 5173, {
2667
2869
  actual: nodeRedPort,
@@ -2669,20 +2871,20 @@ function serverPlugin(options) {
2669
2871
  });
2670
2872
  logger.startGroup("Node-RED");
2671
2873
  nodeRedLauncher.flushLogs();
2672
- const serverSrcDir = path12.resolve(
2874
+ const serverSrcDir = path13.resolve(
2673
2875
  serverBuildOptions.srcDir ?? "./server"
2674
2876
  );
2675
- const clientSrcDir = path12.resolve(
2877
+ const clientSrcDir = path13.resolve(
2676
2878
  clientBuildOptions.srcDir ?? "./client"
2677
2879
  );
2678
- const localesDocsDir = path12.resolve(
2880
+ const localesDocsDir = path13.resolve(
2679
2881
  clientBuildOptions.locales?.docsDir ?? "./locales/docs"
2680
2882
  );
2681
- const localesLabelsDir = path12.resolve(
2883
+ const localesLabelsDir = path13.resolve(
2682
2884
  clientBuildOptions.locales?.labelsDir ?? "./locales/labels"
2683
2885
  );
2684
- const iconsDir = path12.resolve(
2685
- clientBuildOptions.staticDirs?.icons ?? path12.join(path12.dirname(clientSrcDir), "icons")
2886
+ const iconsDir = path13.resolve(
2887
+ clientBuildOptions.staticDirs?.icons ?? path13.join(path13.dirname(clientSrcDir), "icons")
2686
2888
  );
2687
2889
  const watchPaths = [
2688
2890
  serverSrcDir,
@@ -2697,12 +2899,12 @@ function serverPlugin(options) {
2697
2899
  ignored: ["**/node_modules/**", "**/dist/**", "**/.node-red/**"]
2698
2900
  });
2699
2901
  const debounceBeforeStart = debounce(
2700
- () => start(false),
2902
+ () => start2(false),
2701
2903
  nodeRedLauncher.restartDelay ?? 1e3
2702
2904
  );
2703
2905
  const handleFileChange = (file, event) => {
2704
2906
  if (!initialStartDone) return;
2705
- logger.info(`${event}: ${path12.relative(process.cwd(), file)}`);
2907
+ logger.info(`${event}: ${path13.relative(process.cwd(), file)}`);
2706
2908
  debounceBeforeStart();
2707
2909
  };
2708
2910
  watcher.on("change", (file) => handleFileChange(file, "Changed"));
@@ -2780,30 +2982,33 @@ function buildPlugin(options) {
2780
2982
  }
2781
2983
 
2782
2984
  // src/vite/plugin.ts
2783
- function nodeRed(options = {}) {
2784
- const { outDir = DEFAULT_OUTPUT_DIR } = options;
2985
+ function nrg(options = {}) {
2986
+ const { build: build3 = {}, server = {} } = options;
2987
+ const { outDir = DEFAULT_OUTPUT_DIR } = build3;
2785
2988
  const clientBuildOptions = mergeOptions(
2786
2989
  DEFAULT_CLIENT_BUILD_OPTIONS,
2787
- options.clientBuildOptions
2990
+ build3.client
2788
2991
  );
2789
2992
  const serverBuildOptions = mergeOptions(
2790
2993
  DEFAULT_SERVER_BUILD_OPTIONS,
2791
- options.serverBuildOptions
2994
+ build3.server
2792
2995
  );
2793
2996
  const nodeRedLauncherOptions = mergeOptions(
2794
2997
  DEFAULT_NODE_RED_LAUNCHER_OPTIONS,
2795
- options.nodeRedLauncherOptions
2998
+ server.nodeRed
2796
2999
  );
2797
- const extraFilesCopyTargets = options.extraFilesCopyTargets ?? DEFAULT_EXTRA_FILES_COPY_TARGETS;
2798
- const resolvedOutDir = path13.resolve(outDir);
3000
+ const extraFilesCopyTargets = build3.extraFilesCopyTargets ?? DEFAULT_EXTRA_FILES_COPY_TARGETS;
3001
+ const resolvedOutDir = path14.resolve(outDir);
2799
3002
  const buildContext = {
2800
3003
  outDir: resolvedOutDir,
2801
3004
  packageName: getPackageName(),
2802
3005
  isDev: process.env.NODE_ENV === "development"
2803
3006
  };
3007
+ const slug = resolveSlug(server.slug);
2804
3008
  const nodeRedLauncher = new NodeRedLauncher(
2805
3009
  resolvedOutDir,
2806
- nodeRedLauncherOptions
3010
+ nodeRedLauncherOptions,
3011
+ slug
2807
3012
  );
2808
3013
  return [
2809
3014
  serverPlugin({
@@ -2822,5 +3027,5 @@ function nodeRed(options = {}) {
2822
3027
  ];
2823
3028
  }
2824
3029
  export {
2825
- nodeRed
3030
+ nrg
2826
3031
  };