@bonsae/nrg 0.18.5 → 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 (36) hide show
  1. package/package.json +1 -1
  2. package/server/index.cjs +86 -9
  3. package/server/resources/nrg-client.js +2020 -1987
  4. package/test/client/component/config.js +11 -0
  5. package/test/client/component/index.js +218 -235
  6. package/test/client/component/nrg.css +1 -0
  7. package/test/client/component/setup.js +1549 -140
  8. package/test/client/e2e/index.js +720 -369
  9. package/test/client/unit/index.js +204 -16
  10. package/test/client/unit/setup.js +209 -19
  11. package/test/server/unit/index.js +25 -4
  12. package/tsconfig/core/client.json +1 -1
  13. package/tsconfig/test/client/component.json +1 -1
  14. package/types/client.d.ts +98 -18
  15. package/types/server.d.ts +50 -12
  16. package/types/shims/brands.d.ts +32 -0
  17. package/types/shims/{form → client/form}/components/node-red-editor-input.vue.d.ts +1 -1
  18. package/types/shims/{form → client/form}/components/node-red-json-schema-form.vue.d.ts +21 -2
  19. package/types/shims/{form → client/form}/components/node-red-select-input.vue.d.ts +1 -0
  20. package/types/shims/{form → client/form}/components/node-red-typed-input.vue.d.ts +1 -0
  21. package/types/shims/client/types.d.ts +206 -0
  22. package/types/shims/components.d.ts +8 -8
  23. package/types/shims/constants.d.ts +4 -0
  24. package/types/shims/schema-options.d.ts +23 -10
  25. package/types/shims/typebox.d.ts +2 -2
  26. package/types/test-client-component.d.ts +170 -55
  27. package/types/test-client-e2e.d.ts +50 -0
  28. package/types/test-client-unit.d.ts +86 -22
  29. package/types/test-server-unit.d.ts +3 -1
  30. package/types/vite.d.ts +38 -9
  31. package/vite/index.js +732 -530
  32. /package/types/shims/{form → client/form}/components/node-red-config-input.vue.d.ts +0 -0
  33. /package/types/shims/{form → client/form}/components/node-red-input-label.vue.d.ts +0 -0
  34. /package/types/shims/{form → client/form}/components/node-red-input.vue.d.ts +0 -0
  35. /package/types/shims/{form → client/form}/components/node-red-toggle.vue.d.ts +0 -0
  36. /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,203 +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 hasExplicitVersion = this.options.runtime?.version !== void 0 && this.options.runtime.version !== "latest";
403
- if (!hasExplicitVersion) {
404
- const localEntry = this.resolveFromLocalNodeModules();
405
- if (localEntry) {
406
- this.logger.info(`Resolved from local node_modules: ${localEntry}`);
407
- 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();
522
+ }
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);
530
+ }
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);
408
538
  }
409
539
  }
410
- this.logger.info(
411
- hasExplicitVersion ? `Using configured version (${this.options.runtime.version}), downloading via npx...` : `Not found locally, downloading via npx (this may take a while)...`
412
- );
413
- const resolverScript = path2.join(
414
- os.tmpdir(),
415
- `nrg-resolve-node-red-${process.pid}.cjs`
416
- );
417
- fs2.writeFileSync(
418
- resolverScript,
419
- `const fs = require("fs");
420
- const path = require("path");
421
- const isWin = process.platform === "win32";
422
- const binName = isWin ? "node-red.cmd" : "node-red";
423
- const dirs = process.env.PATH.split(path.delimiter);
424
- for (const d of dirs) {
425
- const f = path.join(d, binName);
426
- if (fs.existsSync(f)) {
427
- if (isWin) {
428
- const nodeRedDir = path.resolve(d, "..", "node-red");
429
- const pkg = JSON.parse(fs.readFileSync(path.join(nodeRedDir, "package.json"), "utf-8"));
430
- const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin["node-red"];
431
- process.stdout.write(path.resolve(nodeRedDir, bin));
432
- } else {
433
- process.stdout.write(fs.realpathSync(f));
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;
434
553
  }
435
- break;
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;
436
567
  }
437
- }`
438
- );
439
- try {
440
- const entryPoint = execSync(
441
- `npx --yes -p ${this.nodeRedCommand} -c "node ${resolverScript}"`,
442
- { encoding: "utf-8", timeout: 3e5 }
443
- ).trim();
444
- if (!entryPoint || !fs2.existsSync(entryPoint)) {
445
- throw new NodeRedStartError(
446
- new Error(
447
- `Could not resolve node-red entry point: ${entryPoint || "(empty)"}`
448
- )
449
- );
450
- }
451
- this.logger.info(`Resolved via npx: ${entryPoint}`);
452
- return entryPoint;
453
- } finally {
454
- try {
455
- fs2.unlinkSync(resolverScript);
456
- } catch {
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
+ }
457
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");
458
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);
459
660
  }
460
661
  log(line) {
461
662
  if (line.includes("Server now running at")) {
@@ -463,150 +664,122 @@ for (const d of dirs) {
463
664
  }
464
665
  this.logger.raw(line);
465
666
  }
466
- async start() {
467
- const available = await detect(this.preferredPort);
468
- if (available === this.preferredPort) {
469
- 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);
470
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;
471
689
  this.logger.warn(
472
- `Port ${this.preferredPort} is still in use, waiting...`
690
+ `Node-RED exited unexpectedly (${signal ?? `code ${code}`})`
473
691
  );
474
- await new Promise((resolve) => setTimeout(resolve, 2e3));
475
- const retryAvailable = await detect(this.preferredPort);
476
- if (retryAvailable === this.preferredPort) {
477
- this.port = this.preferredPort;
478
- } else {
479
- this.logger.warn(
480
- `Port ${this.preferredPort} still occupied, using port ${retryAvailable}`
481
- );
482
- this.port = await getPort({ port: this.preferredPort });
483
- }
484
- }
485
- const nodeRedEntryPoint = this.resolveNodeRedEntryPoint();
486
- const startProcess = () => {
487
- return new Promise(async (resolve, reject) => {
488
- try {
489
- const settingsPath = await this.generateRuntimeSettingsFile();
490
- const args = this.options.args ?? [];
491
- this.bufferedLogs = [];
492
- this.isReady = false;
493
- this.process = spawn(
494
- process.execPath,
495
- [nodeRedEntryPoint, "-s", settingsPath, ...args],
496
- {
497
- stdio: ["ignore", "pipe", "pipe"]
498
- }
499
- );
500
- this.process.stdout?.on("data", (data) => {
501
- const lines = data.toString().split("\n").filter(Boolean);
502
- for (const line of lines) {
503
- if (this.isReady) {
504
- this.log(line);
505
- } else {
506
- this.bufferedLogs.push(line);
507
- }
508
- if (line.includes("Started flows") || line.includes("Server now running")) {
509
- this.isReady = true;
510
- resolve(void 0);
511
- }
512
- }
513
- });
514
- this.process.stderr?.on("data", (data) => {
515
- const lines = data.toString().split("\n").filter(Boolean);
516
- for (const line of lines) {
517
- if (this.isReady) {
518
- this.logger.error(`${line}`);
519
- } else {
520
- this.bufferedLogs.push(line);
521
- }
522
- }
523
- });
524
- this.process.on("error", (error) => {
525
- reject(new NodeRedStartError(error));
526
- });
527
- this.process.on("exit", (code) => {
528
- if (!this.isReady && code !== 0 && code !== null) {
529
- reject(
530
- new NodeRedStartError(
531
- new Error(`Process exited with code ${code}`)
532
- )
533
- );
534
- }
535
- resolve(void 0);
536
- });
537
- } catch (error) {
538
- reject(new NodeRedStartError(error));
539
- }
540
- });
541
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() {
542
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
+ };
543
751
  await retry(startProcess, { attempts: 3, delay: 100 });
752
+ this.watchForUnexpectedExit(this.process);
544
753
  return this.port;
545
754
  } catch (error) {
546
- if (this.process) {
547
- const pid = this.process.pid;
548
- if (pid) {
549
- treeKill(pid, "SIGKILL");
550
- }
551
- this.process = null;
552
- }
553
- throw new NodeRedStartError(error);
755
+ await this.killProcess();
756
+ throw error instanceof NodeRedStartError ? error : new NodeRedStartError(error);
554
757
  }
555
758
  }
556
- async stop(skipPortUsageCheck = false) {
759
+ async doStop(skipPortUsageCheck) {
557
760
  if (!this.process) return;
558
- const pid = this.process.pid;
761
+ this.stopWatchingExit();
762
+ const pid = this.process.child.pid;
559
763
  const currentPort = this.port;
560
764
  if (!pid) {
561
765
  this.process = null;
766
+ this.port = null;
562
767
  return;
563
768
  }
564
- const stopProcess = new Promise((resolve) => {
565
- this.process.once("exit", () => {
566
- this.process = null;
567
- resolve(void 0);
568
- });
569
- treeKill(pid, "SIGTERM", (error) => {
570
- if (error) {
571
- try {
572
- process.kill(pid, "SIGTERM");
573
- } catch {
574
- this.process = null;
575
- resolve(void 0);
576
- }
577
- }
578
- });
769
+ await stop({
770
+ child: this.process.child,
771
+ pid,
772
+ logger: this.logger
579
773
  });
580
- try {
581
- await withTimeout(stopProcess, 1e4);
582
- } catch {
583
- this.logger.warn("Graceful shutdown timed out, force killing...");
584
- await new Promise((resolve) => {
585
- treeKill(pid, "SIGKILL", () => {
586
- this.process = null;
587
- resolve(void 0);
588
- });
589
- });
590
- }
774
+ this.process = null;
591
775
  if (!skipPortUsageCheck && currentPort) {
592
- const checkPortUsage = async () => {
593
- const availablePort = await detect(currentPort);
594
- if (availablePort !== currentPort) {
595
- throw new Error("Port still in use");
596
- }
597
- };
598
- try {
599
- await retry(checkPortUsage, { attempts: 10, delay: 300 });
600
- } catch {
776
+ const released = await waitForPortRelease(currentPort);
777
+ if (!released) {
601
778
  this.logger.warn(
602
779
  `Port ${currentPort} still in use after stop. Force killing...`
603
780
  );
604
- if (pid) {
605
- await new Promise((resolve) => {
606
- treeKill(pid, "SIGKILL", () => resolve());
607
- });
608
- await new Promise((resolve) => setTimeout(resolve, 1e3));
609
- }
781
+ await kill(pid);
782
+ await waitForPortRelease(currentPort);
610
783
  }
611
784
  }
612
785
  this.port = null;
@@ -618,32 +791,35 @@ for (const d of dirs) {
618
791
  this.bufferedLogs = [];
619
792
  }
620
793
  cleanup() {
621
- if (this.compiledRuntimeSettingsFilepath && fs2.existsSync(this.compiledRuntimeSettingsFilepath)) {
622
- fs2.unlinkSync(this.compiledRuntimeSettingsFilepath);
623
- this.compiledRuntimeSettingsFilepath = null;
794
+ for (const file of this.tempFiles) {
795
+ try {
796
+ fs4.unlinkSync(file);
797
+ } catch {
798
+ }
624
799
  }
800
+ this.tempFiles = [];
625
801
  }
626
802
  };
627
803
 
628
804
  // src/vite/plugins/server.ts
629
805
  import chokidar from "chokidar";
630
806
  import treeKill2 from "tree-kill";
631
- import path12 from "path";
807
+ import path13 from "path";
632
808
 
633
809
  // src/vite/server/build.ts
634
810
  import { build as viteBuild } from "vite";
635
- import fs5 from "fs";
636
- import path5 from "path";
811
+ import fs7 from "fs";
812
+ import path6 from "path";
637
813
 
638
814
  // src/vite/server/plugins/type-generator.ts
639
815
  import dts from "vite-plugin-dts";
640
- import fs3 from "fs";
641
- import path3 from "path";
816
+ import fs5 from "fs";
817
+ import path4 from "path";
642
818
  import ts from "typescript";
643
819
  function collectTsFiles(dir) {
644
- if (!fs3.existsSync(dir)) return [];
645
- return fs3.readdirSync(dir, { withFileTypes: true }).flatMap((dirent) => {
646
- 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);
647
823
  if (dirent.isDirectory()) return collectTsFiles(full);
648
824
  if (dirent.isFile() && dirent.name.endsWith(".ts") && !dirent.name.endsWith(".d.ts"))
649
825
  return [full];
@@ -658,7 +834,7 @@ var BASE_CLASS_SLOTS = {
658
834
  ConfigNode: ["Config", "Credentials", "Settings"]
659
835
  };
660
836
  function getNodeTypeExports(filePath) {
661
- const content = fs3.readFileSync(filePath, "utf-8");
837
+ const content = fs5.readFileSync(filePath, "utf-8");
662
838
  const source = ts.createSourceFile(
663
839
  filePath,
664
840
  content,
@@ -720,7 +896,7 @@ var SCHEMA_PROP_SEMANTICS = {
720
896
  settingsSchema: "SettingsSchema"
721
897
  };
722
898
  function getSchemaReferences(filePath) {
723
- const content = fs3.readFileSync(filePath, "utf-8");
899
+ const content = fs5.readFileSync(filePath, "utf-8");
724
900
  const source = ts.createSourceFile(
725
901
  filePath,
726
902
  content,
@@ -812,7 +988,7 @@ function getSchemaReferences(filePath) {
812
988
  return result;
813
989
  }
814
990
  function getFactoryInfo(filePath) {
815
- const content = fs3.readFileSync(filePath, "utf-8");
991
+ const content = fs5.readFileSync(filePath, "utf-8");
816
992
  const source = ts.createSourceFile(
817
993
  filePath,
818
994
  content,
@@ -846,13 +1022,13 @@ function buildTypeArg(schemaMap, semanticName, portNameMap) {
846
1022
  return `[${names.map((n) => `Infer<typeof ${n}>`).join(", ")}]`;
847
1023
  }
848
1024
  function buildNodeReexports(srcDir, entryFile) {
849
- const nodesDir = path3.join(srcDir, "nodes");
1025
+ const nodesDir = path4.join(srcDir, "nodes");
850
1026
  const nodeFiles = collectTsFiles(nodesDir);
851
1027
  return nodeFiles.map((file) => {
852
- const rel = path3.relative(path3.dirname(entryFile), file).replace(/\\/g, "/");
1028
+ const rel = path4.relative(path4.dirname(entryFile), file).replace(/\\/g, "/");
853
1029
  const relPath = rel.startsWith(".") ? rel : `./${rel}`;
854
1030
  const specifier = relPath.replace(/\.ts$/, "");
855
- const ns = toPascalCase(path3.basename(file, ".ts"));
1031
+ const ns = toPascalCase(path4.basename(file, ".ts"));
856
1032
  const schemaRefs = getSchemaReferences(file);
857
1033
  const factoryInfo = getFactoryInfo(file);
858
1034
  const lines = [];
@@ -879,9 +1055,9 @@ function buildNodeReexports(srcDir, entryFile) {
879
1055
  if (hasSchemas) {
880
1056
  const bySource2 = /* @__PURE__ */ new Map();
881
1057
  for (const ref of schemaRefs) {
882
- const resolvedSource = path3.relative(
883
- path3.dirname(entryFile),
884
- path3.resolve(path3.dirname(file), ref.importSource)
1058
+ const resolvedSource = path4.relative(
1059
+ path4.dirname(entryFile),
1060
+ path4.resolve(path4.dirname(file), ref.importSource)
885
1061
  ).replace(/\\/g, "/");
886
1062
  const sourceSpecifier = resolvedSource.startsWith(".") ? resolvedSource : `./${resolvedSource}`;
887
1063
  if (!bySource2.has(sourceSpecifier))
@@ -927,9 +1103,9 @@ function buildNodeReexports(srcDir, entryFile) {
927
1103
  }
928
1104
  const bySource = /* @__PURE__ */ new Map();
929
1105
  for (const ref of schemaRefs) {
930
- const resolvedSource = path3.relative(
931
- path3.dirname(entryFile),
932
- path3.resolve(path3.dirname(file), ref.importSource)
1106
+ const resolvedSource = path4.relative(
1107
+ path4.dirname(entryFile),
1108
+ path4.resolve(path4.dirname(file), ref.importSource)
933
1109
  ).replace(/\\/g, "/");
934
1110
  const sourceSpecifier = resolvedSource.startsWith(".") ? resolvedSource : `./${resolvedSource}`;
935
1111
  if (!bySource.has(sourceSpecifier)) bySource.set(sourceSpecifier, []);
@@ -943,12 +1119,12 @@ function buildNodeReexports(srcDir, entryFile) {
943
1119
  }
944
1120
  function typeGenerator(options) {
945
1121
  const { srcDir, outDir, entryFiles } = options;
946
- const serverTsconfig = path3.resolve(srcDir, "tsconfig.json");
947
- const rootTsconfig = path3.resolve("tsconfig.json");
948
- const userTsconfig = fs3.existsSync(serverTsconfig) ? serverTsconfig : rootTsconfig;
949
- 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(
950
1126
  process.cwd(),
951
- path3.join(path3.dirname(srcDir), "client")
1127
+ path4.join(path4.dirname(srcDir), "client")
952
1128
  );
953
1129
  const augmentedContents = /* @__PURE__ */ new Map();
954
1130
  let origReadFile = null;
@@ -960,9 +1136,9 @@ function typeGenerator(options) {
960
1136
  for (const entryFile of entryFiles) {
961
1137
  const reexports = buildNodeReexports(srcDir, entryFile);
962
1138
  if (!reexports) continue;
963
- const original = fs3.readFileSync(entryFile, "utf-8");
1139
+ const original = fs5.readFileSync(entryFile, "utf-8");
964
1140
  augmentedContents.set(
965
- path3.resolve(entryFile),
1141
+ path4.resolve(entryFile),
966
1142
  `${original}
967
1143
  ${reexports}
968
1144
  `
@@ -971,7 +1147,7 @@ ${reexports}
971
1147
  if (augmentedContents.size === 0) return;
972
1148
  origReadFile = ts.sys.readFile.bind(ts.sys);
973
1149
  ts.sys.readFile = (fileName, encoding) => {
974
- const resolved = path3.resolve(fileName);
1150
+ const resolved = path4.resolve(fileName);
975
1151
  return augmentedContents.get(resolved) ?? origReadFile(fileName, encoding);
976
1152
  };
977
1153
  },
@@ -995,7 +1171,7 @@ ${reexports}
995
1171
  }
996
1172
  },
997
1173
  outDir,
998
- ...fs3.existsSync(userTsconfig) && { tsconfigPath: userTsconfig },
1174
+ ...fs5.existsSync(userTsconfig) && { tsconfigPath: userTsconfig },
999
1175
  compilerOptions: {
1000
1176
  noEmit: false,
1001
1177
  declaration: true,
@@ -1068,8 +1244,8 @@ function esmWrapper() {
1068
1244
  }
1069
1245
 
1070
1246
  // src/vite/server/plugins/package-json-generator.ts
1071
- import fs4 from "fs";
1072
- import path4 from "path";
1247
+ import fs6 from "fs";
1248
+ import path5 from "path";
1073
1249
  import { builtinModules as builtinModules2 } from "node:module";
1074
1250
  var nodeBuiltins = /* @__PURE__ */ new Set([
1075
1251
  ...builtinModules2,
@@ -1158,13 +1334,13 @@ function packageJsonGenerator(options) {
1158
1334
  }
1159
1335
  },
1160
1336
  closeBundle() {
1161
- const rootPackageJsonPath = path4.resolve("./package.json");
1162
- if (!fs4.existsSync(rootPackageJsonPath)) {
1337
+ const rootPackageJsonPath = path5.resolve("./package.json");
1338
+ if (!fs6.existsSync(rootPackageJsonPath)) {
1163
1339
  logger.warn(`package.json not found: ${rootPackageJsonPath}`);
1164
1340
  return;
1165
1341
  }
1166
1342
  const rootPackageJson = JSON.parse(
1167
- fs4.readFileSync(rootPackageJsonPath, "utf-8")
1343
+ fs6.readFileSync(rootPackageJsonPath, "utf-8")
1168
1344
  );
1169
1345
  const sourceDeps = rootPackageJson.dependencies ?? {};
1170
1346
  const peerDeps = rootPackageJson.peerDependencies ?? {};
@@ -1176,12 +1352,12 @@ function packageJsonGenerator(options) {
1176
1352
  if (sourceDeps[dep]) {
1177
1353
  distDependencies[dep] = sourceDeps[dep];
1178
1354
  } else {
1179
- const dependencyPackageJsonPath = path4.resolve(
1355
+ const dependencyPackageJsonPath = path5.resolve(
1180
1356
  `./node_modules/${dep}/package.json`
1181
1357
  );
1182
- if (fs4.existsSync(dependencyPackageJsonPath)) {
1358
+ if (fs6.existsSync(dependencyPackageJsonPath)) {
1183
1359
  const dependencyPackageJson = JSON.parse(
1184
- fs4.readFileSync(dependencyPackageJsonPath, "utf-8")
1360
+ fs6.readFileSync(dependencyPackageJsonPath, "utf-8")
1185
1361
  );
1186
1362
  distDependencies[dep] = `^${dependencyPackageJson.version}`;
1187
1363
  }
@@ -1218,11 +1394,11 @@ function packageJsonGenerator(options) {
1218
1394
  }
1219
1395
  }
1220
1396
  }
1221
- if (!fs4.existsSync(outDir)) {
1222
- fs4.mkdirSync(outDir, { recursive: true });
1397
+ if (!fs6.existsSync(outDir)) {
1398
+ fs6.mkdirSync(outDir, { recursive: true });
1223
1399
  }
1224
- fs4.writeFileSync(
1225
- path4.join(outDir, "package.json"),
1400
+ fs6.writeFileSync(
1401
+ path5.join(outDir, "package.json"),
1226
1402
  JSON.stringify(distPackageJson, null, 2)
1227
1403
  );
1228
1404
  }
@@ -1241,11 +1417,11 @@ async function build(serverOpts, buildContext) {
1241
1417
  nodeTarget = "node22"
1242
1418
  } = serverOpts;
1243
1419
  const entries = Array.isArray(entry) ? entry : [entry];
1244
- const resolvedSrcDir = path5.resolve(srcDir);
1420
+ const resolvedSrcDir = path6.resolve(srcDir);
1245
1421
  const entryPoints = {};
1246
1422
  for (const entry2 of entries) {
1247
- const entryFilePath = path5.join(resolvedSrcDir, entry2);
1248
- if (fs5.existsSync(entryFilePath)) {
1423
+ const entryFilePath = path6.join(resolvedSrcDir, entry2);
1424
+ if (fs7.existsSync(entryFilePath)) {
1249
1425
  const fileName = entry2.replace(/\.ts$/, "");
1250
1426
  entryPoints[fileName] = entryFilePath;
1251
1427
  }
@@ -1321,7 +1497,7 @@ module.exports = function (RED) {
1321
1497
  });
1322
1498
  };
1323
1499
  `;
1324
- fs5.writeFileSync(path5.join(buildContext.outDir, "index.js"), bridgeCode);
1500
+ fs7.writeFileSync(path6.join(buildContext.outDir, "index.js"), bridgeCode);
1325
1501
  }
1326
1502
  } catch (error) {
1327
1503
  throw new BuildError("server", error);
@@ -1331,12 +1507,12 @@ module.exports = function (RED) {
1331
1507
  // src/vite/client/build.ts
1332
1508
  import { build as viteBuild2 } from "vite";
1333
1509
  import vue from "@vitejs/plugin-vue";
1334
- import fs11 from "fs";
1335
- import path11 from "path";
1510
+ import fs13 from "fs";
1511
+ import path12 from "path";
1336
1512
 
1337
1513
  // src/vite/client/plugins/help-generator.ts
1338
- import fs6 from "fs";
1339
- import path6 from "path";
1514
+ import fs8 from "fs";
1515
+ import path7 from "path";
1340
1516
  import { pathToFileURL as pathToFileURL2 } from "url";
1341
1517
  import { createRequire as createRequire2 } from "module";
1342
1518
 
@@ -1658,9 +1834,9 @@ ${table}
1658
1834
  `;
1659
1835
  }
1660
1836
  function loadNodeLabels(labelPath) {
1661
- if (!fs6.existsSync(labelPath)) return {};
1837
+ if (!fs8.existsSync(labelPath)) return {};
1662
1838
  try {
1663
- const raw = JSON.parse(fs6.readFileSync(labelPath, "utf-8"));
1839
+ const raw = JSON.parse(fs8.readFileSync(labelPath, "utf-8"));
1664
1840
  return {
1665
1841
  description: raw.description,
1666
1842
  configs: raw.configs,
@@ -1702,10 +1878,10 @@ function generateHelpDoc(nodeClass, labels, t) {
1702
1878
  if (inputSection) lines.push(inputSection);
1703
1879
  }
1704
1880
  if (nodeClass.outputsSchema) {
1705
- const os2 = nodeClass.outputsSchema;
1706
- if (Array.isArray(os2)) {
1881
+ const os3 = nodeClass.outputsSchema;
1882
+ if (Array.isArray(os3)) {
1707
1883
  const portSections = [];
1708
- os2.forEach((schema, i) => {
1884
+ os3.forEach((schema, i) => {
1709
1885
  const title = `${t.sections.port} ${i + 1}`;
1710
1886
  const portPropLabels = labels.outputs?.[i];
1711
1887
  const section = generateSchemaSection({
@@ -1724,9 +1900,9 @@ function generateHelpDoc(nodeClass, labels, t) {
1724
1900
  ${portSections.join("\n")}`
1725
1901
  );
1726
1902
  }
1727
- } else if (!("type" in os2 || "properties" in os2)) {
1903
+ } else if (!("type" in os3 || "properties" in os3)) {
1728
1904
  const portSections = [];
1729
- for (const [portName, schema] of Object.entries(os2)) {
1905
+ for (const [portName, schema] of Object.entries(os3)) {
1730
1906
  const portPropLabels = labels.outputs?.[portName];
1731
1907
  const section = generateSchemaSection({
1732
1908
  title: portName,
@@ -1748,7 +1924,7 @@ ${portSections.join("\n")}`
1748
1924
  const outputPropLabels = labels.outputs?.[0];
1749
1925
  const section = generateSchemaSection({
1750
1926
  title: t.sections.output,
1751
- schema: os2,
1927
+ schema: os3,
1752
1928
  t,
1753
1929
  labels: outputPropLabels,
1754
1930
  includeDefault: false
@@ -1759,9 +1935,9 @@ ${portSections.join("\n")}`
1759
1935
  return lines.join("\n").trim();
1760
1936
  }
1761
1937
  function discoverLanguages(labelsDir, nodeType) {
1762
- const nodeLabelsDir = path6.join(labelsDir, nodeType);
1763
- if (!fs6.existsSync(nodeLabelsDir)) return [];
1764
- 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"));
1765
1941
  }
1766
1942
  function helpGenerator(options) {
1767
1943
  const { outDir, localesOutDir, docsDir, labelsDir } = options;
@@ -1770,15 +1946,15 @@ function helpGenerator(options) {
1770
1946
  apply: "build",
1771
1947
  enforce: "post",
1772
1948
  async closeBundle() {
1773
- const esmPath = path6.resolve(outDir, "index.mjs");
1774
- const cjsPath = path6.resolve(outDir, "index.js");
1949
+ const esmPath = path7.resolve(outDir, "index.mjs");
1950
+ const cjsPath = path7.resolve(outDir, "index.js");
1775
1951
  let packageFn;
1776
1952
  try {
1777
- if (fs6.existsSync(esmPath)) {
1953
+ if (fs8.existsSync(esmPath)) {
1778
1954
  const fileUrl = pathToFileURL2(esmPath).href + `?t=${Date.now()}`;
1779
1955
  const mod = await import(fileUrl);
1780
1956
  packageFn = mod?.default ?? mod;
1781
- } else if (fs6.existsSync(cjsPath)) {
1957
+ } else if (fs8.existsSync(cjsPath)) {
1782
1958
  const require2 = createRequire2(import.meta.url);
1783
1959
  delete require2.cache[cjsPath];
1784
1960
  const rawMod = require2(cjsPath);
@@ -1795,10 +1971,10 @@ function helpGenerator(options) {
1795
1971
  const languages = discoverLanguages(labelsDir, type);
1796
1972
  if (!languages.includes("en-US")) languages.push("en-US");
1797
1973
  for (const lang of languages) {
1798
- const manualMd = path6.join(docsDir, type, `${lang}.md`);
1799
- const manualHtml = path6.join(docsDir, type, `${lang}.html`);
1800
- if (fs6.existsSync(manualMd) || fs6.existsSync(manualHtml)) continue;
1801
- 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`);
1802
1978
  const labels = loadNodeLabels(labelPath);
1803
1979
  const t = getHelpTranslations(lang);
1804
1980
  const content = generateHelpDoc(NodeClass, labels, t);
@@ -1812,11 +1988,11 @@ ${content}
1812
1988
  }
1813
1989
  }
1814
1990
  for (const [lang, scripts] of helpByLang) {
1815
- const langDir = path6.join(localesOutDir, lang);
1816
- fs6.mkdirSync(langDir, { recursive: true });
1817
- const indexPath = path6.join(langDir, "index.html");
1818
- const existing = fs6.existsSync(indexPath) ? fs6.readFileSync(indexPath, "utf-8") : "";
1819
- 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(
1820
1996
  indexPath,
1821
1997
  existing + (existing ? "\n" : "") + scripts.join("\n"),
1822
1998
  "utf-8"
@@ -1828,8 +2004,8 @@ ${content}
1828
2004
 
1829
2005
  // src/vite/client/plugins/html-generator.ts
1830
2006
  import mime from "mime-types";
1831
- import fs7 from "fs";
1832
- import path7 from "path";
2007
+ import fs9 from "fs";
2008
+ import path8 from "path";
1833
2009
  function htmlGenerator(options) {
1834
2010
  const { packageName, licensePath } = options;
1835
2011
  return {
@@ -1839,7 +2015,7 @@ function htmlGenerator(options) {
1839
2015
  generateBundle(_, bundle) {
1840
2016
  const resourcesTags = Object.keys(bundle).map((fileName) => {
1841
2017
  const asset = bundle[fileName];
1842
- const srcPath = path7.join(
2018
+ const srcPath = path8.join(
1843
2019
  "resources",
1844
2020
  packageName,
1845
2021
  fileName.replace(/^resources\/?/, "")
@@ -1867,8 +2043,8 @@ function htmlGenerator(options) {
1867
2043
  return null;
1868
2044
  }
1869
2045
  }).filter(Boolean).join("\n");
1870
- const licenseBanner = licensePath && fs7.existsSync(licensePath) ? `<!--
1871
- ${fs7.readFileSync(licensePath, "utf-8")}
2046
+ const licenseBanner = licensePath && fs9.existsSync(licensePath) ? `<!--
2047
+ ${fs9.readFileSync(licensePath, "utf-8")}
1872
2048
  -->` : "";
1873
2049
  this.emitFile({
1874
2050
  type: "asset",
@@ -1881,8 +2057,8 @@ ${resourcesTags}`
1881
2057
  }
1882
2058
 
1883
2059
  // src/vite/client/plugins/locales-generator.ts
1884
- import fs8 from "fs";
1885
- import path8 from "path";
2060
+ import fs10 from "fs";
2061
+ import path9 from "path";
1886
2062
  import { merge } from "es-toolkit";
1887
2063
  function localesGenerator(options) {
1888
2064
  const { outDir, docsDir, labelsDir } = options;
@@ -1900,103 +2076,113 @@ function localesGenerator(options) {
1900
2076
  ];
1901
2077
  const frameworkLabels = {
1902
2078
  "en-US": {
1903
- configs: { name: "Name" },
2079
+ configs: { name: "Name", returnProperty: "Return key" },
1904
2080
  toggles: {
1905
2081
  validateInput: "Validate Input",
1906
2082
  validateOutput: "Validate Output",
1907
2083
  errorPort: "Error Port",
1908
2084
  completePort: "Complete Port",
1909
- statusPort: "Status Port"
2085
+ statusPort: "Status Port",
2086
+ returnPropertyOverride: "Override return prop key"
1910
2087
  }
1911
2088
  },
1912
2089
  de: {
1913
- configs: { name: "Name" },
2090
+ configs: { name: "Name", returnProperty: "R\xFCckgabe-Schl\xFCssel" },
1914
2091
  toggles: {
1915
2092
  validateInput: "Eingabe validieren",
1916
2093
  validateOutput: "Ausgabe validieren",
1917
2094
  errorPort: "Fehler-Port",
1918
2095
  completePort: "Abschluss-Port",
1919
- statusPort: "Status-Port"
2096
+ statusPort: "Status-Port",
2097
+ returnPropertyOverride: "R\xFCckgabe-Schl\xFCssel \xFCberschreiben"
1920
2098
  }
1921
2099
  },
1922
2100
  "es-ES": {
1923
- configs: { name: "Nombre" },
2101
+ configs: { name: "Nombre", returnProperty: "Clave de retorno" },
1924
2102
  toggles: {
1925
2103
  validateInput: "Validar entrada",
1926
2104
  validateOutput: "Validar salida",
1927
2105
  errorPort: "Puerto de error",
1928
2106
  completePort: "Puerto de completado",
1929
- statusPort: "Puerto de estado"
2107
+ statusPort: "Puerto de estado",
2108
+ returnPropertyOverride: "Sobrescribir clave de retorno"
1930
2109
  }
1931
2110
  },
1932
2111
  fr: {
1933
- configs: { name: "Nom" },
2112
+ configs: { name: "Nom", returnProperty: "Cl\xE9 de retour" },
1934
2113
  toggles: {
1935
2114
  validateInput: "Valider l'entr\xE9e",
1936
2115
  validateOutput: "Valider la sortie",
1937
2116
  errorPort: "Port d'erreur",
1938
2117
  completePort: "Port de compl\xE9tion",
1939
- statusPort: "Port de statut"
2118
+ statusPort: "Port de statut",
2119
+ returnPropertyOverride: "Remplacer la cl\xE9 de retour"
1940
2120
  }
1941
2121
  },
1942
2122
  ko: {
1943
- configs: { name: "\uC774\uB984" },
2123
+ configs: { name: "\uC774\uB984", returnProperty: "\uBC18\uD658 \uD0A4" },
1944
2124
  toggles: {
1945
2125
  validateInput: "\uC785\uB825 \uAC80\uC99D",
1946
2126
  validateOutput: "\uCD9C\uB825 \uAC80\uC99D",
1947
2127
  errorPort: "\uC624\uB958 \uD3EC\uD2B8",
1948
2128
  completePort: "\uC644\uB8CC \uD3EC\uD2B8",
1949
- statusPort: "\uC0C1\uD0DC \uD3EC\uD2B8"
2129
+ statusPort: "\uC0C1\uD0DC \uD3EC\uD2B8",
2130
+ returnPropertyOverride: "\uBC18\uD658 \uD0A4 \uC7AC\uC815\uC758"
1950
2131
  }
1951
2132
  },
1952
2133
  "pt-BR": {
1953
- configs: { name: "Nome" },
2134
+ configs: { name: "Nome", returnProperty: "Chave de retorno" },
1954
2135
  toggles: {
1955
2136
  validateInput: "Validar Entrada",
1956
2137
  validateOutput: "Validar Sa\xEDda",
1957
2138
  errorPort: "Porta de Erro",
1958
2139
  completePort: "Porta de Conclus\xE3o",
1959
- statusPort: "Porta de Status"
2140
+ statusPort: "Porta de Status",
2141
+ returnPropertyOverride: "Sobrescrever chave de retorno"
1960
2142
  }
1961
2143
  },
1962
2144
  ru: {
1963
- configs: { name: "\u0418\u043C\u044F" },
2145
+ configs: { name: "\u0418\u043C\u044F", returnProperty: "\u041A\u043B\u044E\u0447 \u0432\u043E\u0437\u0432\u0440\u0430\u0442\u0430" },
1964
2146
  toggles: {
1965
2147
  validateInput: "\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0432\u0445\u043E\u0434",
1966
2148
  validateOutput: "\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0432\u044B\u0445\u043E\u0434",
1967
2149
  errorPort: "\u041F\u043E\u0440\u0442 \u043E\u0448\u0438\u0431\u043A\u0438",
1968
2150
  completePort: "\u041F\u043E\u0440\u0442 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u044F",
1969
- 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"
1970
2153
  }
1971
2154
  },
1972
2155
  ja: {
1973
- configs: { name: "\u540D\u524D" },
2156
+ configs: { name: "\u540D\u524D", returnProperty: "\u623B\u308A\u30AD\u30FC" },
1974
2157
  toggles: {
1975
2158
  validateInput: "\u5165\u529B\u691C\u8A3C",
1976
2159
  validateOutput: "\u51FA\u529B\u691C\u8A3C",
1977
2160
  errorPort: "\u30A8\u30E9\u30FC\u30DD\u30FC\u30C8",
1978
2161
  completePort: "\u5B8C\u4E86\u30DD\u30FC\u30C8",
1979
- 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"
1980
2164
  }
1981
2165
  },
1982
2166
  "zh-CN": {
1983
- configs: { name: "\u540D\u79F0" },
2167
+ configs: { name: "\u540D\u79F0", returnProperty: "\u8FD4\u56DE\u952E" },
1984
2168
  toggles: {
1985
2169
  validateInput: "\u9A8C\u8BC1\u8F93\u5165",
1986
2170
  validateOutput: "\u9A8C\u8BC1\u8F93\u51FA",
1987
2171
  errorPort: "\u9519\u8BEF\u7AEF\u53E3",
1988
2172
  completePort: "\u5B8C\u6210\u7AEF\u53E3",
1989
- statusPort: "\u72B6\u6001\u7AEF\u53E3"
2173
+ statusPort: "\u72B6\u6001\u7AEF\u53E3",
2174
+ returnPropertyOverride: "\u8986\u76D6\u8FD4\u56DE\u952E"
1990
2175
  }
1991
2176
  },
1992
2177
  "zh-TW": {
1993
- configs: { name: "\u540D\u7A31" },
2178
+ configs: { name: "\u540D\u7A31", returnProperty: "\u8FD4\u56DE\u9375" },
1994
2179
  toggles: {
1995
2180
  validateInput: "\u9A57\u8B49\u8F38\u5165",
1996
2181
  validateOutput: "\u9A57\u8B49\u8F38\u51FA",
1997
2182
  errorPort: "\u932F\u8AA4\u7AEF\u53E3",
1998
2183
  completePort: "\u5B8C\u6210\u7AEF\u53E3",
1999
- statusPort: "\u72C0\u614B\u7AEF\u53E3"
2184
+ statusPort: "\u72C0\u614B\u7AEF\u53E3",
2185
+ returnPropertyOverride: "\u8986\u84CB\u8FD4\u56DE\u9375"
2000
2186
  }
2001
2187
  }
2002
2188
  };
@@ -2015,17 +2201,17 @@ Supported: ${languages.join(", ")}`
2015
2201
  }
2016
2202
  function forEachFile(baseDir, fileExtensions, processFile) {
2017
2203
  const langMap = /* @__PURE__ */ new Map();
2018
- if (!fs8.existsSync(baseDir)) return langMap;
2019
- 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());
2020
2206
  for (const nodeDir of nodeDirs) {
2021
2207
  const nodeType = nodeDir.name;
2022
- const nodePath = path8.join(baseDir, nodeType);
2023
- const files = fs8.readdirSync(nodePath);
2208
+ const nodePath = path9.join(baseDir, nodeType);
2209
+ const files = fs10.readdirSync(nodePath);
2024
2210
  for (const file of files) {
2025
- const ext = path8.extname(file);
2211
+ const ext = path9.extname(file);
2026
2212
  if (!fileExtensions.includes(ext)) continue;
2027
- const lang = path8.basename(file, ext);
2028
- const filePath = path8.join(nodePath, file);
2213
+ const lang = path9.basename(file, ext);
2214
+ const filePath = path9.join(nodePath, file);
2029
2215
  validateLanguage(lang, filePath);
2030
2216
  const value = processFile({ ext, filePath, nodeType });
2031
2217
  if (value == null) continue;
@@ -2043,10 +2229,10 @@ Supported: ${languages.join(", ")}`
2043
2229
  }
2044
2230
  function writeOutput(langMap, fileName, serialize) {
2045
2231
  for (const [lang, data] of langMap.entries()) {
2046
- const langOutDir = path8.join(outDir, lang);
2047
- fs8.mkdirSync(langOutDir, { recursive: true });
2048
- fs8.writeFileSync(
2049
- 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),
2050
2236
  serialize(data),
2051
2237
  "utf-8"
2052
2238
  );
@@ -2058,7 +2244,7 @@ Supported: ${languages.join(", ")}`
2058
2244
  ({ ext, filePath, nodeType }) => {
2059
2245
  const type = ext === ".html" ? "text/html" : ext === ".md" ? "text/markdown" : null;
2060
2246
  if (!type) return null;
2061
- const content = fs8.readFileSync(filePath, "utf-8");
2247
+ const content = fs10.readFileSync(filePath, "utf-8");
2062
2248
  return [
2063
2249
  `<script type="${type}" data-help-name="${nodeType}">
2064
2250
  ${content}
@@ -2075,7 +2261,7 @@ ${content}
2075
2261
  labelsDir,
2076
2262
  [".json"],
2077
2263
  ({ filePath, nodeType }) => {
2078
- const parsed = JSON.parse(fs8.readFileSync(filePath, "utf-8"));
2264
+ const parsed = JSON.parse(fs10.readFileSync(filePath, "utf-8"));
2079
2265
  if (parsed[nodeType] && typeof parsed[nodeType] === "object") {
2080
2266
  console.warn(
2081
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`
@@ -2125,8 +2311,8 @@ function minifier() {
2125
2311
  // src/vite/client/plugins/node-definitions-inliner.ts
2126
2312
  import { createRequire as createRequire3 } from "module";
2127
2313
  import { pathToFileURL as pathToFileURL3 } from "url";
2128
- import path9 from "path";
2129
- import fs9 from "fs";
2314
+ import path10 from "path";
2315
+ import fs11 from "fs";
2130
2316
  import mime2 from "mime-types";
2131
2317
  var VIRTUAL_ID = "virtual:nrg/node-definitions";
2132
2318
  var RESOLVED_ID = "\0" + VIRTUAL_ID;
@@ -2157,9 +2343,9 @@ function getCredentialsFromSchema(schema) {
2157
2343
  return result;
2158
2344
  }
2159
2345
  function resolveIcon(iconsDir, type) {
2160
- if (!fs9.existsSync(iconsDir)) return void 0;
2161
- return fs9.readdirSync(iconsDir).find((f) => {
2162
- 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;
2163
2349
  const mimeType = mime2.lookup(f);
2164
2350
  return mimeType !== false && mimeType.startsWith("image/");
2165
2351
  });
@@ -2167,7 +2353,7 @@ function resolveIcon(iconsDir, type) {
2167
2353
  function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir, nodesDir, hasUserEntry = true) {
2168
2354
  let _nodeTypes = [];
2169
2355
  let _definitions = {};
2170
- const cacheDir = path9.resolve("node_modules", ".nrg", "client");
2356
+ const cacheDir = path10.resolve("node_modules", ".nrg", "client");
2171
2357
  return {
2172
2358
  name: "vite-plugin-node-red:client:node-definitions-inliner",
2173
2359
  enforce: "pre",
@@ -2176,14 +2362,14 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2176
2362
  async buildStart() {
2177
2363
  _nodeTypes = [];
2178
2364
  _definitions = {};
2179
- const esmEntryPath = path9.resolve(serverOutDir, "index.mjs");
2180
- const cjsEntryPath = path9.resolve(serverOutDir, "index.js");
2365
+ const esmEntryPath = path10.resolve(serverOutDir, "index.mjs");
2366
+ const cjsEntryPath = path10.resolve(serverOutDir, "index.js");
2181
2367
  let packageFn;
2182
- if (fs9.existsSync(esmEntryPath)) {
2368
+ if (fs11.existsSync(esmEntryPath)) {
2183
2369
  const fileUrl = pathToFileURL3(esmEntryPath).href + `?t=${Date.now()}`;
2184
2370
  const mod = await import(fileUrl);
2185
2371
  packageFn = mod?.default ?? mod;
2186
- } else if (fs9.existsSync(cjsEntryPath)) {
2372
+ } else if (fs11.existsSync(cjsEntryPath)) {
2187
2373
  const require2 = createRequire3(import.meta.url);
2188
2374
  delete require2.cache[cjsEntryPath];
2189
2375
  const rawMod = require2(cjsEntryPath);
@@ -2224,14 +2410,14 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2224
2410
  };
2225
2411
  }
2226
2412
  if (!hasUserEntry) {
2227
- const nodesCache = path9.resolve(cacheDir, "nodes");
2228
- if (fs9.existsSync(nodesCache)) {
2229
- fs9.rmSync(nodesCache, { recursive: true });
2413
+ const nodesCache = path10.resolve(cacheDir, "nodes");
2414
+ if (fs11.existsSync(nodesCache)) {
2415
+ fs11.rmSync(nodesCache, { recursive: true });
2230
2416
  }
2231
- fs9.mkdirSync(nodesCache, { recursive: true });
2417
+ fs11.mkdirSync(nodesCache, { recursive: true });
2232
2418
  for (const type of _nodeTypes) {
2233
- const userTsPath = nodesDir ? path9.resolve(nodesDir, `${type}.ts`) : null;
2234
- if (userTsPath && fs9.existsSync(userTsPath)) continue;
2419
+ const userTsPath = nodesDir ? path10.resolve(nodesDir, `${type}.ts`) : null;
2420
+ if (userTsPath && fs11.existsSync(userTsPath)) continue;
2235
2421
  const content = [
2236
2422
  `// auto-generated by nrg`,
2237
2423
  `import { defineNode } from "@bonsae/nrg/client";`,
@@ -2241,13 +2427,13 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2241
2427
  `});`,
2242
2428
  ``
2243
2429
  ].join("\n");
2244
- fs9.writeFileSync(path9.resolve(nodesCache, `${type}.ts`), content);
2430
+ fs11.writeFileSync(path10.resolve(nodesCache, `${type}.ts`), content);
2245
2431
  }
2246
2432
  const entryContent = generateEntryCode("");
2247
- fs9.mkdirSync(path9.dirname(path9.resolve(cacheDir, "index.ts")), {
2433
+ fs11.mkdirSync(path10.dirname(path10.resolve(cacheDir, "index.ts")), {
2248
2434
  recursive: true
2249
2435
  });
2250
- fs9.writeFileSync(path9.resolve(cacheDir, "index.ts"), entryContent);
2436
+ fs11.writeFileSync(path10.resolve(cacheDir, "index.ts"), entryContent);
2251
2437
  }
2252
2438
  },
2253
2439
  resolveId(id) {
@@ -2270,12 +2456,12 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2270
2456
  const nrgImports = /* @__PURE__ */ new Set(["__setSchemas"]);
2271
2457
  const lines = [`import __nrgSchemas from "${VIRTUAL_ID}";`];
2272
2458
  const postLines = [`__setSchemas(__nrgSchemas);`];
2273
- if (componentsDir && fs9.existsSync(componentsDir)) {
2459
+ if (componentsDir && fs11.existsSync(componentsDir)) {
2274
2460
  const formImports = [];
2275
2461
  const formEntries = [];
2276
2462
  for (const type of _nodeTypes) {
2277
- const componentPath = path9.resolve(componentsDir, `${type}.vue`);
2278
- if (fs9.existsSync(componentPath)) {
2463
+ const componentPath = path10.resolve(componentsDir, `${type}.vue`);
2464
+ if (fs11.existsSync(componentPath)) {
2279
2465
  const varName = `__nrgForm_${type.replace(/-/g, "_")}`;
2280
2466
  formImports.push(
2281
2467
  `import ${varName} from ${JSON.stringify(componentPath)};`
@@ -2290,12 +2476,12 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2290
2476
  }
2291
2477
  }
2292
2478
  if (!hasUserEntry) {
2293
- const nodesCache = path9.resolve(cacheDir, "nodes");
2479
+ const nodesCache = path10.resolve(cacheDir, "nodes");
2294
2480
  const defVarNames = [];
2295
2481
  for (const type of _nodeTypes) {
2296
2482
  const varName = `__nrgNodeDef_${type.replace(/-/g, "_")}`;
2297
- const userTsPath = nodesDir ? path9.resolve(nodesDir, `${type}.ts`) : null;
2298
- 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`);
2299
2485
  lines.push(`import ${varName} from ${JSON.stringify(tsPath)};`);
2300
2486
  defVarNames.push(varName);
2301
2487
  }
@@ -2313,8 +2499,8 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2313
2499
  }
2314
2500
 
2315
2501
  // src/vite/client/plugins/static-copy.ts
2316
- import fs10 from "fs";
2317
- import path10 from "path";
2502
+ import fs12 from "fs";
2503
+ import path11 from "path";
2318
2504
  function staticCopy(options) {
2319
2505
  const { targets } = options;
2320
2506
  return {
@@ -2323,23 +2509,23 @@ function staticCopy(options) {
2323
2509
  enforce: "post",
2324
2510
  closeBundle() {
2325
2511
  for (const { src, dest } of targets) {
2326
- if (!fs10.existsSync(src)) continue;
2327
- fs10.mkdirSync(dest, { recursive: true });
2328
- const stat = fs10.statSync(src);
2512
+ if (!fs12.existsSync(src)) continue;
2513
+ fs12.mkdirSync(dest, { recursive: true });
2514
+ const stat = fs12.statSync(src);
2329
2515
  if (stat.isDirectory()) {
2330
- const files = fs10.readdirSync(src);
2516
+ const files = fs12.readdirSync(src);
2331
2517
  for (const file of files) {
2332
- const srcFile = path10.join(src, file);
2333
- const destFile = path10.join(dest, file);
2334
- 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);
2335
2521
  if (fileStat.isDirectory()) {
2336
- fs10.cpSync(srcFile, destFile, { recursive: true });
2522
+ fs12.cpSync(srcFile, destFile, { recursive: true });
2337
2523
  } else {
2338
- fs10.copyFileSync(srcFile, destFile);
2524
+ fs12.copyFileSync(srcFile, destFile);
2339
2525
  }
2340
2526
  }
2341
2527
  } else {
2342
- fs10.copyFileSync(src, dest);
2528
+ fs12.copyFileSync(src, dest);
2343
2529
  }
2344
2530
  }
2345
2531
  }
@@ -2360,74 +2546,74 @@ async function build2(clientBuildOptions, buildContext) {
2360
2546
  globals = {},
2361
2547
  manualChunks
2362
2548
  } = clientBuildOptions;
2363
- const physicalEntryPath = path11.resolve(srcDir, entry);
2549
+ const physicalEntryPath = path12.resolve(srcDir, entry);
2364
2550
  let entryPath;
2365
2551
  let generatedEntry = false;
2366
- if (fs11.existsSync(physicalEntryPath)) {
2552
+ if (fs13.existsSync(physicalEntryPath)) {
2367
2553
  entryPath = physicalEntryPath;
2368
2554
  } else {
2369
- const cacheDir = path11.resolve("node_modules", ".nrg", "client");
2370
- const cachedEntryPath = path11.resolve(cacheDir, entry);
2371
- if (!fs11.existsSync(cacheDir)) {
2372
- 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 });
2373
2559
  }
2374
- fs11.writeFileSync(cachedEntryPath, "// auto-generated entry\n");
2560
+ fs13.writeFileSync(cachedEntryPath, "// auto-generated entry\n");
2375
2561
  entryPath = cachedEntryPath;
2376
2562
  generatedEntry = true;
2377
2563
  }
2378
- const iconsDir = path11.resolve(
2379
- 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")
2380
2566
  );
2381
2567
  const plugins = [
2382
2568
  vue(),
2383
2569
  nodeDefinitionsInliner(
2384
2570
  buildContext.outDir,
2385
2571
  entryPath,
2386
- fs11.existsSync(iconsDir) ? iconsDir : void 0,
2387
- path11.resolve(srcDir, "components"),
2388
- path11.resolve(srcDir, "nodes"),
2572
+ fs13.existsSync(iconsDir) ? iconsDir : void 0,
2573
+ path12.resolve(srcDir, "components"),
2574
+ path12.resolve(srcDir, "nodes"),
2389
2575
  !generatedEntry
2390
2576
  )
2391
2577
  ];
2392
2578
  plugins.push(
2393
2579
  htmlGenerator({
2394
2580
  packageName: buildContext.packageName,
2395
- licensePath: licensePath ? path11.resolve(licensePath) : void 0
2581
+ licensePath: licensePath ? path12.resolve(licensePath) : void 0
2396
2582
  })
2397
2583
  );
2398
2584
  if (locales) {
2399
2585
  const { docsDir = "./locales/docs", labelsDir = "./locales/labels" } = locales;
2400
- const localesOutDir = path11.join(buildContext.outDir, "locales");
2586
+ const localesOutDir = path12.join(buildContext.outDir, "locales");
2401
2587
  plugins.push(
2402
2588
  localesGenerator({
2403
2589
  outDir: localesOutDir,
2404
- docsDir: path11.resolve(docsDir),
2405
- labelsDir: path11.resolve(labelsDir)
2590
+ docsDir: path12.resolve(docsDir),
2591
+ labelsDir: path12.resolve(labelsDir)
2406
2592
  })
2407
2593
  );
2408
2594
  plugins.push(
2409
2595
  helpGenerator({
2410
2596
  outDir: buildContext.outDir,
2411
2597
  localesOutDir,
2412
- docsDir: path11.resolve(docsDir),
2413
- labelsDir: path11.resolve(labelsDir)
2598
+ docsDir: path12.resolve(docsDir),
2599
+ labelsDir: path12.resolve(labelsDir)
2414
2600
  })
2415
2601
  );
2416
2602
  }
2417
2603
  const copyTargets = [];
2418
- const publicDir = path11.resolve(
2419
- staticDirs.public ?? path11.join(srcDir, "public")
2604
+ const publicDir = path12.resolve(
2605
+ staticDirs.public ?? path12.join(srcDir, "public")
2420
2606
  );
2421
- if (fs11.existsSync(publicDir)) {
2607
+ if (fs13.existsSync(publicDir)) {
2422
2608
  copyTargets.push({
2423
2609
  src: publicDir,
2424
- dest: path11.join(buildContext.outDir, "resources")
2610
+ dest: path12.join(buildContext.outDir, "resources")
2425
2611
  });
2426
2612
  }
2427
- if (fs11.existsSync(iconsDir)) {
2613
+ if (fs13.existsSync(iconsDir)) {
2428
2614
  copyTargets.push({
2429
2615
  src: iconsDir,
2430
- dest: path11.join(buildContext.outDir, "icons")
2616
+ dest: path12.join(buildContext.outDir, "icons")
2431
2617
  });
2432
2618
  }
2433
2619
  if (copyTargets.length > 0) {
@@ -2455,10 +2641,10 @@ async function build2(clientBuildOptions, buildContext) {
2455
2641
  configFile: false,
2456
2642
  logLevel: "warn",
2457
2643
  base: `/resources/${buildContext.packageName}`,
2458
- publicDir: path11.resolve(srcDir, "public"),
2644
+ publicDir: path12.resolve(srcDir, "public"),
2459
2645
  resolve: {
2460
2646
  alias: {
2461
- "@": path11.resolve(srcDir)
2647
+ "@": path12.resolve(srcDir)
2462
2648
  }
2463
2649
  },
2464
2650
  plugins,
@@ -2512,8 +2698,8 @@ async function build2(clientBuildOptions, buildContext) {
2512
2698
  throw new BuildError("client", error);
2513
2699
  } finally {
2514
2700
  if (generatedEntry) {
2515
- if (fs11.existsSync(entryPath)) {
2516
- fs11.unlinkSync(entryPath);
2701
+ if (fs13.existsSync(entryPath)) {
2702
+ fs13.unlinkSync(entryPath);
2517
2703
  }
2518
2704
  }
2519
2705
  }
@@ -2534,6 +2720,8 @@ function serverPlugin(options) {
2534
2720
  extraFilesCopyTargets,
2535
2721
  buildContext
2536
2722
  } = options;
2723
+ const { slug, basePath } = nodeRedLauncher;
2724
+ const proxyKey = slug ? `^/${slug}(?:/|\\?|$)` : "^/.*";
2537
2725
  let nodeRedPort;
2538
2726
  let initialStartDone = false;
2539
2727
  let isStarting = false;
@@ -2558,7 +2746,7 @@ function serverPlugin(options) {
2558
2746
  logger.stopSpinner("Copied extra files");
2559
2747
  }
2560
2748
  };
2561
- const start = async (clean = false) => {
2749
+ const start2 = async (clean = false) => {
2562
2750
  if (isStarting) {
2563
2751
  pendingStart = true;
2564
2752
  return;
@@ -2573,7 +2761,7 @@ function serverPlugin(options) {
2573
2761
  logger.stopSpinner("Node-RED started");
2574
2762
  const proxyConfig = server.config.server.proxy;
2575
2763
  if (proxyConfig && typeof proxyConfig === "object") {
2576
- const rule = proxyConfig["^/.*"];
2764
+ const rule = proxyConfig[proxyKey];
2577
2765
  if (rule && typeof rule === "object") {
2578
2766
  rule.target = `http://127.0.0.1:${nodeRedPort}`;
2579
2767
  }
@@ -2591,14 +2779,14 @@ function serverPlugin(options) {
2591
2779
  } finally {
2592
2780
  isStarting = false;
2593
2781
  if (pendingStart) {
2594
- start(false);
2782
+ start2(false);
2595
2783
  }
2596
2784
  }
2597
2785
  };
2598
2786
  const printServerUrls = (vitePort, nodeRedPorts) => {
2599
2787
  console.log();
2600
2788
  console.log(
2601
- ` ${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}`)}`
2602
2790
  );
2603
2791
  if (nodeRedPorts.actual != nodeRedPorts.preferred) {
2604
2792
  console.log(
@@ -2620,7 +2808,7 @@ function serverPlugin(options) {
2620
2808
  server: {
2621
2809
  host: "127.0.0.1",
2622
2810
  proxy: {
2623
- "^/.*": {
2811
+ [proxyKey]: {
2624
2812
  target: `http://127.0.0.1:${nodeRedLauncher.preferredPort}`,
2625
2813
  changeOrigin: true,
2626
2814
  ws: true,
@@ -2663,8 +2851,19 @@ function serverPlugin(options) {
2663
2851
  },
2664
2852
  async configureServer(viteServer) {
2665
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
+ }
2666
2865
  logger.intro();
2667
- await start(true);
2866
+ await start2(true);
2668
2867
  initialStartDone = true;
2669
2868
  printServerUrls(server.config.server.port ?? 5173, {
2670
2869
  actual: nodeRedPort,
@@ -2672,20 +2871,20 @@ function serverPlugin(options) {
2672
2871
  });
2673
2872
  logger.startGroup("Node-RED");
2674
2873
  nodeRedLauncher.flushLogs();
2675
- const serverSrcDir = path12.resolve(
2874
+ const serverSrcDir = path13.resolve(
2676
2875
  serverBuildOptions.srcDir ?? "./server"
2677
2876
  );
2678
- const clientSrcDir = path12.resolve(
2877
+ const clientSrcDir = path13.resolve(
2679
2878
  clientBuildOptions.srcDir ?? "./client"
2680
2879
  );
2681
- const localesDocsDir = path12.resolve(
2880
+ const localesDocsDir = path13.resolve(
2682
2881
  clientBuildOptions.locales?.docsDir ?? "./locales/docs"
2683
2882
  );
2684
- const localesLabelsDir = path12.resolve(
2883
+ const localesLabelsDir = path13.resolve(
2685
2884
  clientBuildOptions.locales?.labelsDir ?? "./locales/labels"
2686
2885
  );
2687
- const iconsDir = path12.resolve(
2688
- clientBuildOptions.staticDirs?.icons ?? path12.join(path12.dirname(clientSrcDir), "icons")
2886
+ const iconsDir = path13.resolve(
2887
+ clientBuildOptions.staticDirs?.icons ?? path13.join(path13.dirname(clientSrcDir), "icons")
2689
2888
  );
2690
2889
  const watchPaths = [
2691
2890
  serverSrcDir,
@@ -2700,12 +2899,12 @@ function serverPlugin(options) {
2700
2899
  ignored: ["**/node_modules/**", "**/dist/**", "**/.node-red/**"]
2701
2900
  });
2702
2901
  const debounceBeforeStart = debounce(
2703
- () => start(false),
2902
+ () => start2(false),
2704
2903
  nodeRedLauncher.restartDelay ?? 1e3
2705
2904
  );
2706
2905
  const handleFileChange = (file, event) => {
2707
2906
  if (!initialStartDone) return;
2708
- logger.info(`${event}: ${path12.relative(process.cwd(), file)}`);
2907
+ logger.info(`${event}: ${path13.relative(process.cwd(), file)}`);
2709
2908
  debounceBeforeStart();
2710
2909
  };
2711
2910
  watcher.on("change", (file) => handleFileChange(file, "Changed"));
@@ -2783,30 +2982,33 @@ function buildPlugin(options) {
2783
2982
  }
2784
2983
 
2785
2984
  // src/vite/plugin.ts
2786
- function nodeRed(options = {}) {
2787
- const { outDir = DEFAULT_OUTPUT_DIR } = options;
2985
+ function nrg(options = {}) {
2986
+ const { build: build3 = {}, server = {} } = options;
2987
+ const { outDir = DEFAULT_OUTPUT_DIR } = build3;
2788
2988
  const clientBuildOptions = mergeOptions(
2789
2989
  DEFAULT_CLIENT_BUILD_OPTIONS,
2790
- options.clientBuildOptions
2990
+ build3.client
2791
2991
  );
2792
2992
  const serverBuildOptions = mergeOptions(
2793
2993
  DEFAULT_SERVER_BUILD_OPTIONS,
2794
- options.serverBuildOptions
2994
+ build3.server
2795
2995
  );
2796
2996
  const nodeRedLauncherOptions = mergeOptions(
2797
2997
  DEFAULT_NODE_RED_LAUNCHER_OPTIONS,
2798
- options.nodeRedLauncherOptions
2998
+ server.nodeRed
2799
2999
  );
2800
- const extraFilesCopyTargets = options.extraFilesCopyTargets ?? DEFAULT_EXTRA_FILES_COPY_TARGETS;
2801
- const resolvedOutDir = path13.resolve(outDir);
3000
+ const extraFilesCopyTargets = build3.extraFilesCopyTargets ?? DEFAULT_EXTRA_FILES_COPY_TARGETS;
3001
+ const resolvedOutDir = path14.resolve(outDir);
2802
3002
  const buildContext = {
2803
3003
  outDir: resolvedOutDir,
2804
3004
  packageName: getPackageName(),
2805
3005
  isDev: process.env.NODE_ENV === "development"
2806
3006
  };
3007
+ const slug = resolveSlug(server.slug);
2807
3008
  const nodeRedLauncher = new NodeRedLauncher(
2808
3009
  resolvedOutDir,
2809
- nodeRedLauncherOptions
3010
+ nodeRedLauncherOptions,
3011
+ slug
2810
3012
  );
2811
3013
  return [
2812
3014
  serverPlugin({
@@ -2825,5 +3027,5 @@ function nodeRed(options = {}) {
2825
3027
  ];
2826
3028
  }
2827
3029
  export {
2828
- nodeRed
3030
+ nrg
2829
3031
  };