@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.
- package/package.json +1 -1
- package/server/index.cjs +86 -9
- package/server/resources/nrg-client.js +2020 -1987
- package/test/client/component/config.js +11 -0
- package/test/client/component/index.js +218 -235
- package/test/client/component/nrg.css +1 -0
- package/test/client/component/setup.js +1549 -140
- package/test/client/e2e/index.js +720 -369
- package/test/client/unit/index.js +204 -16
- package/test/client/unit/setup.js +209 -19
- package/test/server/unit/index.js +25 -4
- package/tsconfig/core/client.json +1 -1
- package/tsconfig/test/client/component.json +1 -1
- package/types/client.d.ts +98 -18
- package/types/server.d.ts +50 -12
- package/types/shims/brands.d.ts +32 -0
- package/types/shims/{form → client/form}/components/node-red-editor-input.vue.d.ts +1 -1
- package/types/shims/{form → client/form}/components/node-red-json-schema-form.vue.d.ts +21 -2
- package/types/shims/{form → client/form}/components/node-red-select-input.vue.d.ts +1 -0
- package/types/shims/{form → client/form}/components/node-red-typed-input.vue.d.ts +1 -0
- package/types/shims/client/types.d.ts +206 -0
- package/types/shims/components.d.ts +8 -8
- package/types/shims/constants.d.ts +4 -0
- package/types/shims/schema-options.d.ts +23 -10
- package/types/shims/typebox.d.ts +2 -2
- package/types/test-client-component.d.ts +170 -55
- package/types/test-client-e2e.d.ts +50 -0
- package/types/test-client-unit.d.ts +86 -22
- package/types/test-server-unit.d.ts +3 -1
- package/types/vite.d.ts +38 -9
- package/vite/index.js +732 -530
- /package/types/shims/{form → client/form}/components/node-red-config-input.vue.d.ts +0 -0
- /package/types/shims/{form → client/form}/components/node-red-input-label.vue.d.ts +0 -0
- /package/types/shims/{form → client/form}/components/node-red-input.vue.d.ts +0 -0
- /package/types/shims/{form → client/form}/components/node-red-toggle.vue.d.ts +0 -0
- /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
|
|
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
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
281
|
-
|
|
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
|
-
|
|
284
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
295
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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 = ${
|
|
358
|
-
if(!settings.userDir){
|
|
359
|
-
settings.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(
|
|
363
|
-
settings.nodesDir.push(
|
|
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: ${
|
|
372
|
-
userDir:
|
|
475
|
+
uiPort: ${port},
|
|
476
|
+
userDir: ${userDirLiteral},
|
|
373
477
|
flowFile: "flows.json",
|
|
374
|
-
nodesDir: [
|
|
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
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
-
|
|
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
547
|
-
|
|
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
|
|
759
|
+
async doStop(skipPortUsageCheck) {
|
|
557
760
|
if (!this.process) return;
|
|
558
|
-
|
|
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
|
-
|
|
565
|
-
this.process.
|
|
566
|
-
|
|
567
|
-
|
|
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
|
-
|
|
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
|
|
593
|
-
|
|
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
|
-
|
|
605
|
-
|
|
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
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
|
807
|
+
import path13 from "path";
|
|
632
808
|
|
|
633
809
|
// src/vite/server/build.ts
|
|
634
810
|
import { build as viteBuild } from "vite";
|
|
635
|
-
import
|
|
636
|
-
import
|
|
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
|
|
641
|
-
import
|
|
816
|
+
import fs5 from "fs";
|
|
817
|
+
import path4 from "path";
|
|
642
818
|
import ts from "typescript";
|
|
643
819
|
function collectTsFiles(dir) {
|
|
644
|
-
if (!
|
|
645
|
-
return
|
|
646
|
-
const full =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1025
|
+
const nodesDir = path4.join(srcDir, "nodes");
|
|
850
1026
|
const nodeFiles = collectTsFiles(nodesDir);
|
|
851
1027
|
return nodeFiles.map((file) => {
|
|
852
|
-
const rel =
|
|
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(
|
|
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 =
|
|
883
|
-
|
|
884
|
-
|
|
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 =
|
|
931
|
-
|
|
932
|
-
|
|
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 =
|
|
947
|
-
const rootTsconfig =
|
|
948
|
-
const userTsconfig =
|
|
949
|
-
const clientDir =
|
|
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
|
-
|
|
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 =
|
|
1139
|
+
const original = fs5.readFileSync(entryFile, "utf-8");
|
|
964
1140
|
augmentedContents.set(
|
|
965
|
-
|
|
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 =
|
|
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
|
-
...
|
|
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
|
|
1072
|
-
import
|
|
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 =
|
|
1162
|
-
if (!
|
|
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
|
-
|
|
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 =
|
|
1355
|
+
const dependencyPackageJsonPath = path5.resolve(
|
|
1180
1356
|
`./node_modules/${dep}/package.json`
|
|
1181
1357
|
);
|
|
1182
|
-
if (
|
|
1358
|
+
if (fs6.existsSync(dependencyPackageJsonPath)) {
|
|
1183
1359
|
const dependencyPackageJson = JSON.parse(
|
|
1184
|
-
|
|
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 (!
|
|
1222
|
-
|
|
1397
|
+
if (!fs6.existsSync(outDir)) {
|
|
1398
|
+
fs6.mkdirSync(outDir, { recursive: true });
|
|
1223
1399
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
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 =
|
|
1420
|
+
const resolvedSrcDir = path6.resolve(srcDir);
|
|
1245
1421
|
const entryPoints = {};
|
|
1246
1422
|
for (const entry2 of entries) {
|
|
1247
|
-
const entryFilePath =
|
|
1248
|
-
if (
|
|
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
|
-
|
|
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
|
|
1335
|
-
import
|
|
1510
|
+
import fs13 from "fs";
|
|
1511
|
+
import path12 from "path";
|
|
1336
1512
|
|
|
1337
1513
|
// src/vite/client/plugins/help-generator.ts
|
|
1338
|
-
import
|
|
1339
|
-
import
|
|
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 (!
|
|
1837
|
+
if (!fs8.existsSync(labelPath)) return {};
|
|
1662
1838
|
try {
|
|
1663
|
-
const raw = JSON.parse(
|
|
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
|
|
1706
|
-
if (Array.isArray(
|
|
1881
|
+
const os3 = nodeClass.outputsSchema;
|
|
1882
|
+
if (Array.isArray(os3)) {
|
|
1707
1883
|
const portSections = [];
|
|
1708
|
-
|
|
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
|
|
1903
|
+
} else if (!("type" in os3 || "properties" in os3)) {
|
|
1728
1904
|
const portSections = [];
|
|
1729
|
-
for (const [portName, schema] of Object.entries(
|
|
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:
|
|
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 =
|
|
1763
|
-
if (!
|
|
1764
|
-
return
|
|
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 =
|
|
1774
|
-
const cjsPath =
|
|
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 (
|
|
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 (
|
|
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 =
|
|
1799
|
-
const manualHtml =
|
|
1800
|
-
if (
|
|
1801
|
-
const labelPath =
|
|
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 =
|
|
1816
|
-
|
|
1817
|
-
const indexPath =
|
|
1818
|
-
const existing =
|
|
1819
|
-
|
|
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
|
|
1832
|
-
import
|
|
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 =
|
|
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 &&
|
|
1871
|
-
${
|
|
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
|
|
1885
|
-
import
|
|
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 (!
|
|
2019
|
-
const nodeDirs =
|
|
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 =
|
|
2023
|
-
const files =
|
|
2208
|
+
const nodePath = path9.join(baseDir, nodeType);
|
|
2209
|
+
const files = fs10.readdirSync(nodePath);
|
|
2024
2210
|
for (const file of files) {
|
|
2025
|
-
const ext =
|
|
2211
|
+
const ext = path9.extname(file);
|
|
2026
2212
|
if (!fileExtensions.includes(ext)) continue;
|
|
2027
|
-
const lang =
|
|
2028
|
-
const filePath =
|
|
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 =
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
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 =
|
|
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(
|
|
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
|
|
2129
|
-
import
|
|
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 (!
|
|
2161
|
-
return
|
|
2162
|
-
if (
|
|
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 =
|
|
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 =
|
|
2180
|
-
const cjsEntryPath =
|
|
2365
|
+
const esmEntryPath = path10.resolve(serverOutDir, "index.mjs");
|
|
2366
|
+
const cjsEntryPath = path10.resolve(serverOutDir, "index.js");
|
|
2181
2367
|
let packageFn;
|
|
2182
|
-
if (
|
|
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 (
|
|
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 =
|
|
2228
|
-
if (
|
|
2229
|
-
|
|
2413
|
+
const nodesCache = path10.resolve(cacheDir, "nodes");
|
|
2414
|
+
if (fs11.existsSync(nodesCache)) {
|
|
2415
|
+
fs11.rmSync(nodesCache, { recursive: true });
|
|
2230
2416
|
}
|
|
2231
|
-
|
|
2417
|
+
fs11.mkdirSync(nodesCache, { recursive: true });
|
|
2232
2418
|
for (const type of _nodeTypes) {
|
|
2233
|
-
const userTsPath = nodesDir ?
|
|
2234
|
-
if (userTsPath &&
|
|
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
|
-
|
|
2430
|
+
fs11.writeFileSync(path10.resolve(nodesCache, `${type}.ts`), content);
|
|
2245
2431
|
}
|
|
2246
2432
|
const entryContent = generateEntryCode("");
|
|
2247
|
-
|
|
2433
|
+
fs11.mkdirSync(path10.dirname(path10.resolve(cacheDir, "index.ts")), {
|
|
2248
2434
|
recursive: true
|
|
2249
2435
|
});
|
|
2250
|
-
|
|
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 &&
|
|
2459
|
+
if (componentsDir && fs11.existsSync(componentsDir)) {
|
|
2274
2460
|
const formImports = [];
|
|
2275
2461
|
const formEntries = [];
|
|
2276
2462
|
for (const type of _nodeTypes) {
|
|
2277
|
-
const componentPath =
|
|
2278
|
-
if (
|
|
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 =
|
|
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 ?
|
|
2298
|
-
const tsPath = userTsPath &&
|
|
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
|
|
2317
|
-
import
|
|
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 (!
|
|
2327
|
-
|
|
2328
|
-
const stat =
|
|
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 =
|
|
2516
|
+
const files = fs12.readdirSync(src);
|
|
2331
2517
|
for (const file of files) {
|
|
2332
|
-
const srcFile =
|
|
2333
|
-
const destFile =
|
|
2334
|
-
const fileStat =
|
|
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
|
-
|
|
2522
|
+
fs12.cpSync(srcFile, destFile, { recursive: true });
|
|
2337
2523
|
} else {
|
|
2338
|
-
|
|
2524
|
+
fs12.copyFileSync(srcFile, destFile);
|
|
2339
2525
|
}
|
|
2340
2526
|
}
|
|
2341
2527
|
} else {
|
|
2342
|
-
|
|
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 =
|
|
2549
|
+
const physicalEntryPath = path12.resolve(srcDir, entry);
|
|
2364
2550
|
let entryPath;
|
|
2365
2551
|
let generatedEntry = false;
|
|
2366
|
-
if (
|
|
2552
|
+
if (fs13.existsSync(physicalEntryPath)) {
|
|
2367
2553
|
entryPath = physicalEntryPath;
|
|
2368
2554
|
} else {
|
|
2369
|
-
const cacheDir =
|
|
2370
|
-
const cachedEntryPath =
|
|
2371
|
-
if (!
|
|
2372
|
-
|
|
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
|
-
|
|
2560
|
+
fs13.writeFileSync(cachedEntryPath, "// auto-generated entry\n");
|
|
2375
2561
|
entryPath = cachedEntryPath;
|
|
2376
2562
|
generatedEntry = true;
|
|
2377
2563
|
}
|
|
2378
|
-
const iconsDir =
|
|
2379
|
-
staticDirs.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
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
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 ?
|
|
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 =
|
|
2586
|
+
const localesOutDir = path12.join(buildContext.outDir, "locales");
|
|
2401
2587
|
plugins.push(
|
|
2402
2588
|
localesGenerator({
|
|
2403
2589
|
outDir: localesOutDir,
|
|
2404
|
-
docsDir:
|
|
2405
|
-
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:
|
|
2413
|
-
labelsDir:
|
|
2598
|
+
docsDir: path12.resolve(docsDir),
|
|
2599
|
+
labelsDir: path12.resolve(labelsDir)
|
|
2414
2600
|
})
|
|
2415
2601
|
);
|
|
2416
2602
|
}
|
|
2417
2603
|
const copyTargets = [];
|
|
2418
|
-
const publicDir =
|
|
2419
|
-
staticDirs.public ??
|
|
2604
|
+
const publicDir = path12.resolve(
|
|
2605
|
+
staticDirs.public ?? path12.join(srcDir, "public")
|
|
2420
2606
|
);
|
|
2421
|
-
if (
|
|
2607
|
+
if (fs13.existsSync(publicDir)) {
|
|
2422
2608
|
copyTargets.push({
|
|
2423
2609
|
src: publicDir,
|
|
2424
|
-
dest:
|
|
2610
|
+
dest: path12.join(buildContext.outDir, "resources")
|
|
2425
2611
|
});
|
|
2426
2612
|
}
|
|
2427
|
-
if (
|
|
2613
|
+
if (fs13.existsSync(iconsDir)) {
|
|
2428
2614
|
copyTargets.push({
|
|
2429
2615
|
src: iconsDir,
|
|
2430
|
-
dest:
|
|
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:
|
|
2644
|
+
publicDir: path12.resolve(srcDir, "public"),
|
|
2459
2645
|
resolve: {
|
|
2460
2646
|
alias: {
|
|
2461
|
-
"@":
|
|
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 (
|
|
2516
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
2874
|
+
const serverSrcDir = path13.resolve(
|
|
2676
2875
|
serverBuildOptions.srcDir ?? "./server"
|
|
2677
2876
|
);
|
|
2678
|
-
const clientSrcDir =
|
|
2877
|
+
const clientSrcDir = path13.resolve(
|
|
2679
2878
|
clientBuildOptions.srcDir ?? "./client"
|
|
2680
2879
|
);
|
|
2681
|
-
const localesDocsDir =
|
|
2880
|
+
const localesDocsDir = path13.resolve(
|
|
2682
2881
|
clientBuildOptions.locales?.docsDir ?? "./locales/docs"
|
|
2683
2882
|
);
|
|
2684
|
-
const localesLabelsDir =
|
|
2883
|
+
const localesLabelsDir = path13.resolve(
|
|
2685
2884
|
clientBuildOptions.locales?.labelsDir ?? "./locales/labels"
|
|
2686
2885
|
);
|
|
2687
|
-
const iconsDir =
|
|
2688
|
-
clientBuildOptions.staticDirs?.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
|
-
() =>
|
|
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}: ${
|
|
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
|
|
2787
|
-
const {
|
|
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
|
-
|
|
2990
|
+
build3.client
|
|
2791
2991
|
);
|
|
2792
2992
|
const serverBuildOptions = mergeOptions(
|
|
2793
2993
|
DEFAULT_SERVER_BUILD_OPTIONS,
|
|
2794
|
-
|
|
2994
|
+
build3.server
|
|
2795
2995
|
);
|
|
2796
2996
|
const nodeRedLauncherOptions = mergeOptions(
|
|
2797
2997
|
DEFAULT_NODE_RED_LAUNCHER_OPTIONS,
|
|
2798
|
-
|
|
2998
|
+
server.nodeRed
|
|
2799
2999
|
);
|
|
2800
|
-
const extraFilesCopyTargets =
|
|
2801
|
-
const resolvedOutDir =
|
|
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
|
-
|
|
3030
|
+
nrg
|
|
2829
3031
|
};
|