@bonsae/nrg 0.1.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/README.md +130 -0
- package/build/server/index.cjs +910 -0
- package/build/server/resources/nrg-client.js +6530 -0
- package/build/server/resources/vue.esm-browser.prod.js +13 -0
- package/build/vite/index.js +1893 -0
- package/build/vite/utils.js +60 -0
- package/package.json +110 -0
- package/src/core/client/api/index.ts +17 -0
- package/src/core/client/app.vue +201 -0
- package/src/core/client/components/node-red-config-input.vue +57 -0
- package/src/core/client/components/node-red-editor-input.vue +283 -0
- package/src/core/client/components/node-red-input.vue +71 -0
- package/src/core/client/components/node-red-json-schema-form.vue +369 -0
- package/src/core/client/components/node-red-select-input.vue +86 -0
- package/src/core/client/components/node-red-typed-input.vue +130 -0
- package/src/core/client/components.d.ts +18 -0
- package/src/core/client/globals.d.ts +17 -0
- package/src/core/client/index.ts +504 -0
- package/src/core/client/shims-vue.d.ts +5 -0
- package/src/core/client/tsconfig.json +18 -0
- package/src/core/client/virtual.d.ts +5 -0
- package/src/core/constants.ts +18 -0
- package/src/core/server/index.ts +209 -0
- package/src/core/server/nodes/config-node.ts +67 -0
- package/src/core/server/nodes/index.ts +4 -0
- package/src/core/server/nodes/io-node.ts +178 -0
- package/src/core/server/nodes/node.ts +255 -0
- package/src/core/server/nodes/types/config-node.ts +28 -0
- package/src/core/server/nodes/types/index.ts +3 -0
- package/src/core/server/nodes/types/io-node.ts +37 -0
- package/src/core/server/nodes/types/node.ts +41 -0
- package/src/core/server/nodes/utils.ts +83 -0
- package/src/core/server/schemas/base.ts +66 -0
- package/src/core/server/schemas/index.ts +3 -0
- package/src/core/server/schemas/type.ts +95 -0
- package/src/core/server/schemas/types/index.ts +73 -0
- package/src/core/server/tsconfig.json +17 -0
- package/src/core/server/types/index.ts +73 -0
- package/src/core/server/utils.ts +56 -0
- package/src/core/server/validator.ts +32 -0
- package/src/core/validator.ts +222 -0
- package/src/tsconfig/base.json +23 -0
- package/src/tsconfig/client.json +11 -0
- package/src/tsconfig/server.json +6 -0
- package/src/vite/async-utils.ts +61 -0
- package/src/vite/client/build.ts +223 -0
- package/src/vite/client/index.ts +1 -0
- package/src/vite/client/plugins/html-generator.ts +75 -0
- package/src/vite/client/plugins/index.ts +5 -0
- package/src/vite/client/plugins/locales-generator.ts +126 -0
- package/src/vite/client/plugins/minifier.ts +22 -0
- package/src/vite/client/plugins/node-definitions-inliner.ts +224 -0
- package/src/vite/client/plugins/static-copy.ts +43 -0
- package/src/vite/defaults.ts +77 -0
- package/src/vite/errors.ts +37 -0
- package/src/vite/index.ts +3 -0
- package/src/vite/logger.ts +94 -0
- package/src/vite/node-red-launcher.ts +344 -0
- package/src/vite/plugin.ts +61 -0
- package/src/vite/plugins/build.ts +73 -0
- package/src/vite/plugins/index.ts +2 -0
- package/src/vite/plugins/server.ts +267 -0
- package/src/vite/server/build.ts +124 -0
- package/src/vite/server/index.ts +1 -0
- package/src/vite/server/plugins/index.ts +3 -0
- package/src/vite/server/plugins/output-wrapper.ts +109 -0
- package/src/vite/server/plugins/package-json-generator.ts +203 -0
- package/src/vite/server/plugins/type-generator.ts +285 -0
- package/src/vite/types.ts +369 -0
- package/src/vite/utils.ts +103 -0
|
@@ -0,0 +1,1893 @@
|
|
|
1
|
+
// src/vite/plugin.ts
|
|
2
|
+
import path13 from "path";
|
|
3
|
+
|
|
4
|
+
// src/vite/defaults.ts
|
|
5
|
+
var DEFAULT_OUTPUT_DIR = "./dist";
|
|
6
|
+
var DEFAULT_CLIENT_BUILD_OPTIONS = {
|
|
7
|
+
srcDir: "./src/client",
|
|
8
|
+
entry: "index.ts",
|
|
9
|
+
nodesSubdir: "nodes",
|
|
10
|
+
name: "NodeRedNodes",
|
|
11
|
+
format: "es",
|
|
12
|
+
licensePath: "./LICENSE",
|
|
13
|
+
external: ["jquery", "node-red", "vue", "@bonsae/nrg/client"],
|
|
14
|
+
globals: {
|
|
15
|
+
jquery: "$",
|
|
16
|
+
"node-red": "RED",
|
|
17
|
+
vue: "Vue"
|
|
18
|
+
},
|
|
19
|
+
locales: {
|
|
20
|
+
docsDir: "./src/locales/docs",
|
|
21
|
+
labelsDir: "./src/locales/labels",
|
|
22
|
+
languages: [
|
|
23
|
+
"en-US",
|
|
24
|
+
"de",
|
|
25
|
+
"es-ES",
|
|
26
|
+
"fr",
|
|
27
|
+
"ko",
|
|
28
|
+
"pt-BR",
|
|
29
|
+
"ru",
|
|
30
|
+
"ja",
|
|
31
|
+
"zh-CN",
|
|
32
|
+
"zh-TW"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
staticDirs: {
|
|
36
|
+
icons: "./src/icons",
|
|
37
|
+
public: "./src/client/public"
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var DEFAULT_SERVER_BUILD_OPTIONS = {
|
|
41
|
+
srcDir: "./src/server",
|
|
42
|
+
entry: "index.ts",
|
|
43
|
+
format: "esm",
|
|
44
|
+
external: [],
|
|
45
|
+
bundled: [],
|
|
46
|
+
types: true,
|
|
47
|
+
nodeTarget: "node22"
|
|
48
|
+
};
|
|
49
|
+
var DEFAULT_NODE_RED_LAUNCHER_OPTIONS = {
|
|
50
|
+
runtime: {
|
|
51
|
+
port: 1880,
|
|
52
|
+
settingsFilepath: "./node-red.settings.ts",
|
|
53
|
+
version: "latest"
|
|
54
|
+
},
|
|
55
|
+
restartDelay: 1e3,
|
|
56
|
+
args: []
|
|
57
|
+
};
|
|
58
|
+
var DEFAULT_EXTRA_FILES_COPY_TARGETS = [
|
|
59
|
+
{ src: "./LICENSE", dest: "LICENSE" },
|
|
60
|
+
{ src: "./README.md", dest: "README.md" },
|
|
61
|
+
{ src: "./src/examples", dest: "examples" }
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
// src/vite/utils.ts
|
|
65
|
+
import fs from "fs";
|
|
66
|
+
import path from "path";
|
|
67
|
+
function cleanDir(dir) {
|
|
68
|
+
if (fs.existsSync(dir)) {
|
|
69
|
+
fs.rmSync(dir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
function copyFiles(targets, outDir) {
|
|
74
|
+
for (const { src, dest } of targets) {
|
|
75
|
+
const srcPath = path.resolve(src);
|
|
76
|
+
const destPath = path.join(outDir, dest);
|
|
77
|
+
if (!fs.existsSync(srcPath)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const stat = fs.statSync(srcPath);
|
|
81
|
+
if (stat.isDirectory()) {
|
|
82
|
+
fs.cpSync(srcPath, destPath, { recursive: true });
|
|
83
|
+
} else {
|
|
84
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
85
|
+
fs.copyFileSync(srcPath, destPath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function getPackageName() {
|
|
90
|
+
const pkgPath = path.resolve("./package.json");
|
|
91
|
+
if (fs.existsSync(pkgPath)) {
|
|
92
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
93
|
+
return pkg.name;
|
|
94
|
+
}
|
|
95
|
+
return "node-red-nodes";
|
|
96
|
+
}
|
|
97
|
+
function mergeOptions(defaults, overrides) {
|
|
98
|
+
if (!overrides) return { ...defaults };
|
|
99
|
+
const result = { ...defaults };
|
|
100
|
+
for (const key of Object.keys(overrides)) {
|
|
101
|
+
const overrideVal = overrides[key];
|
|
102
|
+
const defaultVal = defaults[key];
|
|
103
|
+
if (overrideVal !== void 0 && !Array.isArray(overrideVal) && !Array.isArray(defaultVal) && typeof overrideVal === "object" && typeof defaultVal === "object" && overrideVal !== null && defaultVal !== null) {
|
|
104
|
+
result[key] = mergeOptions(
|
|
105
|
+
defaultVal,
|
|
106
|
+
overrideVal
|
|
107
|
+
);
|
|
108
|
+
} else if (overrideVal !== void 0) {
|
|
109
|
+
result[key] = overrideVal;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
function defineRuntimeSettings(settings) {
|
|
115
|
+
return settings;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/vite/node-red-launcher.ts
|
|
119
|
+
import { spawn } from "child_process";
|
|
120
|
+
import getPort from "get-port";
|
|
121
|
+
import detect from "detect-port";
|
|
122
|
+
import { builtinModules } from "module";
|
|
123
|
+
import treeKill2 from "tree-kill";
|
|
124
|
+
import fs2 from "fs";
|
|
125
|
+
import os from "os";
|
|
126
|
+
import path2 from "path";
|
|
127
|
+
import { build as esbuild } from "esbuild";
|
|
128
|
+
|
|
129
|
+
// src/vite/async-utils.ts
|
|
130
|
+
function debounce(fn, delay) {
|
|
131
|
+
let timeout = null;
|
|
132
|
+
return (...args) => {
|
|
133
|
+
if (timeout) clearTimeout(timeout);
|
|
134
|
+
timeout = setTimeout(() => fn(...args), delay);
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function withTimeout(promise, ms, fallback) {
|
|
138
|
+
return new Promise((resolve, reject) => {
|
|
139
|
+
const timeout = setTimeout(() => {
|
|
140
|
+
if (fallback !== void 0) {
|
|
141
|
+
resolve(fallback);
|
|
142
|
+
} else {
|
|
143
|
+
reject(new Error(`Timeout after ${ms}ms`));
|
|
144
|
+
}
|
|
145
|
+
}, ms);
|
|
146
|
+
promise.then((result) => {
|
|
147
|
+
clearTimeout(timeout);
|
|
148
|
+
resolve(result);
|
|
149
|
+
}).catch((error) => {
|
|
150
|
+
clearTimeout(timeout);
|
|
151
|
+
reject(error);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async function retry(fn, options = {}) {
|
|
156
|
+
const { attempts = 3, delay = 1e3 } = options;
|
|
157
|
+
let lastError;
|
|
158
|
+
for (let i = 0; i < attempts; i++) {
|
|
159
|
+
try {
|
|
160
|
+
return await fn();
|
|
161
|
+
} catch (error) {
|
|
162
|
+
lastError = error;
|
|
163
|
+
if (i < attempts - 1) {
|
|
164
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
throw lastError;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/vite/errors.ts
|
|
172
|
+
var PluginError = class extends Error {
|
|
173
|
+
constructor(message, code, cause) {
|
|
174
|
+
super(message);
|
|
175
|
+
this.code = code;
|
|
176
|
+
this.cause = cause;
|
|
177
|
+
this.name = "PluginError";
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
var NodeRedStartError = class extends PluginError {
|
|
181
|
+
constructor(cause) {
|
|
182
|
+
super("Failed to start Node-RED", "NODE_RED_START_FAILED", cause);
|
|
183
|
+
this.name = "NodeRedStartError";
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
var BuildError = class extends PluginError {
|
|
187
|
+
constructor(phase, cause) {
|
|
188
|
+
super(
|
|
189
|
+
`Failed to build ${phase}`,
|
|
190
|
+
`BUILD_${phase.toUpperCase()}_FAILED`,
|
|
191
|
+
cause
|
|
192
|
+
);
|
|
193
|
+
this.name = "BuildError";
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// src/vite/logger.ts
|
|
198
|
+
import * as clackLogger from "@clack/prompts";
|
|
199
|
+
var color = {
|
|
200
|
+
dim: (s) => `\x1B[2m${s}\x1B[0m`,
|
|
201
|
+
cyan: (s) => `\x1B[36m${s}\x1B[0m`
|
|
202
|
+
};
|
|
203
|
+
var Logger = class _Logger {
|
|
204
|
+
name;
|
|
205
|
+
prefix;
|
|
206
|
+
spinner = clackLogger.spinner();
|
|
207
|
+
constructor(options) {
|
|
208
|
+
this.name = options.name;
|
|
209
|
+
this.prefix = options.prefix;
|
|
210
|
+
}
|
|
211
|
+
format(message) {
|
|
212
|
+
return this.prefix ? `[${this.prefix}] ${message}` : message;
|
|
213
|
+
}
|
|
214
|
+
intro(message) {
|
|
215
|
+
clackLogger.intro(message ?? this.name);
|
|
216
|
+
}
|
|
217
|
+
outro(message) {
|
|
218
|
+
clackLogger.outro(this.format(message));
|
|
219
|
+
}
|
|
220
|
+
step(message) {
|
|
221
|
+
clackLogger.log.step(this.format(message));
|
|
222
|
+
}
|
|
223
|
+
success(message) {
|
|
224
|
+
clackLogger.log.success(this.format(message));
|
|
225
|
+
}
|
|
226
|
+
warn(message) {
|
|
227
|
+
clackLogger.log.warn(this.format(message));
|
|
228
|
+
}
|
|
229
|
+
error(message, cause) {
|
|
230
|
+
clackLogger.log.error(this.format(message));
|
|
231
|
+
if (cause) {
|
|
232
|
+
console.error(cause);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
info(message) {
|
|
236
|
+
clackLogger.log.info(this.format(message));
|
|
237
|
+
}
|
|
238
|
+
message(message) {
|
|
239
|
+
clackLogger.log.message(this.format(message));
|
|
240
|
+
}
|
|
241
|
+
raw(message) {
|
|
242
|
+
const prefix = this.prefix ? color.dim(`[${this.prefix}]`) : "";
|
|
243
|
+
console.log(`\u2502 ${prefix} ${message}`);
|
|
244
|
+
}
|
|
245
|
+
startGroup(title) {
|
|
246
|
+
if (title) {
|
|
247
|
+
console.log(`\u2502`);
|
|
248
|
+
console.log(`\u251C\u2500 ${color.cyan(title)}`);
|
|
249
|
+
}
|
|
250
|
+
console.log(`\u2502`);
|
|
251
|
+
}
|
|
252
|
+
endGroup(message) {
|
|
253
|
+
console.log(`\u2502`);
|
|
254
|
+
console.log(`\u2514\u2500 ${message ?? "Done"}`);
|
|
255
|
+
console.log();
|
|
256
|
+
}
|
|
257
|
+
startSpinner(message) {
|
|
258
|
+
this.spinner.start(this.format(message));
|
|
259
|
+
}
|
|
260
|
+
stopSpinner(message) {
|
|
261
|
+
this.spinner.stop(this.format(message));
|
|
262
|
+
}
|
|
263
|
+
child(prefix) {
|
|
264
|
+
const newPrefix = this.prefix ? `${this.prefix}:${prefix}` : prefix;
|
|
265
|
+
return new _Logger({ name: this.name, prefix: newPrefix });
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
var logger = new Logger({ name: "vite-plugin-node-red" });
|
|
269
|
+
|
|
270
|
+
// src/vite/node-red-launcher.ts
|
|
271
|
+
var NodeRedLauncher = class {
|
|
272
|
+
compiledRuntimeSettingsFilepath = null;
|
|
273
|
+
process = null;
|
|
274
|
+
bufferedLogs = [];
|
|
275
|
+
isReady = false;
|
|
276
|
+
port = null;
|
|
277
|
+
outDir;
|
|
278
|
+
options;
|
|
279
|
+
logger;
|
|
280
|
+
constructor(outDir, options) {
|
|
281
|
+
this.outDir = outDir;
|
|
282
|
+
this.options = options;
|
|
283
|
+
this.logger = new Logger({
|
|
284
|
+
name: "vite-plugin-node-red",
|
|
285
|
+
prefix: "node-red"
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
get preferredPort() {
|
|
289
|
+
return this.options.runtime?.port ?? 1880;
|
|
290
|
+
}
|
|
291
|
+
get restartDelay() {
|
|
292
|
+
return this.options.restartDelay ?? 1e3;
|
|
293
|
+
}
|
|
294
|
+
get pid() {
|
|
295
|
+
return this.process?.pid ?? null;
|
|
296
|
+
}
|
|
297
|
+
get nodeRedCommand() {
|
|
298
|
+
const version = this.options.runtime?.version;
|
|
299
|
+
if (version === "latest") {
|
|
300
|
+
return "node-red@latest";
|
|
301
|
+
}
|
|
302
|
+
if (version) {
|
|
303
|
+
return `node-red@${version}`;
|
|
304
|
+
}
|
|
305
|
+
return "node-red";
|
|
306
|
+
}
|
|
307
|
+
findRuntimeSettingsFilepath() {
|
|
308
|
+
const runtimeSettingsFilepath = this.options.runtime.settingsFilepath;
|
|
309
|
+
if (runtimeSettingsFilepath) {
|
|
310
|
+
const resolved2 = path2.resolve(runtimeSettingsFilepath);
|
|
311
|
+
if (fs2.existsSync(resolved2)) {
|
|
312
|
+
return resolved2;
|
|
313
|
+
}
|
|
314
|
+
this.logger.warn(`Settings file not found: ${runtimeSettingsFilepath}`);
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
const resolved = path2.resolve("node-red.settings.ts");
|
|
318
|
+
if (fs2.existsSync(resolved)) {
|
|
319
|
+
return resolved;
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
async compileRuntimeSettingsFile(runtimeSettingsFilepath) {
|
|
324
|
+
const compiledRuntimeSettingsFilepath = path2.join(
|
|
325
|
+
os.tmpdir(),
|
|
326
|
+
`node-red.settings.${process.pid}.cjs`
|
|
327
|
+
);
|
|
328
|
+
const nodeBuiltins2 = [
|
|
329
|
+
...builtinModules,
|
|
330
|
+
...builtinModules.map((m) => `node:${m}`)
|
|
331
|
+
];
|
|
332
|
+
const settingsDir = path2.dirname(runtimeSettingsFilepath).split(path2.sep).join("/");
|
|
333
|
+
const settingsFile = runtimeSettingsFilepath.split(path2.sep).join("/");
|
|
334
|
+
await esbuild({
|
|
335
|
+
entryPoints: [runtimeSettingsFilepath],
|
|
336
|
+
outfile: compiledRuntimeSettingsFilepath,
|
|
337
|
+
format: "cjs",
|
|
338
|
+
platform: "node",
|
|
339
|
+
target: "node18",
|
|
340
|
+
bundle: true,
|
|
341
|
+
define: {
|
|
342
|
+
"import.meta.dirname": JSON.stringify(settingsDir),
|
|
343
|
+
"import.meta.filename": JSON.stringify(settingsFile),
|
|
344
|
+
"import.meta.url": JSON.stringify(`file://${settingsFile}`)
|
|
345
|
+
},
|
|
346
|
+
external: [...nodeBuiltins2, "node-red", "@node-red/*"]
|
|
347
|
+
});
|
|
348
|
+
this.compiledRuntimeSettingsFilepath = compiledRuntimeSettingsFilepath;
|
|
349
|
+
return compiledRuntimeSettingsFilepath;
|
|
350
|
+
}
|
|
351
|
+
async generateRuntimeSettingsFile() {
|
|
352
|
+
const userRuntimeSettingsFilepath = this.findRuntimeSettingsFilepath();
|
|
353
|
+
let compiledRuntimeSettingsFilepath = null;
|
|
354
|
+
if (userRuntimeSettingsFilepath) {
|
|
355
|
+
compiledRuntimeSettingsFilepath = await this.compileRuntimeSettingsFile(
|
|
356
|
+
userRuntimeSettingsFilepath
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
const outDir = path2.resolve(this.outDir).split(path2.sep).join("/");
|
|
360
|
+
const cwd = process.cwd().split(path2.sep).join("/");
|
|
361
|
+
const userDir = path2.resolve(cwd, ".node-red");
|
|
362
|
+
const finalRuntimeSettingsFile = compiledRuntimeSettingsFilepath ? `
|
|
363
|
+
const compiledRuntimeSettings = require("${compiledRuntimeSettingsFilepath.split(path2.sep).join("/")}");
|
|
364
|
+
const settings = compiledRuntimeSettings.default || compiledRuntimeSettings;
|
|
365
|
+
settings.uiPort = ${this.port};
|
|
366
|
+
if(!settings.userDir){
|
|
367
|
+
settings.userDir = "${userDir}";
|
|
368
|
+
}
|
|
369
|
+
settings.nodesDir = settings.nodesDir || [];
|
|
370
|
+
if (!settings.nodesDir.includes("${outDir}")) {
|
|
371
|
+
settings.nodesDir.push("${outDir}");
|
|
372
|
+
}
|
|
373
|
+
if(!settings.flowFile){
|
|
374
|
+
settings.flowFile = "flows.json";
|
|
375
|
+
}
|
|
376
|
+
module.exports = settings;
|
|
377
|
+
` : `
|
|
378
|
+
const settings = {
|
|
379
|
+
uiPort: ${this.port},
|
|
380
|
+
userDir: "${userDir}",
|
|
381
|
+
flowFile: "flows.json",
|
|
382
|
+
nodesDir: ["${outDir}"],
|
|
383
|
+
};
|
|
384
|
+
module.exports = settings;
|
|
385
|
+
`;
|
|
386
|
+
const finalRuntimeSettingsFilepath = path2.join(
|
|
387
|
+
os.tmpdir(),
|
|
388
|
+
`node-red-settings-final-${process.pid}.cjs`
|
|
389
|
+
);
|
|
390
|
+
fs2.writeFileSync(finalRuntimeSettingsFilepath, finalRuntimeSettingsFile);
|
|
391
|
+
this.compiledRuntimeSettingsFilepath = finalRuntimeSettingsFilepath;
|
|
392
|
+
return finalRuntimeSettingsFilepath;
|
|
393
|
+
}
|
|
394
|
+
log(line) {
|
|
395
|
+
if (line.includes("Server now running at")) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
this.logger.raw(line);
|
|
399
|
+
}
|
|
400
|
+
async start() {
|
|
401
|
+
this.port = await getPort({ port: this.preferredPort });
|
|
402
|
+
const startProcess = () => {
|
|
403
|
+
return new Promise(async (resolve, reject) => {
|
|
404
|
+
try {
|
|
405
|
+
const settingsPath = await this.generateRuntimeSettingsFile();
|
|
406
|
+
const args = this.options.args ?? [];
|
|
407
|
+
this.bufferedLogs = [];
|
|
408
|
+
this.isReady = false;
|
|
409
|
+
this.process = spawn(
|
|
410
|
+
"npx",
|
|
411
|
+
[this.nodeRedCommand, "-s", settingsPath, ...args],
|
|
412
|
+
{
|
|
413
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
414
|
+
shell: true
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
this.process.stdout?.on("data", (data) => {
|
|
418
|
+
const lines = data.toString().split("\n").filter(Boolean);
|
|
419
|
+
for (const line of lines) {
|
|
420
|
+
if (this.isReady) {
|
|
421
|
+
this.log(line);
|
|
422
|
+
} else {
|
|
423
|
+
this.bufferedLogs.push(line);
|
|
424
|
+
}
|
|
425
|
+
if (line.includes("Started flows") || line.includes("Server now running")) {
|
|
426
|
+
this.isReady = true;
|
|
427
|
+
resolve();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
this.process.stderr?.on("data", (data) => {
|
|
432
|
+
const lines = data.toString().split("\n").filter(Boolean);
|
|
433
|
+
for (const line of lines) {
|
|
434
|
+
if (this.isReady) {
|
|
435
|
+
this.logger.error(`${line}`);
|
|
436
|
+
} else {
|
|
437
|
+
this.bufferedLogs.push(line);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
this.process.on("error", (error) => {
|
|
442
|
+
reject(new NodeRedStartError(error));
|
|
443
|
+
});
|
|
444
|
+
this.process.on("exit", (code) => {
|
|
445
|
+
if (!this.isReady && code !== 0 && code !== null) {
|
|
446
|
+
reject(
|
|
447
|
+
new NodeRedStartError(
|
|
448
|
+
new Error(`Process exited with code ${code}`)
|
|
449
|
+
)
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
resolve();
|
|
453
|
+
});
|
|
454
|
+
} catch (error) {
|
|
455
|
+
reject(new NodeRedStartError(error));
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
try {
|
|
460
|
+
await retry(startProcess, { attempts: 3, delay: 100 });
|
|
461
|
+
return this.port;
|
|
462
|
+
} catch (error) {
|
|
463
|
+
if (this.process) {
|
|
464
|
+
const pid = this.process.pid;
|
|
465
|
+
if (pid) {
|
|
466
|
+
treeKill2(pid, "SIGKILL");
|
|
467
|
+
}
|
|
468
|
+
this.process = null;
|
|
469
|
+
}
|
|
470
|
+
throw new NodeRedStartError(error);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async stop(skipPortUsageCheck = false) {
|
|
474
|
+
if (!this.process) return;
|
|
475
|
+
const pid = this.process.pid;
|
|
476
|
+
const currentPort = this.port;
|
|
477
|
+
if (!pid) {
|
|
478
|
+
this.process = null;
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const stopProcess = new Promise((resolve) => {
|
|
482
|
+
this.process.once("exit", () => {
|
|
483
|
+
this.process = null;
|
|
484
|
+
resolve();
|
|
485
|
+
});
|
|
486
|
+
treeKill2(pid, "SIGTERM", (error) => {
|
|
487
|
+
if (error) {
|
|
488
|
+
try {
|
|
489
|
+
process.kill(pid, "SIGTERM");
|
|
490
|
+
} catch {
|
|
491
|
+
this.process = null;
|
|
492
|
+
resolve();
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
try {
|
|
498
|
+
await withTimeout(stopProcess, 1e4);
|
|
499
|
+
} catch {
|
|
500
|
+
this.logger.warn("Graceful shutdown timed out, force killing...");
|
|
501
|
+
await new Promise((resolve) => {
|
|
502
|
+
treeKill2(pid, "SIGKILL", () => {
|
|
503
|
+
this.process = null;
|
|
504
|
+
resolve();
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
if (!skipPortUsageCheck && currentPort) {
|
|
509
|
+
const checkPortUsage = async () => {
|
|
510
|
+
const availablePort = await detect(currentPort);
|
|
511
|
+
if (availablePort !== currentPort) {
|
|
512
|
+
throw new Error("Port still in use");
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
try {
|
|
516
|
+
await retry(checkPortUsage, { attempts: 5, delay: 100 });
|
|
517
|
+
} catch {
|
|
518
|
+
this.logger.warn(
|
|
519
|
+
`Port ${currentPort} may still be in use. If restart fails, try again in a few seconds.`
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
this.port = null;
|
|
524
|
+
}
|
|
525
|
+
flushLogs() {
|
|
526
|
+
for (const line of this.bufferedLogs) {
|
|
527
|
+
this.log(line);
|
|
528
|
+
}
|
|
529
|
+
this.bufferedLogs = [];
|
|
530
|
+
}
|
|
531
|
+
cleanup() {
|
|
532
|
+
if (this.compiledRuntimeSettingsFilepath && fs2.existsSync(this.compiledRuntimeSettingsFilepath)) {
|
|
533
|
+
fs2.unlinkSync(this.compiledRuntimeSettingsFilepath);
|
|
534
|
+
this.compiledRuntimeSettingsFilepath = null;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// src/vite/plugins/server.ts
|
|
540
|
+
import chokidar from "chokidar";
|
|
541
|
+
import path11 from "path";
|
|
542
|
+
|
|
543
|
+
// src/vite/server/build.ts
|
|
544
|
+
import { build as viteBuild } from "vite";
|
|
545
|
+
import fs5 from "fs";
|
|
546
|
+
import path5 from "path";
|
|
547
|
+
|
|
548
|
+
// src/vite/server/plugins/type-generator.ts
|
|
549
|
+
import dts from "vite-plugin-dts";
|
|
550
|
+
import fs3 from "fs";
|
|
551
|
+
import path3 from "path";
|
|
552
|
+
import ts from "typescript";
|
|
553
|
+
function collectTsFiles(dir) {
|
|
554
|
+
if (!fs3.existsSync(dir)) return [];
|
|
555
|
+
return fs3.readdirSync(dir, { withFileTypes: true }).flatMap((dirent) => {
|
|
556
|
+
const full = path3.join(dir, dirent.name);
|
|
557
|
+
if (dirent.isDirectory()) return collectTsFiles(full);
|
|
558
|
+
if (dirent.isFile() && dirent.name.endsWith(".ts") && !dirent.name.endsWith(".d.ts"))
|
|
559
|
+
return [full];
|
|
560
|
+
return [];
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
function toPascalCase(name) {
|
|
564
|
+
return name.split(/[-_]/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
565
|
+
}
|
|
566
|
+
var BASE_CLASS_SLOTS = {
|
|
567
|
+
IONode: ["Config", "Credentials", "Input", "Output", "Settings"],
|
|
568
|
+
ConfigNode: ["Config", "Credentials", "Settings"]
|
|
569
|
+
};
|
|
570
|
+
function getNodeTypeExports(filePath) {
|
|
571
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
572
|
+
const source = ts.createSourceFile(
|
|
573
|
+
filePath,
|
|
574
|
+
content,
|
|
575
|
+
ts.ScriptTarget.ESNext,
|
|
576
|
+
/* setParentNodes */
|
|
577
|
+
true
|
|
578
|
+
);
|
|
579
|
+
const hasModifier = (node, kind) => (node.modifiers ?? []).some((m) => m.kind === kind);
|
|
580
|
+
const exportedTypeNames = /* @__PURE__ */ new Set();
|
|
581
|
+
for (const stmt of source.statements) {
|
|
582
|
+
if (ts.isTypeAliasDeclaration(stmt) && hasModifier(stmt, ts.SyntaxKind.ExportKeyword)) {
|
|
583
|
+
exportedTypeNames.add(stmt.name.text);
|
|
584
|
+
}
|
|
585
|
+
if (ts.isInterfaceDeclaration(stmt) && hasModifier(stmt, ts.SyntaxKind.ExportKeyword)) {
|
|
586
|
+
exportedTypeNames.add(stmt.name.text);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
if (exportedTypeNames.size === 0) return [];
|
|
590
|
+
let baseClassName = "";
|
|
591
|
+
const genericArgNames = [];
|
|
592
|
+
for (const stmt of source.statements) {
|
|
593
|
+
if (!ts.isClassDeclaration(stmt) || !hasModifier(stmt, ts.SyntaxKind.DefaultKeyword)) {
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
for (const clause of stmt.heritageClauses ?? []) {
|
|
597
|
+
if (clause.token !== ts.SyntaxKind.ExtendsKeyword) continue;
|
|
598
|
+
for (const type of clause.types) {
|
|
599
|
+
if (ts.isIdentifier(type.expression)) {
|
|
600
|
+
baseClassName = type.expression.text;
|
|
601
|
+
}
|
|
602
|
+
for (const arg of type.typeArguments ?? []) {
|
|
603
|
+
if (ts.isTypeReferenceNode(arg) && ts.isIdentifier(arg.typeName)) {
|
|
604
|
+
genericArgNames.push(arg.typeName.text);
|
|
605
|
+
} else {
|
|
606
|
+
genericArgNames.push("");
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
const slots = BASE_CLASS_SLOTS[baseClassName];
|
|
614
|
+
if (!slots) return [];
|
|
615
|
+
const result = [];
|
|
616
|
+
for (let i = 0; i < genericArgNames.length; i++) {
|
|
617
|
+
const localName = genericArgNames[i];
|
|
618
|
+
const semanticName = slots[i];
|
|
619
|
+
if (localName && semanticName && exportedTypeNames.has(localName)) {
|
|
620
|
+
result.push({ localName, semanticName });
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return result;
|
|
624
|
+
}
|
|
625
|
+
function buildNodeReexports(srcDir, entryFile) {
|
|
626
|
+
const nodesDir = path3.join(srcDir, "nodes");
|
|
627
|
+
const nodeFiles = collectTsFiles(nodesDir);
|
|
628
|
+
return nodeFiles.map((file) => {
|
|
629
|
+
const rel = path3.relative(path3.dirname(entryFile), file).replace(/\\/g, "/");
|
|
630
|
+
const relPath = rel.startsWith(".") ? rel : `./${rel}`;
|
|
631
|
+
const specifier = relPath.replace(/\.ts$/, "");
|
|
632
|
+
const ns = toPascalCase(path3.basename(file, ".ts"));
|
|
633
|
+
const lines = [`export { default as ${ns} } from "${specifier}";`];
|
|
634
|
+
const typePairs = getNodeTypeExports(file);
|
|
635
|
+
if (typePairs.length > 0) {
|
|
636
|
+
const prefixed = typePairs.map(
|
|
637
|
+
({ localName, semanticName }) => `${localName} as ${ns}${semanticName}`
|
|
638
|
+
).join(", ");
|
|
639
|
+
lines.push(`export type { ${prefixed} } from "${specifier}";`);
|
|
640
|
+
}
|
|
641
|
+
return lines.join("\n");
|
|
642
|
+
}).join("\n");
|
|
643
|
+
}
|
|
644
|
+
function typeGenerator(options) {
|
|
645
|
+
const { srcDir, outDir, entryFiles } = options;
|
|
646
|
+
const serverTsconfig = path3.resolve(srcDir, "tsconfig.json");
|
|
647
|
+
const rootTsconfig = path3.resolve("tsconfig.json");
|
|
648
|
+
const userTsconfig = fs3.existsSync(serverTsconfig) ? serverTsconfig : rootTsconfig;
|
|
649
|
+
const clientDir = path3.relative(
|
|
650
|
+
process.cwd(),
|
|
651
|
+
path3.join(path3.dirname(srcDir), "client")
|
|
652
|
+
);
|
|
653
|
+
const augmentedContents = /* @__PURE__ */ new Map();
|
|
654
|
+
let origReadFile = null;
|
|
655
|
+
const nodeTypeExporter = {
|
|
656
|
+
name: "vite-plugin-node-red:server:type-exporter",
|
|
657
|
+
enforce: "pre",
|
|
658
|
+
buildStart() {
|
|
659
|
+
augmentedContents.clear();
|
|
660
|
+
for (const entryFile of entryFiles) {
|
|
661
|
+
const reexports = buildNodeReexports(srcDir, entryFile);
|
|
662
|
+
if (!reexports) continue;
|
|
663
|
+
const original = fs3.readFileSync(entryFile, "utf-8");
|
|
664
|
+
augmentedContents.set(
|
|
665
|
+
path3.resolve(entryFile),
|
|
666
|
+
`${original}
|
|
667
|
+
${reexports}
|
|
668
|
+
`
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
if (augmentedContents.size === 0) return;
|
|
672
|
+
origReadFile = ts.sys.readFile.bind(ts.sys);
|
|
673
|
+
ts.sys.readFile = (fileName, encoding) => {
|
|
674
|
+
const resolved = path3.resolve(fileName);
|
|
675
|
+
return augmentedContents.get(resolved) ?? origReadFile(fileName, encoding);
|
|
676
|
+
};
|
|
677
|
+
},
|
|
678
|
+
closeBundle() {
|
|
679
|
+
if (origReadFile) {
|
|
680
|
+
ts.sys.readFile = origReadFile;
|
|
681
|
+
origReadFile = null;
|
|
682
|
+
}
|
|
683
|
+
augmentedContents.clear();
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
return [
|
|
687
|
+
nodeTypeExporter,
|
|
688
|
+
dts({
|
|
689
|
+
rollupTypes: true,
|
|
690
|
+
rollupOptions: {
|
|
691
|
+
messageCallback(message) {
|
|
692
|
+
if (message.messageId === "console-preamble") {
|
|
693
|
+
message.logLevel = "none";
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
},
|
|
697
|
+
outDir,
|
|
698
|
+
...fs3.existsSync(userTsconfig) && { tsconfigPath: userTsconfig },
|
|
699
|
+
compilerOptions: {
|
|
700
|
+
noEmit: false,
|
|
701
|
+
declaration: true,
|
|
702
|
+
emitDeclarationOnly: true,
|
|
703
|
+
noCheck: true
|
|
704
|
+
},
|
|
705
|
+
exclude: [
|
|
706
|
+
`${clientDir}/**`,
|
|
707
|
+
"**/client/**",
|
|
708
|
+
"vite/**",
|
|
709
|
+
"node_modules/**",
|
|
710
|
+
"dist/**"
|
|
711
|
+
]
|
|
712
|
+
})
|
|
713
|
+
].flat();
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// src/vite/server/plugins/output-wrapper.ts
|
|
717
|
+
function cjsWrapper() {
|
|
718
|
+
return {
|
|
719
|
+
name: "vite-plugin-node-red:server:cjs-wrapper",
|
|
720
|
+
renderChunk(code, chunk, outputOptions) {
|
|
721
|
+
if (!chunk.isEntry || outputOptions.format !== "cjs") return null;
|
|
722
|
+
const footer = `(function(){var _exp=module.exports&&module.exports.__esModule?module.exports.default:module.exports;if(_exp&&typeof _exp==="object"&&Array.isArray(_exp.nodes)){var _nrg=require("@bonsae/nrg/server");module.exports=_nrg.registerTypes(_exp.nodes);}else if(typeof _exp==="function"&&Array.isArray(_exp.nodes)){module.exports=_exp;_exp.nodes.forEach(function(cls){if(cls&&cls.type){_exp[cls.type.replace(/(?:^|[-_])(\\w)/g,function(_,c){return c.toUpperCase();})] = cls;}});}})();`;
|
|
723
|
+
return { code: `${code}
|
|
724
|
+
${footer}`, map: null };
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
function esmWrapper() {
|
|
729
|
+
return {
|
|
730
|
+
name: "vite-plugin-node-red:server:esm-wrapper",
|
|
731
|
+
renderChunk(code, chunk, outputOptions) {
|
|
732
|
+
if (!chunk.isEntry || outputOptions.format !== "es") return null;
|
|
733
|
+
const reNamed = /export\s*\{\s*(\w+)\s+as\s+default\s*\}\s*;?/;
|
|
734
|
+
const reDefault = /export\s+default\s+(\w+)\s*;?/;
|
|
735
|
+
let match = code.match(reNamed);
|
|
736
|
+
let varName;
|
|
737
|
+
let matchStr;
|
|
738
|
+
if (match) {
|
|
739
|
+
varName = match[1];
|
|
740
|
+
matchStr = match[0];
|
|
741
|
+
} else {
|
|
742
|
+
match = code.match(reDefault);
|
|
743
|
+
if (match) {
|
|
744
|
+
varName = match[1];
|
|
745
|
+
matchStr = match[0];
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (!varName || !matchStr) return null;
|
|
749
|
+
const header = [
|
|
750
|
+
`import { fileURLToPath as __nrgFileURLToPath } from "url";`,
|
|
751
|
+
`import { dirname as __nrgDirname } from "path";`,
|
|
752
|
+
`var __filename = __nrgFileURLToPath(import.meta.url);`,
|
|
753
|
+
`var __dirname = __nrgDirname(__filename);`,
|
|
754
|
+
`import { registerTypes as __nrgRegisterTypes } from "@bonsae/nrg/server";`,
|
|
755
|
+
``
|
|
756
|
+
].join("\n");
|
|
757
|
+
const replacement = [
|
|
758
|
+
`var __nrgExport = ${varName};`,
|
|
759
|
+
`if (__nrgExport && typeof __nrgExport === "object" && Array.isArray(__nrgExport.nodes)) {`,
|
|
760
|
+
` __nrgExport = __nrgRegisterTypes(__nrgExport.nodes);`,
|
|
761
|
+
`}`,
|
|
762
|
+
`export { __nrgExport as default };`
|
|
763
|
+
].join("\n");
|
|
764
|
+
const newCode = header + code.replace(matchStr, replacement);
|
|
765
|
+
return { code: newCode, map: null };
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/vite/server/plugins/package-json-generator.ts
|
|
771
|
+
import fs4 from "fs";
|
|
772
|
+
import path4 from "path";
|
|
773
|
+
import { builtinModules as builtinModules2 } from "node:module";
|
|
774
|
+
var nodeBuiltins = /* @__PURE__ */ new Set([
|
|
775
|
+
...builtinModules2,
|
|
776
|
+
...builtinModules2.map((m) => `node:${m}`)
|
|
777
|
+
]);
|
|
778
|
+
function buildTypesPath(entryName) {
|
|
779
|
+
return `./${entryName}.d.ts`;
|
|
780
|
+
}
|
|
781
|
+
function buildOutputPath(entryName) {
|
|
782
|
+
return `./${entryName}.js`;
|
|
783
|
+
}
|
|
784
|
+
function buildExportKey(entryName) {
|
|
785
|
+
return entryName === "index" ? "." : `./${entryName}`;
|
|
786
|
+
}
|
|
787
|
+
function buildEsmOutputPath(entryName) {
|
|
788
|
+
return `./${entryName}.mjs`;
|
|
789
|
+
}
|
|
790
|
+
function generateExports(entryNames, format = "cjs") {
|
|
791
|
+
const exports = {};
|
|
792
|
+
for (const name of entryNames) {
|
|
793
|
+
const key = buildExportKey(name);
|
|
794
|
+
if (format === "esm") {
|
|
795
|
+
exports[key] = {
|
|
796
|
+
types: buildTypesPath(name),
|
|
797
|
+
import: buildEsmOutputPath(name),
|
|
798
|
+
require: buildOutputPath(name),
|
|
799
|
+
default: buildOutputPath(name)
|
|
800
|
+
};
|
|
801
|
+
} else {
|
|
802
|
+
exports[key] = {
|
|
803
|
+
types: buildTypesPath(name),
|
|
804
|
+
require: buildOutputPath(name),
|
|
805
|
+
default: buildOutputPath(name)
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return exports;
|
|
810
|
+
}
|
|
811
|
+
function patchExportsWithTypes(existingExports, entryNames) {
|
|
812
|
+
const patched = { ...existingExports };
|
|
813
|
+
for (const [key, value] of Object.entries(patched)) {
|
|
814
|
+
const entryName = key === "." ? "index" : key.replace(/^\.\//, "");
|
|
815
|
+
if (!entryNames.includes(entryName)) continue;
|
|
816
|
+
const typesPath = buildTypesPath(entryName);
|
|
817
|
+
if (typeof value === "string") {
|
|
818
|
+
patched[key] = { types: typesPath, require: value, default: value };
|
|
819
|
+
} else if (typeof value === "object" && value !== null) {
|
|
820
|
+
const condition = value;
|
|
821
|
+
if (!condition.types) {
|
|
822
|
+
patched[key] = { types: typesPath, ...condition };
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return patched;
|
|
827
|
+
}
|
|
828
|
+
function packageJsonGenerator(options) {
|
|
829
|
+
const {
|
|
830
|
+
outDir,
|
|
831
|
+
bundled = [],
|
|
832
|
+
types = false,
|
|
833
|
+
entryNames = [],
|
|
834
|
+
format = "cjs"
|
|
835
|
+
} = options;
|
|
836
|
+
const trackedDependencies = /* @__PURE__ */ new Set();
|
|
837
|
+
return {
|
|
838
|
+
name: "vite-plugin-node-red:server:package-json-generator",
|
|
839
|
+
enforce: "pre",
|
|
840
|
+
buildStart() {
|
|
841
|
+
trackedDependencies.clear();
|
|
842
|
+
},
|
|
843
|
+
resolveId: {
|
|
844
|
+
order: "pre",
|
|
845
|
+
handler(source, importer) {
|
|
846
|
+
if (!importer || source.startsWith(".") || source.startsWith("/")) {
|
|
847
|
+
return null;
|
|
848
|
+
}
|
|
849
|
+
if (nodeBuiltins.has(source)) {
|
|
850
|
+
return { id: source, external: true };
|
|
851
|
+
}
|
|
852
|
+
const packageName = source.startsWith("@") ? source.split("/").slice(0, 2).join("/") : source.split("/")[0];
|
|
853
|
+
if (bundled.includes(packageName)) {
|
|
854
|
+
return null;
|
|
855
|
+
}
|
|
856
|
+
trackedDependencies.add(packageName);
|
|
857
|
+
return { id: source, external: true };
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
closeBundle() {
|
|
861
|
+
const rootPackageJsonPath = path4.resolve("./package.json");
|
|
862
|
+
if (!fs4.existsSync(rootPackageJsonPath)) {
|
|
863
|
+
logger.warn(`package.json not found: ${rootPackageJsonPath}`);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const rootPackageJson = JSON.parse(
|
|
867
|
+
fs4.readFileSync(rootPackageJsonPath, "utf-8")
|
|
868
|
+
);
|
|
869
|
+
const sourceDeps = rootPackageJson.dependencies ?? {};
|
|
870
|
+
const peerDeps = rootPackageJson.peerDependencies ?? {};
|
|
871
|
+
let distDependencies = {};
|
|
872
|
+
for (const dep of trackedDependencies) {
|
|
873
|
+
if (peerDeps[dep]) {
|
|
874
|
+
continue;
|
|
875
|
+
}
|
|
876
|
+
if (sourceDeps[dep]) {
|
|
877
|
+
distDependencies[dep] = sourceDeps[dep];
|
|
878
|
+
} else {
|
|
879
|
+
const dependencyPackageJsonPath = path4.resolve(
|
|
880
|
+
`./node_modules/${dep}/package.json`
|
|
881
|
+
);
|
|
882
|
+
if (fs4.existsSync(dependencyPackageJsonPath)) {
|
|
883
|
+
const dependencyPackageJson = JSON.parse(
|
|
884
|
+
fs4.readFileSync(dependencyPackageJsonPath, "utf-8")
|
|
885
|
+
);
|
|
886
|
+
distDependencies[dep] = `^${dependencyPackageJson.version}`;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if (Object.keys(distDependencies).length === 0) {
|
|
891
|
+
distDependencies = void 0;
|
|
892
|
+
}
|
|
893
|
+
const distPackageJson = {
|
|
894
|
+
...rootPackageJson,
|
|
895
|
+
main: "index.js",
|
|
896
|
+
type: "commonjs",
|
|
897
|
+
devDependencies: void 0,
|
|
898
|
+
scripts: void 0,
|
|
899
|
+
dependencies: distDependencies,
|
|
900
|
+
keywords: [
|
|
901
|
+
.../* @__PURE__ */ new Set([...rootPackageJson.keywords ?? [], "node-red"])
|
|
902
|
+
],
|
|
903
|
+
"node-red": {
|
|
904
|
+
nodes: { nodes: "index.js" }
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
if (types && entryNames.length > 0) {
|
|
908
|
+
const userExports = rootPackageJson.exports;
|
|
909
|
+
if (userExports && Object.keys(userExports).length > 0) {
|
|
910
|
+
distPackageJson.exports = patchExportsWithTypes(
|
|
911
|
+
userExports,
|
|
912
|
+
entryNames
|
|
913
|
+
);
|
|
914
|
+
} else {
|
|
915
|
+
distPackageJson.exports = generateExports(entryNames, format);
|
|
916
|
+
if (entryNames.includes("index")) {
|
|
917
|
+
distPackageJson.types = "index.d.ts";
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (!fs4.existsSync(outDir)) {
|
|
922
|
+
fs4.mkdirSync(outDir, { recursive: true });
|
|
923
|
+
}
|
|
924
|
+
fs4.writeFileSync(
|
|
925
|
+
path4.join(outDir, "package.json"),
|
|
926
|
+
JSON.stringify(distPackageJson, null, 2)
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/vite/server/build.ts
|
|
933
|
+
async function build(serverOpts, buildContext) {
|
|
934
|
+
const {
|
|
935
|
+
srcDir = "./server",
|
|
936
|
+
entry = "index.ts",
|
|
937
|
+
format = "esm",
|
|
938
|
+
bundled = [],
|
|
939
|
+
types = true,
|
|
940
|
+
nodeTarget = "node22",
|
|
941
|
+
plugins: userPlugins = []
|
|
942
|
+
} = serverOpts;
|
|
943
|
+
const entries = Array.isArray(entry) ? entry : [entry];
|
|
944
|
+
const resolvedSrcDir = path5.resolve(srcDir);
|
|
945
|
+
const entryPoints = {};
|
|
946
|
+
for (const entry2 of entries) {
|
|
947
|
+
const entryFilePath = path5.join(resolvedSrcDir, entry2);
|
|
948
|
+
if (fs5.existsSync(entryFilePath)) {
|
|
949
|
+
const fileName = entry2.replace(/\.ts$/, "");
|
|
950
|
+
entryPoints[fileName] = entryFilePath;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (Object.keys(entryPoints).length === 0) {
|
|
954
|
+
logger.warn("No server entry points found");
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
const isEsm = format === "esm";
|
|
958
|
+
const plugins = [
|
|
959
|
+
packageJsonGenerator({
|
|
960
|
+
outDir: buildContext.outDir,
|
|
961
|
+
bundled,
|
|
962
|
+
types: types && !buildContext.isDev,
|
|
963
|
+
entryNames: Object.keys(entryPoints),
|
|
964
|
+
format
|
|
965
|
+
}),
|
|
966
|
+
isEsm ? esmWrapper() : cjsWrapper(),
|
|
967
|
+
...userPlugins
|
|
968
|
+
];
|
|
969
|
+
if (types && !buildContext.isDev) {
|
|
970
|
+
plugins.push(
|
|
971
|
+
...typeGenerator({
|
|
972
|
+
srcDir: resolvedSrcDir,
|
|
973
|
+
outDir: buildContext.outDir,
|
|
974
|
+
entryFiles: Object.values(entryPoints)
|
|
975
|
+
})
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
const config = {
|
|
979
|
+
configFile: false,
|
|
980
|
+
logLevel: "warn",
|
|
981
|
+
plugins,
|
|
982
|
+
build: {
|
|
983
|
+
outDir: buildContext.outDir,
|
|
984
|
+
emptyOutDir: false,
|
|
985
|
+
sourcemap: buildContext.isDev ? "inline" : true,
|
|
986
|
+
minify: !buildContext.isDev,
|
|
987
|
+
lib: {
|
|
988
|
+
entry: entryPoints,
|
|
989
|
+
formats: [isEsm ? "es" : "cjs"]
|
|
990
|
+
},
|
|
991
|
+
rollupOptions: {
|
|
992
|
+
output: {
|
|
993
|
+
entryFileNames: isEsm ? "[name].mjs" : "[name].js",
|
|
994
|
+
exports: isEsm ? void 0 : "auto",
|
|
995
|
+
preserveModules: false
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
},
|
|
999
|
+
esbuild: {
|
|
1000
|
+
platform: "node",
|
|
1001
|
+
target: nodeTarget,
|
|
1002
|
+
keepNames: true
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
try {
|
|
1006
|
+
await viteBuild(config);
|
|
1007
|
+
if (isEsm) {
|
|
1008
|
+
const bridgeCode = `'use strict';
|
|
1009
|
+
// CJS bridge \u2014 auto-generated by @bonsae/nrg/vite
|
|
1010
|
+
// Node-RED uses require() to load packages. This bridge delegates to the ESM bundle.
|
|
1011
|
+
module.exports = function (RED) {
|
|
1012
|
+
(async () => {
|
|
1013
|
+
const mod = await import("./index.mjs");
|
|
1014
|
+
if (typeof mod.default !== 'function') {
|
|
1015
|
+
throw new Error('ESM bundle must export a default function(RED)');
|
|
1016
|
+
}
|
|
1017
|
+
await mod.default(RED);
|
|
1018
|
+
})().catch(function (err) {
|
|
1019
|
+
RED.log.error('Failed to load ESM bundle: ' + err.message);
|
|
1020
|
+
});
|
|
1021
|
+
};
|
|
1022
|
+
`;
|
|
1023
|
+
fs5.writeFileSync(path5.join(buildContext.outDir, "index.js"), bridgeCode);
|
|
1024
|
+
}
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
throw new BuildError("server", error);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/vite/client/build.ts
|
|
1031
|
+
import { build as viteBuild2 } from "vite";
|
|
1032
|
+
import vue from "@vitejs/plugin-vue";
|
|
1033
|
+
import fs10 from "fs";
|
|
1034
|
+
import path10 from "path";
|
|
1035
|
+
|
|
1036
|
+
// src/vite/client/plugins/html-generator.ts
|
|
1037
|
+
import mime from "mime-types";
|
|
1038
|
+
import fs6 from "fs";
|
|
1039
|
+
import path6 from "path";
|
|
1040
|
+
function htmlGenerator(options) {
|
|
1041
|
+
const { packageName, licensePath } = options;
|
|
1042
|
+
return {
|
|
1043
|
+
name: "vite-plugin-node-red:client:html-generator",
|
|
1044
|
+
apply: "build",
|
|
1045
|
+
enforce: "post",
|
|
1046
|
+
generateBundle(_, bundle) {
|
|
1047
|
+
const resourcesTags = Object.keys(bundle).map((fileName) => {
|
|
1048
|
+
const asset = bundle[fileName];
|
|
1049
|
+
const srcPath = path6.join(
|
|
1050
|
+
"resources",
|
|
1051
|
+
packageName,
|
|
1052
|
+
fileName.replace(/^resources\/?/, "")
|
|
1053
|
+
);
|
|
1054
|
+
const content = asset.type === "asset" ? asset.source : asset.type === "chunk" ? asset.code : null;
|
|
1055
|
+
if (typeof content !== "string" && !(content instanceof Uint8Array))
|
|
1056
|
+
return null;
|
|
1057
|
+
const mimeType = mime.lookup(fileName);
|
|
1058
|
+
switch (mimeType) {
|
|
1059
|
+
case "application/javascript":
|
|
1060
|
+
case "text/javascript":
|
|
1061
|
+
return `<script type="module" src="${srcPath}" defer></script>`;
|
|
1062
|
+
case "text/css":
|
|
1063
|
+
return `<link rel="stylesheet" href="${srcPath}">`;
|
|
1064
|
+
case "font/woff":
|
|
1065
|
+
case "font/woff2":
|
|
1066
|
+
case "application/font-woff":
|
|
1067
|
+
case "application/font-woff2":
|
|
1068
|
+
case "application/x-font-ttf":
|
|
1069
|
+
case "application/x-font-opentype":
|
|
1070
|
+
case "font/ttf":
|
|
1071
|
+
case "font/otf":
|
|
1072
|
+
return `<link rel="preload" as="font" href="${srcPath}" type="${mimeType}">`;
|
|
1073
|
+
default:
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
}).filter(Boolean).join("\n");
|
|
1077
|
+
const licenseBanner = licensePath && fs6.existsSync(licensePath) ? `<!--
|
|
1078
|
+
${fs6.readFileSync(licensePath, "utf-8")}
|
|
1079
|
+
-->` : "";
|
|
1080
|
+
this.emitFile({
|
|
1081
|
+
type: "asset",
|
|
1082
|
+
fileName: "index.html",
|
|
1083
|
+
source: `${licenseBanner}
|
|
1084
|
+
${resourcesTags}`
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/vite/client/plugins/locales-generator.ts
|
|
1091
|
+
import fs7 from "fs";
|
|
1092
|
+
import path7 from "path";
|
|
1093
|
+
function localesGenerator(options) {
|
|
1094
|
+
const { outDir, docsDir, labelsDir, languages } = options;
|
|
1095
|
+
return {
|
|
1096
|
+
name: "vite-plugin-node-red:client:locales-generator",
|
|
1097
|
+
apply: "build",
|
|
1098
|
+
enforce: "post",
|
|
1099
|
+
closeBundle() {
|
|
1100
|
+
function validateLanguage(lang, filePath) {
|
|
1101
|
+
if (!languages.includes(lang)) {
|
|
1102
|
+
throw new Error(
|
|
1103
|
+
`[locales] Invalid language "${lang}" in "${filePath}".
|
|
1104
|
+
Supported: ${languages.join(", ")}`
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
function forEachFile(baseDir, fileExtensions, processFile) {
|
|
1109
|
+
const langMap = /* @__PURE__ */ new Map();
|
|
1110
|
+
if (!fs7.existsSync(baseDir)) return langMap;
|
|
1111
|
+
const nodeDirs = fs7.readdirSync(baseDir, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
1112
|
+
for (const nodeDir of nodeDirs) {
|
|
1113
|
+
const nodeType = nodeDir.name;
|
|
1114
|
+
const nodePath = path7.join(baseDir, nodeType);
|
|
1115
|
+
const files = fs7.readdirSync(nodePath);
|
|
1116
|
+
for (const file of files) {
|
|
1117
|
+
const ext = path7.extname(file);
|
|
1118
|
+
if (!fileExtensions.includes(ext)) continue;
|
|
1119
|
+
const lang = path7.basename(file, ext);
|
|
1120
|
+
const filePath = path7.join(nodePath, file);
|
|
1121
|
+
validateLanguage(lang, filePath);
|
|
1122
|
+
const value = processFile({ ext, filePath, nodeType });
|
|
1123
|
+
if (value == null) continue;
|
|
1124
|
+
if (!langMap.has(lang)) {
|
|
1125
|
+
langMap.set(lang, Array.isArray(value) ? [] : {});
|
|
1126
|
+
}
|
|
1127
|
+
if (Array.isArray(value)) {
|
|
1128
|
+
langMap.get(lang).push(...value);
|
|
1129
|
+
} else {
|
|
1130
|
+
langMap.get(lang)[nodeType] = value;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
return langMap;
|
|
1135
|
+
}
|
|
1136
|
+
function writeOutput(langMap, fileName, serialize) {
|
|
1137
|
+
for (const [lang, data] of langMap.entries()) {
|
|
1138
|
+
const langOutDir = path7.join(outDir, lang);
|
|
1139
|
+
fs7.mkdirSync(langOutDir, { recursive: true });
|
|
1140
|
+
fs7.writeFileSync(
|
|
1141
|
+
path7.join(langOutDir, fileName),
|
|
1142
|
+
serialize(data),
|
|
1143
|
+
"utf-8"
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
const docLangs = forEachFile(
|
|
1148
|
+
docsDir,
|
|
1149
|
+
[".html", ".md"],
|
|
1150
|
+
({ ext, filePath, nodeType }) => {
|
|
1151
|
+
const type = ext === ".html" ? "text/html" : ext === ".md" ? "text/markdown" : null;
|
|
1152
|
+
if (!type) return null;
|
|
1153
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
1154
|
+
return [
|
|
1155
|
+
`<script type="${type}" data-help-name="${nodeType}">
|
|
1156
|
+
${content}
|
|
1157
|
+
</script>`
|
|
1158
|
+
];
|
|
1159
|
+
}
|
|
1160
|
+
);
|
|
1161
|
+
writeOutput(
|
|
1162
|
+
docLangs,
|
|
1163
|
+
"index.html",
|
|
1164
|
+
(value) => value.join("\n")
|
|
1165
|
+
);
|
|
1166
|
+
const labelLangs = forEachFile(
|
|
1167
|
+
labelsDir,
|
|
1168
|
+
[".json"],
|
|
1169
|
+
({ filePath }) => JSON.parse(fs7.readFileSync(filePath, "utf-8"))
|
|
1170
|
+
);
|
|
1171
|
+
writeOutput(
|
|
1172
|
+
labelLangs,
|
|
1173
|
+
"index.json",
|
|
1174
|
+
(value) => JSON.stringify(value, null, 2)
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/vite/client/plugins/minifier.ts
|
|
1181
|
+
import { transform } from "esbuild";
|
|
1182
|
+
function minifier() {
|
|
1183
|
+
return {
|
|
1184
|
+
name: "vite-plugin-node-red:client:minifier",
|
|
1185
|
+
apply: "build",
|
|
1186
|
+
renderChunk: {
|
|
1187
|
+
order: "post",
|
|
1188
|
+
async handler(code, chunk, outputOptions) {
|
|
1189
|
+
if (outputOptions.format === "es" && chunk.fileName.endsWith(".js")) {
|
|
1190
|
+
const result = await transform(code, { minify: true });
|
|
1191
|
+
return result.code;
|
|
1192
|
+
}
|
|
1193
|
+
return code;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// src/vite/client/plugins/node-definitions-inliner.ts
|
|
1200
|
+
import { createRequire } from "module";
|
|
1201
|
+
import { pathToFileURL } from "url";
|
|
1202
|
+
import path8 from "path";
|
|
1203
|
+
import fs8 from "fs";
|
|
1204
|
+
import mime2 from "mime-types";
|
|
1205
|
+
var VIRTUAL_ID = "virtual:nrg/node-definitions";
|
|
1206
|
+
var RESOLVED_ID = "\0" + VIRTUAL_ID;
|
|
1207
|
+
var VIRTUAL_ENTRY_ID = "virtual:nrg/client-entry";
|
|
1208
|
+
var RESOLVED_ENTRY_ID = "\0" + VIRTUAL_ENTRY_ID;
|
|
1209
|
+
var SKIP_DEFAULTS = /* @__PURE__ */ new Set(["x", "y", "z", "g", "wires", "type", "id"]);
|
|
1210
|
+
function getDefaultsFromSchema(schema) {
|
|
1211
|
+
if (!schema?.properties) return void 0;
|
|
1212
|
+
const result = {};
|
|
1213
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1214
|
+
if (SKIP_DEFAULTS.has(key)) continue;
|
|
1215
|
+
result[key] = {
|
|
1216
|
+
required: false,
|
|
1217
|
+
value: prop.default ?? void 0,
|
|
1218
|
+
type: prop["node-type"]
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
return result;
|
|
1222
|
+
}
|
|
1223
|
+
function getCredentialsFromSchema(schema) {
|
|
1224
|
+
if (!schema?.properties) return void 0;
|
|
1225
|
+
const result = {};
|
|
1226
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1227
|
+
result[key] = {
|
|
1228
|
+
required: false,
|
|
1229
|
+
type: prop.format === "password" ? "password" : "text",
|
|
1230
|
+
value: prop.default ?? void 0
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
return result;
|
|
1234
|
+
}
|
|
1235
|
+
function resolveIcon(iconsDir, type) {
|
|
1236
|
+
if (!fs8.existsSync(iconsDir)) return void 0;
|
|
1237
|
+
return fs8.readdirSync(iconsDir).find((f) => {
|
|
1238
|
+
if (path8.basename(f, path8.extname(f)) !== type) return false;
|
|
1239
|
+
const mimeType = mime2.lookup(f);
|
|
1240
|
+
return mimeType !== false && mimeType.startsWith("image/");
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir, nodesDir, hasUserEntry = true) {
|
|
1244
|
+
let _nodeTypes = [];
|
|
1245
|
+
let _definitions = {};
|
|
1246
|
+
return {
|
|
1247
|
+
name: "vite-plugin-node-red:client:node-definitions-inliner",
|
|
1248
|
+
enforce: "pre",
|
|
1249
|
+
// Load the server bundle in buildStart so _nodeTypes is populated
|
|
1250
|
+
// before any load/transform hooks run.
|
|
1251
|
+
async buildStart() {
|
|
1252
|
+
_nodeTypes = [];
|
|
1253
|
+
_definitions = {};
|
|
1254
|
+
const esmEntryPath = path8.resolve(serverOutDir, "index.mjs");
|
|
1255
|
+
const cjsEntryPath = path8.resolve(serverOutDir, "index.js");
|
|
1256
|
+
let packageFn;
|
|
1257
|
+
if (fs8.existsSync(esmEntryPath)) {
|
|
1258
|
+
const fileUrl = pathToFileURL(esmEntryPath).href + `?t=${Date.now()}`;
|
|
1259
|
+
const mod = await import(fileUrl);
|
|
1260
|
+
packageFn = mod?.default ?? mod;
|
|
1261
|
+
} else if (fs8.existsSync(cjsEntryPath)) {
|
|
1262
|
+
const require2 = createRequire(import.meta.url);
|
|
1263
|
+
delete require2.cache[cjsEntryPath];
|
|
1264
|
+
const rawMod = require2(cjsEntryPath);
|
|
1265
|
+
packageFn = rawMod?.default ?? rawMod;
|
|
1266
|
+
}
|
|
1267
|
+
const nodeClasses = packageFn?.nodes ?? [];
|
|
1268
|
+
for (const NodeClass of nodeClasses) {
|
|
1269
|
+
const type = NodeClass.type;
|
|
1270
|
+
if (!type) continue;
|
|
1271
|
+
_nodeTypes.push(type);
|
|
1272
|
+
const configSchema = NodeClass.configSchema ?? null;
|
|
1273
|
+
const credentialsSchema = NodeClass.credentialsSchema ?? null;
|
|
1274
|
+
const inputSchema = NodeClass.inputSchema ?? null;
|
|
1275
|
+
const outputsSchema = NodeClass.outputsSchema ?? null;
|
|
1276
|
+
const defaults = getDefaultsFromSchema(configSchema);
|
|
1277
|
+
if (defaults && inputSchema) {
|
|
1278
|
+
defaults.validateInput = { required: false, value: false };
|
|
1279
|
+
}
|
|
1280
|
+
if (defaults && outputsSchema) {
|
|
1281
|
+
defaults.validateOutput = { required: false, value: false };
|
|
1282
|
+
}
|
|
1283
|
+
const credentials = getCredentialsFromSchema(credentialsSchema);
|
|
1284
|
+
_definitions[type] = {
|
|
1285
|
+
type,
|
|
1286
|
+
category: NodeClass.category,
|
|
1287
|
+
configSchema,
|
|
1288
|
+
credentialsSchema,
|
|
1289
|
+
settingsSchema: NodeClass.settingsSchema ?? null,
|
|
1290
|
+
defaults: defaults ?? void 0,
|
|
1291
|
+
credentials: credentials ?? void 0,
|
|
1292
|
+
align: NodeClass.align,
|
|
1293
|
+
color: NodeClass.color,
|
|
1294
|
+
icon: iconsDir ? resolveIcon(iconsDir, type) : void 0,
|
|
1295
|
+
labelStyle: NodeClass.labelStyle,
|
|
1296
|
+
paletteLabel: NodeClass.paletteLabel,
|
|
1297
|
+
inputs: NodeClass.inputs,
|
|
1298
|
+
outputs: NodeClass.outputs,
|
|
1299
|
+
inputLabels: NodeClass.inputLabels,
|
|
1300
|
+
outputLabels: NodeClass.outputLabels,
|
|
1301
|
+
inputSchema,
|
|
1302
|
+
outputsSchema
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
},
|
|
1306
|
+
resolveId(id) {
|
|
1307
|
+
if (id === VIRTUAL_ID) return RESOLVED_ID;
|
|
1308
|
+
if (id === VIRTUAL_ENTRY_ID) return RESOLVED_ENTRY_ID;
|
|
1309
|
+
},
|
|
1310
|
+
load(id) {
|
|
1311
|
+
if (id === RESOLVED_ENTRY_ID) return "";
|
|
1312
|
+
if (id !== RESOLVED_ID) return;
|
|
1313
|
+
return `export default ${JSON.stringify(_definitions)};`;
|
|
1314
|
+
},
|
|
1315
|
+
transform(code, id) {
|
|
1316
|
+
if (id !== entryPath && id !== RESOLVED_ENTRY_ID) return;
|
|
1317
|
+
const nrgImports = /* @__PURE__ */ new Set(["__setSchemas"]);
|
|
1318
|
+
const lines = [`import __nrgSchemas from "${VIRTUAL_ID}";`];
|
|
1319
|
+
const postLines = [`__setSchemas(__nrgSchemas);`];
|
|
1320
|
+
if (componentsDir && fs8.existsSync(componentsDir)) {
|
|
1321
|
+
const formImports = [];
|
|
1322
|
+
const formEntries = [];
|
|
1323
|
+
for (const type of _nodeTypes) {
|
|
1324
|
+
const componentPath = path8.resolve(componentsDir, `${type}.vue`);
|
|
1325
|
+
if (fs8.existsSync(componentPath)) {
|
|
1326
|
+
const varName = `__nrgForm_${type.replace(/-/g, "_")}`;
|
|
1327
|
+
formImports.push(
|
|
1328
|
+
`import ${varName} from ${JSON.stringify(componentPath)};`
|
|
1329
|
+
);
|
|
1330
|
+
formEntries.push(`${JSON.stringify(type)}: ${varName}`);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
if (formImports.length > 0) {
|
|
1334
|
+
lines.push(...formImports);
|
|
1335
|
+
nrgImports.add("__setForms");
|
|
1336
|
+
postLines.push(`__setForms({ ${formEntries.join(", ")} });`);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (!hasUserEntry) {
|
|
1340
|
+
const defVarNames = [];
|
|
1341
|
+
for (const type of _nodeTypes) {
|
|
1342
|
+
const varName = `__nrgNodeDef_${type.replace(/-/g, "_")}`;
|
|
1343
|
+
const tsPath = nodesDir ? path8.resolve(nodesDir, `${type}.ts`) : null;
|
|
1344
|
+
if (tsPath && fs8.existsSync(tsPath)) {
|
|
1345
|
+
lines.push(`import ${varName} from ${JSON.stringify(tsPath)};`);
|
|
1346
|
+
} else {
|
|
1347
|
+
lines.push(`const ${varName} = { type: ${JSON.stringify(type)} };`);
|
|
1348
|
+
}
|
|
1349
|
+
defVarNames.push(varName);
|
|
1350
|
+
}
|
|
1351
|
+
if (defVarNames.length > 0) {
|
|
1352
|
+
nrgImports.add("registerTypes");
|
|
1353
|
+
postLines.push(`registerTypes([${defVarNames.join(", ")}]);`);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
const importLine = `import { ${[...nrgImports].join(", ")} } from "@bonsae/nrg/client";`;
|
|
1357
|
+
lines.splice(1, 0, importLine);
|
|
1358
|
+
lines.push(...postLines);
|
|
1359
|
+
lines.push("");
|
|
1360
|
+
return { code: lines.join("\n") + code, map: null };
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// src/vite/client/plugins/static-copy.ts
|
|
1366
|
+
import fs9 from "fs";
|
|
1367
|
+
import path9 from "path";
|
|
1368
|
+
function staticCopy(options) {
|
|
1369
|
+
const { targets } = options;
|
|
1370
|
+
return {
|
|
1371
|
+
name: "vite-plugin-node-red:client:static-copy",
|
|
1372
|
+
apply: "build",
|
|
1373
|
+
enforce: "post",
|
|
1374
|
+
closeBundle() {
|
|
1375
|
+
for (const { src, dest } of targets) {
|
|
1376
|
+
if (!fs9.existsSync(src)) continue;
|
|
1377
|
+
fs9.mkdirSync(dest, { recursive: true });
|
|
1378
|
+
const stat = fs9.statSync(src);
|
|
1379
|
+
if (stat.isDirectory()) {
|
|
1380
|
+
const files = fs9.readdirSync(src);
|
|
1381
|
+
for (const file of files) {
|
|
1382
|
+
const srcFile = path9.join(src, file);
|
|
1383
|
+
const destFile = path9.join(dest, file);
|
|
1384
|
+
const fileStat = fs9.statSync(srcFile);
|
|
1385
|
+
if (fileStat.isDirectory()) {
|
|
1386
|
+
fs9.cpSync(srcFile, destFile, { recursive: true });
|
|
1387
|
+
} else {
|
|
1388
|
+
fs9.copyFileSync(srcFile, destFile);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
} else {
|
|
1392
|
+
fs9.copyFileSync(src, dest);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// src/vite/client/build.ts
|
|
1400
|
+
async function build2(clientBuildOptions, buildContext) {
|
|
1401
|
+
const {
|
|
1402
|
+
srcDir = "./client",
|
|
1403
|
+
entry = "index.ts",
|
|
1404
|
+
name = "NodeRedNodes",
|
|
1405
|
+
format = "es",
|
|
1406
|
+
licensePath = "./LICENSE",
|
|
1407
|
+
locales,
|
|
1408
|
+
staticDirs = {},
|
|
1409
|
+
external = ["jquery", "node-red", "vue"],
|
|
1410
|
+
globals = { jquery: "$", "node-red": "RED", vue: "Vue" },
|
|
1411
|
+
manualChunks,
|
|
1412
|
+
plugins: userPlugins = []
|
|
1413
|
+
} = clientBuildOptions;
|
|
1414
|
+
const physicalEntryPath = path10.resolve(srcDir, entry);
|
|
1415
|
+
let entryPath;
|
|
1416
|
+
let generatedEntry = false;
|
|
1417
|
+
if (fs10.existsSync(physicalEntryPath)) {
|
|
1418
|
+
entryPath = physicalEntryPath;
|
|
1419
|
+
} else {
|
|
1420
|
+
if (!fs10.existsSync(path10.dirname(physicalEntryPath))) {
|
|
1421
|
+
fs10.mkdirSync(path10.dirname(physicalEntryPath), { recursive: true });
|
|
1422
|
+
}
|
|
1423
|
+
fs10.writeFileSync(physicalEntryPath, "// auto-generated entry\n");
|
|
1424
|
+
entryPath = physicalEntryPath;
|
|
1425
|
+
generatedEntry = true;
|
|
1426
|
+
}
|
|
1427
|
+
const iconsDir = path10.resolve(
|
|
1428
|
+
staticDirs.icons ?? path10.join(path10.dirname(path10.resolve(srcDir)), "icons")
|
|
1429
|
+
);
|
|
1430
|
+
const plugins = [
|
|
1431
|
+
vue(),
|
|
1432
|
+
nodeDefinitionsInliner(
|
|
1433
|
+
buildContext.outDir,
|
|
1434
|
+
entryPath,
|
|
1435
|
+
fs10.existsSync(iconsDir) ? iconsDir : void 0,
|
|
1436
|
+
path10.resolve(srcDir, "components"),
|
|
1437
|
+
path10.resolve(srcDir, "nodes"),
|
|
1438
|
+
!generatedEntry
|
|
1439
|
+
),
|
|
1440
|
+
...userPlugins
|
|
1441
|
+
];
|
|
1442
|
+
plugins.push(
|
|
1443
|
+
htmlGenerator({
|
|
1444
|
+
packageName: buildContext.packageName,
|
|
1445
|
+
licensePath: licensePath ? path10.resolve(licensePath) : void 0
|
|
1446
|
+
})
|
|
1447
|
+
);
|
|
1448
|
+
if (locales) {
|
|
1449
|
+
const {
|
|
1450
|
+
docsDir = "./locales/docs",
|
|
1451
|
+
labelsDir = "./locales/labels",
|
|
1452
|
+
languages = [
|
|
1453
|
+
"en-US",
|
|
1454
|
+
"de",
|
|
1455
|
+
"es-ES",
|
|
1456
|
+
"fr",
|
|
1457
|
+
"ko",
|
|
1458
|
+
"pt-BR",
|
|
1459
|
+
"ru",
|
|
1460
|
+
"ja",
|
|
1461
|
+
"zh-CN",
|
|
1462
|
+
"zh-TW"
|
|
1463
|
+
]
|
|
1464
|
+
} = locales;
|
|
1465
|
+
plugins.push(
|
|
1466
|
+
localesGenerator({
|
|
1467
|
+
outDir: path10.join(buildContext.outDir, "locales"),
|
|
1468
|
+
docsDir: path10.resolve(docsDir),
|
|
1469
|
+
labelsDir: path10.resolve(labelsDir),
|
|
1470
|
+
languages
|
|
1471
|
+
})
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
const copyTargets = [];
|
|
1475
|
+
const publicDir = path10.resolve(
|
|
1476
|
+
staticDirs.public ?? path10.join(srcDir, "public")
|
|
1477
|
+
);
|
|
1478
|
+
if (fs10.existsSync(publicDir)) {
|
|
1479
|
+
copyTargets.push({
|
|
1480
|
+
src: publicDir,
|
|
1481
|
+
dest: path10.join(buildContext.outDir, "resources")
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
if (fs10.existsSync(iconsDir)) {
|
|
1485
|
+
copyTargets.push({
|
|
1486
|
+
src: iconsDir,
|
|
1487
|
+
dest: path10.join(buildContext.outDir, "icons")
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
if (copyTargets.length > 0) {
|
|
1491
|
+
plugins.push(staticCopy({ targets: copyTargets }));
|
|
1492
|
+
}
|
|
1493
|
+
if (!buildContext.isDev && format === "es") {
|
|
1494
|
+
plugins.push(minifier());
|
|
1495
|
+
}
|
|
1496
|
+
plugins.unshift({
|
|
1497
|
+
name: "nrg-client-external",
|
|
1498
|
+
enforce: "pre",
|
|
1499
|
+
resolveId(id) {
|
|
1500
|
+
if (id === "@bonsae/nrg/client")
|
|
1501
|
+
return { id: "@bonsae/nrg/client", external: true };
|
|
1502
|
+
}
|
|
1503
|
+
});
|
|
1504
|
+
const defaultManualChunks = (id) => {
|
|
1505
|
+
if (!id.includes("node_modules")) return void 0;
|
|
1506
|
+
const parts = id.substring(id.lastIndexOf("node_modules/") + "node_modules/".length).split("/");
|
|
1507
|
+
const pkgName = parts[0].startsWith("@") ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
1508
|
+
if (["jsonpointer", "es-toolkit"].includes(pkgName)) return "vendor-utils";
|
|
1509
|
+
return "vendor";
|
|
1510
|
+
};
|
|
1511
|
+
const config = {
|
|
1512
|
+
configFile: false,
|
|
1513
|
+
logLevel: "warn",
|
|
1514
|
+
base: `/${path10.join("resources", buildContext.packageName)}`,
|
|
1515
|
+
publicDir: path10.resolve(srcDir, "public"),
|
|
1516
|
+
resolve: {
|
|
1517
|
+
alias: {
|
|
1518
|
+
"@": path10.resolve(srcDir)
|
|
1519
|
+
}
|
|
1520
|
+
},
|
|
1521
|
+
plugins,
|
|
1522
|
+
css: {
|
|
1523
|
+
devSourcemap: buildContext.isDev
|
|
1524
|
+
},
|
|
1525
|
+
build: {
|
|
1526
|
+
outDir: buildContext.outDir,
|
|
1527
|
+
emptyOutDir: false,
|
|
1528
|
+
sourcemap: buildContext.isDev ? "inline" : true,
|
|
1529
|
+
minify: !buildContext.isDev && format !== "es",
|
|
1530
|
+
copyPublicDir: false,
|
|
1531
|
+
lib: {
|
|
1532
|
+
entry: entryPath,
|
|
1533
|
+
name,
|
|
1534
|
+
fileName: "index",
|
|
1535
|
+
formats: [format]
|
|
1536
|
+
},
|
|
1537
|
+
rollupOptions: {
|
|
1538
|
+
external,
|
|
1539
|
+
treeshake: false,
|
|
1540
|
+
output: {
|
|
1541
|
+
entryFileNames: path10.join("resources", "index.[hash].js"),
|
|
1542
|
+
chunkFileNames: path10.join("resources", "vendor.[hash].js"),
|
|
1543
|
+
assetFileNames: path10.join("resources", "[name].[hash].[ext]"),
|
|
1544
|
+
globals,
|
|
1545
|
+
paths: {
|
|
1546
|
+
vue: "/nrg/assets/vue.esm-browser.prod.js",
|
|
1547
|
+
"@bonsae/nrg/client": "/nrg/assets/nrg-client.js"
|
|
1548
|
+
},
|
|
1549
|
+
sourcemapPathTransform: (relativeSourcePath) => {
|
|
1550
|
+
return relativeSourcePath.replace(/\/client\//g, "/");
|
|
1551
|
+
},
|
|
1552
|
+
manualChunks: manualChunks ?? defaultManualChunks
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
},
|
|
1556
|
+
define: {
|
|
1557
|
+
"process.env.NODE_ENV": JSON.stringify(
|
|
1558
|
+
buildContext.isDev ? "development" : "production"
|
|
1559
|
+
),
|
|
1560
|
+
"process.env": {}
|
|
1561
|
+
}
|
|
1562
|
+
};
|
|
1563
|
+
try {
|
|
1564
|
+
await viteBuild2(config);
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
throw new BuildError("client", error);
|
|
1567
|
+
} finally {
|
|
1568
|
+
if (generatedEntry) {
|
|
1569
|
+
fs10.unlinkSync(physicalEntryPath);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
// src/vite/plugins/server.ts
|
|
1575
|
+
var color2 = {
|
|
1576
|
+
dim: (s) => `\x1B[2m${s}\x1B[0m`,
|
|
1577
|
+
cyan: (s) => `\x1B[36m${s}\x1B[0m`,
|
|
1578
|
+
green: (s) => `\x1B[32m${s}\x1B[0m`,
|
|
1579
|
+
yellow: (s) => `\x1B[33m${s}\x1B[0m`
|
|
1580
|
+
};
|
|
1581
|
+
function serverPlugin(options) {
|
|
1582
|
+
const {
|
|
1583
|
+
nodeRedLauncher,
|
|
1584
|
+
serverBuildOptions,
|
|
1585
|
+
clientBuildOptions,
|
|
1586
|
+
extraFilesCopyTargets,
|
|
1587
|
+
buildContext
|
|
1588
|
+
} = options;
|
|
1589
|
+
let nodeRedPort;
|
|
1590
|
+
let initialStartDone = false;
|
|
1591
|
+
let isStarting = false;
|
|
1592
|
+
let isShuttingDown = false;
|
|
1593
|
+
let shutdownStartTime = 0;
|
|
1594
|
+
let pendingStart = false;
|
|
1595
|
+
let server;
|
|
1596
|
+
let watcher = null;
|
|
1597
|
+
const build3 = async (clean = false) => {
|
|
1598
|
+
if (clean) {
|
|
1599
|
+
logger.startSpinner("Cleaning");
|
|
1600
|
+
cleanDir(buildContext.outDir);
|
|
1601
|
+
logger.stopSpinner("Cleaned");
|
|
1602
|
+
}
|
|
1603
|
+
logger.startSpinner("Building");
|
|
1604
|
+
await build(serverBuildOptions, buildContext);
|
|
1605
|
+
await build2(clientBuildOptions, buildContext);
|
|
1606
|
+
logger.stopSpinner("Built");
|
|
1607
|
+
if (extraFilesCopyTargets.length) {
|
|
1608
|
+
logger.startSpinner("Copying extra files");
|
|
1609
|
+
copyFiles(extraFilesCopyTargets, buildContext.outDir);
|
|
1610
|
+
logger.stopSpinner("Copied extra files");
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
const start = async (clean = false) => {
|
|
1614
|
+
if (isStarting) {
|
|
1615
|
+
pendingStart = true;
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
isStarting = true;
|
|
1619
|
+
pendingStart = false;
|
|
1620
|
+
try {
|
|
1621
|
+
await nodeRedLauncher.stop();
|
|
1622
|
+
await build3(clean);
|
|
1623
|
+
logger.startSpinner("Starting Node-RED");
|
|
1624
|
+
nodeRedPort = await nodeRedLauncher.start();
|
|
1625
|
+
logger.stopSpinner("Node-RED started");
|
|
1626
|
+
const proxyConfig = server.config.server.proxy;
|
|
1627
|
+
if (proxyConfig && typeof proxyConfig === "object") {
|
|
1628
|
+
const rule = proxyConfig["^/.*"];
|
|
1629
|
+
if (rule && typeof rule === "object") {
|
|
1630
|
+
rule.target = `http://127.0.0.1:${nodeRedPort}`;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
logger.success("Ready");
|
|
1634
|
+
} catch (error) {
|
|
1635
|
+
if (error instanceof BuildError) {
|
|
1636
|
+
logger.error(`Rebuild failed: ${error.message}`);
|
|
1637
|
+
} else if (error instanceof NodeRedStartError) {
|
|
1638
|
+
logger.error("Failed to restart Node-RED");
|
|
1639
|
+
} else {
|
|
1640
|
+
logger.error(`Unexpected error: ${error.message}`);
|
|
1641
|
+
}
|
|
1642
|
+
throw error;
|
|
1643
|
+
} finally {
|
|
1644
|
+
isStarting = false;
|
|
1645
|
+
if (pendingStart) {
|
|
1646
|
+
start(false);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
};
|
|
1650
|
+
const printServerUrls = (vitePort, nodeRedPorts) => {
|
|
1651
|
+
console.log();
|
|
1652
|
+
console.log(
|
|
1653
|
+
` ${color2.cyan("Vite")} ${color2.dim("\u279C")} ${color2.cyan(`http://127.0.0.1:${vitePort}/`)}`
|
|
1654
|
+
);
|
|
1655
|
+
if (nodeRedPorts.actual != nodeRedPorts.preferred) {
|
|
1656
|
+
console.log(
|
|
1657
|
+
` ${color2.green("Node-RED")} ${color2.dim("\u279C")} ${color2.green(`http://127.0.0.1:${nodeRedPorts.actual}/`)} ${color2.yellow(`(port ${nodeRedPorts.preferred} was in use)`)}`
|
|
1658
|
+
);
|
|
1659
|
+
} else {
|
|
1660
|
+
console.log(
|
|
1661
|
+
` ${color2.green("Node-RED")} ${color2.dim("\u279C")} ${color2.green(`http://127.0.0.1:${nodeRedPorts.actual}/`)}`
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
console.log();
|
|
1665
|
+
};
|
|
1666
|
+
return {
|
|
1667
|
+
name: "vite-plugin-node-red:server",
|
|
1668
|
+
apply: "serve",
|
|
1669
|
+
config() {
|
|
1670
|
+
return {
|
|
1671
|
+
appType: "custom",
|
|
1672
|
+
server: {
|
|
1673
|
+
host: "127.0.0.1",
|
|
1674
|
+
proxy: {
|
|
1675
|
+
"^/.*": {
|
|
1676
|
+
target: `http://127.0.0.1:${nodeRedLauncher.preferredPort}`,
|
|
1677
|
+
changeOrigin: true,
|
|
1678
|
+
ws: true,
|
|
1679
|
+
configure: (proxy) => {
|
|
1680
|
+
proxy.on("error", () => {
|
|
1681
|
+
if (nodeRedPort) {
|
|
1682
|
+
proxy.options.target = `http://127.0.0.1:${nodeRedPort}`;
|
|
1683
|
+
}
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
},
|
|
1688
|
+
watch: {
|
|
1689
|
+
// NOTE: this is necessary to avoid default hmr in .html files. Watcher is configured manually
|
|
1690
|
+
ignored: ["**/*"]
|
|
1691
|
+
}
|
|
1692
|
+
},
|
|
1693
|
+
customLogger: {
|
|
1694
|
+
...console,
|
|
1695
|
+
info: () => {
|
|
1696
|
+
},
|
|
1697
|
+
warn: console.warn,
|
|
1698
|
+
error: console.error,
|
|
1699
|
+
warnOnce: () => {
|
|
1700
|
+
},
|
|
1701
|
+
hasWarned: false,
|
|
1702
|
+
clearScreen: () => {
|
|
1703
|
+
},
|
|
1704
|
+
hasErrorLogged: () => false
|
|
1705
|
+
}
|
|
1706
|
+
};
|
|
1707
|
+
},
|
|
1708
|
+
async configureServer(viteServer) {
|
|
1709
|
+
server = viteServer;
|
|
1710
|
+
logger.intro();
|
|
1711
|
+
await start(true);
|
|
1712
|
+
initialStartDone = true;
|
|
1713
|
+
printServerUrls(server.config.server.port ?? 5173, {
|
|
1714
|
+
actual: nodeRedPort,
|
|
1715
|
+
preferred: nodeRedLauncher.preferredPort
|
|
1716
|
+
});
|
|
1717
|
+
logger.startGroup("Node-RED");
|
|
1718
|
+
nodeRedLauncher.flushLogs();
|
|
1719
|
+
const serverSrcDir = path11.resolve(
|
|
1720
|
+
serverBuildOptions.srcDir ?? "./server"
|
|
1721
|
+
);
|
|
1722
|
+
const clientSrcDir = path11.resolve(
|
|
1723
|
+
clientBuildOptions.srcDir ?? "./client"
|
|
1724
|
+
);
|
|
1725
|
+
const localesDocsDir = path11.resolve(
|
|
1726
|
+
clientBuildOptions.locales?.docsDir ?? "./locales/docs"
|
|
1727
|
+
);
|
|
1728
|
+
const localesLabelsDir = path11.resolve(
|
|
1729
|
+
clientBuildOptions.locales?.labelsDir ?? "./locales/labels"
|
|
1730
|
+
);
|
|
1731
|
+
const iconsDir = path11.resolve(
|
|
1732
|
+
clientBuildOptions.staticDirs?.icons ?? path11.join(path11.dirname(clientSrcDir), "icons")
|
|
1733
|
+
);
|
|
1734
|
+
const watchPaths = [
|
|
1735
|
+
serverSrcDir,
|
|
1736
|
+
clientSrcDir,
|
|
1737
|
+
localesDocsDir,
|
|
1738
|
+
localesLabelsDir,
|
|
1739
|
+
iconsDir
|
|
1740
|
+
];
|
|
1741
|
+
watcher = chokidar.watch(watchPaths, {
|
|
1742
|
+
ignoreInitial: true,
|
|
1743
|
+
persistent: true,
|
|
1744
|
+
ignored: ["**/node_modules/**", "**/dist/**", "**/.node-red/**"]
|
|
1745
|
+
});
|
|
1746
|
+
const debounceBeforeStart = debounce(
|
|
1747
|
+
() => start(false),
|
|
1748
|
+
nodeRedLauncher.restartDelay ?? 1e3
|
|
1749
|
+
);
|
|
1750
|
+
const handleFileChange = (file, event) => {
|
|
1751
|
+
if (!initialStartDone) return;
|
|
1752
|
+
logger.info(`${event}: ${path11.relative(process.cwd(), file)}`);
|
|
1753
|
+
debounceBeforeStart();
|
|
1754
|
+
};
|
|
1755
|
+
watcher.on("change", (file) => handleFileChange(file, "Changed"));
|
|
1756
|
+
watcher.on("add", (file) => handleFileChange(file, "Added"));
|
|
1757
|
+
watcher.on("unlink", (file) => handleFileChange(file, "Deleted"));
|
|
1758
|
+
process.on("SIGINT", () => {
|
|
1759
|
+
const now = Date.now();
|
|
1760
|
+
if (isShuttingDown && now - shutdownStartTime > 1e3) {
|
|
1761
|
+
const pid = nodeRedLauncher.pid;
|
|
1762
|
+
if (pid) {
|
|
1763
|
+
try {
|
|
1764
|
+
treeKill(pid, "SIGKILL");
|
|
1765
|
+
} catch {
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
logger.endGroup("Node-RED Stopped");
|
|
1769
|
+
process.exit(1);
|
|
1770
|
+
}
|
|
1771
|
+
if (isShuttingDown) {
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
isShuttingDown = true;
|
|
1775
|
+
shutdownStartTime = now;
|
|
1776
|
+
logger.warn("Shutting down gracefully... (Ctrl+C again to force)");
|
|
1777
|
+
nodeRedLauncher.stop(true).then(() => {
|
|
1778
|
+
nodeRedLauncher.cleanup();
|
|
1779
|
+
logger.endGroup("Node-RED Stopped");
|
|
1780
|
+
process.exit(0);
|
|
1781
|
+
}).catch(() => {
|
|
1782
|
+
process.exit(1);
|
|
1783
|
+
});
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// src/vite/plugins/build.ts
|
|
1790
|
+
import { execSync } from "child_process";
|
|
1791
|
+
import fs11 from "fs";
|
|
1792
|
+
import path12 from "path";
|
|
1793
|
+
function buildPlugin(options) {
|
|
1794
|
+
const {
|
|
1795
|
+
serverBuildOptions,
|
|
1796
|
+
clientBuildOptions,
|
|
1797
|
+
extraFilesCopyTargets,
|
|
1798
|
+
buildContext
|
|
1799
|
+
} = options;
|
|
1800
|
+
return {
|
|
1801
|
+
name: "vite-plugin-node-red:build",
|
|
1802
|
+
apply: "build",
|
|
1803
|
+
async buildStart() {
|
|
1804
|
+
try {
|
|
1805
|
+
logger.intro();
|
|
1806
|
+
logger.startSpinner("Type checking");
|
|
1807
|
+
const serverTsconfig = path12.resolve(
|
|
1808
|
+
serverBuildOptions.srcDir ?? "./src/server",
|
|
1809
|
+
"tsconfig.json"
|
|
1810
|
+
);
|
|
1811
|
+
const clientTsconfig = path12.resolve(
|
|
1812
|
+
clientBuildOptions.srcDir ?? "./src/client",
|
|
1813
|
+
"tsconfig.json"
|
|
1814
|
+
);
|
|
1815
|
+
const tsconfigsToCheck = [serverTsconfig, clientTsconfig].filter(
|
|
1816
|
+
(p) => fs11.existsSync(p)
|
|
1817
|
+
);
|
|
1818
|
+
for (const tsconfig of tsconfigsToCheck) {
|
|
1819
|
+
execSync(`npx tsc -p ${tsconfig} --noEmit`, { stdio: "inherit" });
|
|
1820
|
+
}
|
|
1821
|
+
logger.stopSpinner("Type checked");
|
|
1822
|
+
logger.startSpinner("Cleaning");
|
|
1823
|
+
cleanDir(buildContext.outDir);
|
|
1824
|
+
logger.stopSpinner("Cleaned");
|
|
1825
|
+
logger.startSpinner("Building");
|
|
1826
|
+
await build(serverBuildOptions, buildContext);
|
|
1827
|
+
await build2(clientBuildOptions, buildContext);
|
|
1828
|
+
logger.stopSpinner("Built");
|
|
1829
|
+
if (extraFilesCopyTargets.length) {
|
|
1830
|
+
logger.startSpinner("Copying extra files");
|
|
1831
|
+
copyFiles(extraFilesCopyTargets, buildContext.outDir);
|
|
1832
|
+
logger.stopSpinner("Copied extra files");
|
|
1833
|
+
}
|
|
1834
|
+
logger.success("Success");
|
|
1835
|
+
process.exit(0);
|
|
1836
|
+
} catch (error) {
|
|
1837
|
+
if (error instanceof BuildError) {
|
|
1838
|
+
logger.error(`Build failed: ${error.message}`, error.cause);
|
|
1839
|
+
} else {
|
|
1840
|
+
logger.error("Unexpected error during build", error);
|
|
1841
|
+
}
|
|
1842
|
+
process.exit(1);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
// src/vite/plugin.ts
|
|
1849
|
+
function nodeRed(options = {}) {
|
|
1850
|
+
const { outDir = DEFAULT_OUTPUT_DIR } = options;
|
|
1851
|
+
const clientBuildOptions = mergeOptions(
|
|
1852
|
+
DEFAULT_CLIENT_BUILD_OPTIONS,
|
|
1853
|
+
options.clientBuildOptions
|
|
1854
|
+
);
|
|
1855
|
+
const serverBuildOptions = mergeOptions(
|
|
1856
|
+
DEFAULT_SERVER_BUILD_OPTIONS,
|
|
1857
|
+
options.serverBuildOptions
|
|
1858
|
+
);
|
|
1859
|
+
const nodeRedLauncherOptions = mergeOptions(
|
|
1860
|
+
DEFAULT_NODE_RED_LAUNCHER_OPTIONS,
|
|
1861
|
+
options.nodeRedLauncherOptions
|
|
1862
|
+
);
|
|
1863
|
+
const extraFilesCopyTargets = options.extraFilesCopyTargets ?? DEFAULT_EXTRA_FILES_COPY_TARGETS;
|
|
1864
|
+
const resolvedOutDir = path13.resolve(outDir);
|
|
1865
|
+
const buildContext = {
|
|
1866
|
+
outDir: resolvedOutDir,
|
|
1867
|
+
packageName: getPackageName(),
|
|
1868
|
+
isDev: process.env.NODE_ENV === "development"
|
|
1869
|
+
};
|
|
1870
|
+
const nodeRedLauncher = new NodeRedLauncher(
|
|
1871
|
+
resolvedOutDir,
|
|
1872
|
+
nodeRedLauncherOptions
|
|
1873
|
+
);
|
|
1874
|
+
return [
|
|
1875
|
+
serverPlugin({
|
|
1876
|
+
nodeRedLauncher,
|
|
1877
|
+
serverBuildOptions,
|
|
1878
|
+
clientBuildOptions,
|
|
1879
|
+
extraFilesCopyTargets,
|
|
1880
|
+
buildContext
|
|
1881
|
+
}),
|
|
1882
|
+
buildPlugin({
|
|
1883
|
+
serverBuildOptions,
|
|
1884
|
+
clientBuildOptions,
|
|
1885
|
+
extraFilesCopyTargets,
|
|
1886
|
+
buildContext
|
|
1887
|
+
})
|
|
1888
|
+
];
|
|
1889
|
+
}
|
|
1890
|
+
export {
|
|
1891
|
+
defineRuntimeSettings,
|
|
1892
|
+
nodeRed
|
|
1893
|
+
};
|