@bonsae/nrg 0.6.0 → 0.6.2
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 +5 -5
- package/package.json +14 -75
- package/{build/server → server}/index.cjs +1 -1
- package/{src/core/client → shims}/components.d.ts +2 -0
- package/{src/tsconfig → tsconfig}/client.json +3 -3
- package/types/client.d.ts +37 -0
- package/types/index.d.ts +211 -0
- package/types/server.d.ts +2293 -0
- package/types/vite.d.ts +12 -0
- package/{build/vite → vite}/index.js +95 -0
- package/build/vite/utils.js +0 -56
- package/src/core/client/app.vue +0 -185
- package/src/core/client/components/node-red-config-input.vue +0 -79
- package/src/core/client/components/node-red-editor-input.vue +0 -307
- package/src/core/client/components/node-red-input-label.vue +0 -53
- package/src/core/client/components/node-red-input.vue +0 -93
- package/src/core/client/components/node-red-json-schema-form.vue +0 -444
- package/src/core/client/components/node-red-select-input.vue +0 -108
- package/src/core/client/components/node-red-toggle.vue +0 -115
- package/src/core/client/components/node-red-typed-input.vue +0 -158
- package/src/core/client/index.ts +0 -500
- package/src/core/client/tsconfig.json +0 -18
- package/src/core/constants.ts +0 -18
- package/src/core/errors.ts +0 -9
- package/src/core/server/api/index.ts +0 -1
- package/src/core/server/api/serve-nrg-resources.ts +0 -54
- package/src/core/server/index.ts +0 -190
- package/src/core/server/nodes/config-node.ts +0 -67
- package/src/core/server/nodes/factories.ts +0 -133
- package/src/core/server/nodes/index.ts +0 -5
- package/src/core/server/nodes/io-node.ts +0 -179
- package/src/core/server/nodes/node.ts +0 -259
- package/src/core/server/nodes/types/config-node.ts +0 -28
- package/src/core/server/nodes/types/factories.ts +0 -115
- package/src/core/server/nodes/types/index.ts +0 -4
- package/src/core/server/nodes/types/io-node.ts +0 -40
- package/src/core/server/nodes/types/node.ts +0 -41
- package/src/core/server/nodes/utils.ts +0 -106
- package/src/core/server/schemas/base.ts +0 -66
- package/src/core/server/schemas/index.ts +0 -3
- package/src/core/server/schemas/type.ts +0 -95
- package/src/core/server/schemas/types/index.ts +0 -82
- package/src/core/server/tsconfig.json +0 -17
- package/src/core/server/types/index.ts +0 -220
- package/src/core/server/utils.ts +0 -56
- package/src/core/server/validator.ts +0 -36
- package/src/core/validator.ts +0 -222
- package/src/index.ts +0 -2
- package/src/types.ts +0 -189
- package/src/utils.ts +0 -20
- package/src/vite/async-utils.ts +0 -61
- package/src/vite/client/build.ts +0 -227
- package/src/vite/client/index.ts +0 -1
- package/src/vite/client/plugins/html-generator.ts +0 -75
- package/src/vite/client/plugins/index.ts +0 -5
- package/src/vite/client/plugins/locales-generator.ts +0 -126
- package/src/vite/client/plugins/minifier.ts +0 -23
- package/src/vite/client/plugins/node-definitions-inliner.ts +0 -275
- package/src/vite/client/plugins/static-copy.ts +0 -43
- package/src/vite/defaults.ts +0 -77
- package/src/vite/errors.ts +0 -37
- package/src/vite/index.ts +0 -2
- package/src/vite/logger.ts +0 -94
- package/src/vite/node-red-launcher.ts +0 -344
- package/src/vite/plugin.ts +0 -61
- package/src/vite/plugins/build.ts +0 -85
- package/src/vite/plugins/index.ts +0 -2
- package/src/vite/plugins/server.ts +0 -267
- package/src/vite/server/build.ts +0 -124
- package/src/vite/server/index.ts +0 -1
- package/src/vite/server/plugins/index.ts +0 -3
- package/src/vite/server/plugins/output-wrapper.ts +0 -109
- package/src/vite/server/plugins/package-json-generator.ts +0 -203
- package/src/vite/server/plugins/type-generator.ts +0 -285
- package/src/vite/types.ts +0 -174
- package/src/vite/utils.ts +0 -72
- /package/{build/index.js → index.js} +0 -0
- /package/{build/server → server}/resources/nrg-client.js +0 -0
- /package/{build/server → server}/resources/vue.esm-browser.js +0 -0
- /package/{build/server → server}/resources/vue.esm-browser.prod.js +0 -0
- /package/{src/core/client → shims}/globals.d.ts +0 -0
- /package/{src/core/client → shims}/shims-vue.d.ts +0 -0
- /package/{src/tsconfig → tsconfig}/base.json +0 -0
- /package/{src/tsconfig → tsconfig}/server.json +0 -0
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import type { Plugin, FSWatcher, ViteDevServer } from "vite";
|
|
2
|
-
import chokidar from "chokidar";
|
|
3
|
-
import path from "path";
|
|
4
|
-
const color = {
|
|
5
|
-
dim: (s: string) => `\x1b[2m${s}\x1b[0m`,
|
|
6
|
-
cyan: (s: string) => `\x1b[36m${s}\x1b[0m`,
|
|
7
|
-
green: (s: string) => `\x1b[32m${s}\x1b[0m`,
|
|
8
|
-
yellow: (s: string) => `\x1b[33m${s}\x1b[0m`,
|
|
9
|
-
};
|
|
10
|
-
import type { ServerPluginOptions } from "../types";
|
|
11
|
-
import { debounce } from "../async-utils";
|
|
12
|
-
import { BuildError, NodeRedStartError } from "../errors";
|
|
13
|
-
import { logger } from "../logger";
|
|
14
|
-
import { cleanDir, copyFiles } from "../utils";
|
|
15
|
-
import { build as buildServer } from "../server";
|
|
16
|
-
import { build as buildClient } from "../client";
|
|
17
|
-
|
|
18
|
-
function serverPlugin(options: ServerPluginOptions): Plugin {
|
|
19
|
-
const {
|
|
20
|
-
nodeRedLauncher,
|
|
21
|
-
serverBuildOptions,
|
|
22
|
-
clientBuildOptions,
|
|
23
|
-
extraFilesCopyTargets,
|
|
24
|
-
buildContext,
|
|
25
|
-
} = options;
|
|
26
|
-
|
|
27
|
-
let nodeRedPort: number;
|
|
28
|
-
let initialStartDone = false;
|
|
29
|
-
let isStarting = false;
|
|
30
|
-
let isShuttingDown = false;
|
|
31
|
-
let shutdownStartTime = 0;
|
|
32
|
-
let pendingStart = false;
|
|
33
|
-
let server: ViteDevServer;
|
|
34
|
-
let watcher: FSWatcher | null = null;
|
|
35
|
-
|
|
36
|
-
const build = async (clean: boolean = false) => {
|
|
37
|
-
if (clean) {
|
|
38
|
-
logger.startSpinner("Cleaning");
|
|
39
|
-
cleanDir(buildContext.outDir);
|
|
40
|
-
logger.stopSpinner("Cleaned");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
logger.startSpinner("Building");
|
|
44
|
-
await buildServer(serverBuildOptions, buildContext);
|
|
45
|
-
await buildClient(clientBuildOptions, buildContext);
|
|
46
|
-
logger.stopSpinner("Built");
|
|
47
|
-
|
|
48
|
-
if (extraFilesCopyTargets.length) {
|
|
49
|
-
logger.startSpinner("Copying extra files");
|
|
50
|
-
copyFiles(extraFilesCopyTargets, buildContext.outDir);
|
|
51
|
-
logger.stopSpinner("Copied extra files");
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const start = async (clean: boolean = false) => {
|
|
56
|
-
if (isStarting) {
|
|
57
|
-
pendingStart = true;
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
isStarting = true;
|
|
62
|
-
pendingStart = false;
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
await nodeRedLauncher.stop();
|
|
66
|
-
await build(clean);
|
|
67
|
-
|
|
68
|
-
logger.startSpinner("Starting Node-RED");
|
|
69
|
-
nodeRedPort = await nodeRedLauncher.start();
|
|
70
|
-
logger.stopSpinner("Node-RED started");
|
|
71
|
-
|
|
72
|
-
const proxyConfig = server.config.server.proxy;
|
|
73
|
-
if (proxyConfig && typeof proxyConfig === "object") {
|
|
74
|
-
const rule = proxyConfig["^/.*"];
|
|
75
|
-
if (rule && typeof rule === "object") {
|
|
76
|
-
(rule as any).target = `http://127.0.0.1:${nodeRedPort}`;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
logger.success("Ready");
|
|
80
|
-
} catch (error) {
|
|
81
|
-
if (error instanceof BuildError) {
|
|
82
|
-
logger.error(`Rebuild failed: ${error.message}`);
|
|
83
|
-
} else if (error instanceof NodeRedStartError) {
|
|
84
|
-
logger.error("Failed to restart Node-RED");
|
|
85
|
-
} else {
|
|
86
|
-
logger.error(`Unexpected error: ${(error as Error).message}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
throw error;
|
|
90
|
-
} finally {
|
|
91
|
-
isStarting = false;
|
|
92
|
-
|
|
93
|
-
if (pendingStart) {
|
|
94
|
-
start(false);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const printServerUrls = (
|
|
100
|
-
vitePort: number,
|
|
101
|
-
nodeRedPorts: {
|
|
102
|
-
actual: number;
|
|
103
|
-
preferred: number;
|
|
104
|
-
},
|
|
105
|
-
) => {
|
|
106
|
-
console.log();
|
|
107
|
-
console.log(
|
|
108
|
-
` ${color.cyan("Vite")} ${color.dim("➜")} ${color.cyan(`http://127.0.0.1:${vitePort}/`)}`,
|
|
109
|
-
);
|
|
110
|
-
if (nodeRedPorts.actual != nodeRedPorts.preferred) {
|
|
111
|
-
console.log(
|
|
112
|
-
` ${color.green("Node-RED")} ${color.dim("➜")} ${color.green(`http://127.0.0.1:${nodeRedPorts.actual}/`)} ${color.yellow(`(port ${nodeRedPorts.preferred} was in use)`)}`,
|
|
113
|
-
);
|
|
114
|
-
} else {
|
|
115
|
-
console.log(
|
|
116
|
-
` ${color.green("Node-RED")} ${color.dim("➜")} ${color.green(`http://127.0.0.1:${nodeRedPorts.actual}/`)}`,
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
console.log();
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
name: "vite-plugin-node-red:server",
|
|
124
|
-
apply: "serve",
|
|
125
|
-
|
|
126
|
-
config() {
|
|
127
|
-
return {
|
|
128
|
-
appType: "custom",
|
|
129
|
-
server: {
|
|
130
|
-
host: "127.0.0.1",
|
|
131
|
-
proxy: {
|
|
132
|
-
"^/.*": {
|
|
133
|
-
target: `http://127.0.0.1:${nodeRedLauncher.preferredPort}`,
|
|
134
|
-
changeOrigin: true,
|
|
135
|
-
ws: true,
|
|
136
|
-
configure: (proxy) => {
|
|
137
|
-
proxy.on("error", () => {
|
|
138
|
-
if (nodeRedPort) {
|
|
139
|
-
(proxy as any).options.target =
|
|
140
|
-
`http://127.0.0.1:${nodeRedPort}`;
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
watch: {
|
|
147
|
-
// NOTE: this is necessary to avoid default hmr in .html files. Watcher is configured manually
|
|
148
|
-
ignored: ["**/*"],
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
customLogger: {
|
|
152
|
-
...console,
|
|
153
|
-
info: () => {},
|
|
154
|
-
warn: console.warn,
|
|
155
|
-
error: console.error,
|
|
156
|
-
warnOnce: () => {},
|
|
157
|
-
hasWarned: false,
|
|
158
|
-
clearScreen: () => {},
|
|
159
|
-
hasErrorLogged: () => false,
|
|
160
|
-
},
|
|
161
|
-
};
|
|
162
|
-
},
|
|
163
|
-
|
|
164
|
-
async configureServer(viteServer) {
|
|
165
|
-
server = viteServer;
|
|
166
|
-
|
|
167
|
-
logger.intro();
|
|
168
|
-
await start(true);
|
|
169
|
-
initialStartDone = true;
|
|
170
|
-
|
|
171
|
-
printServerUrls(server.config.server.port ?? 5173, {
|
|
172
|
-
actual: nodeRedPort,
|
|
173
|
-
preferred: nodeRedLauncher.preferredPort,
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
logger.startGroup("Node-RED");
|
|
177
|
-
nodeRedLauncher.flushLogs();
|
|
178
|
-
|
|
179
|
-
const serverSrcDir = path.resolve(
|
|
180
|
-
serverBuildOptions.srcDir ?? "./server",
|
|
181
|
-
);
|
|
182
|
-
const clientSrcDir = path.resolve(
|
|
183
|
-
clientBuildOptions.srcDir ?? "./client",
|
|
184
|
-
);
|
|
185
|
-
const localesDocsDir = path.resolve(
|
|
186
|
-
clientBuildOptions.locales?.docsDir ?? "./locales/docs",
|
|
187
|
-
);
|
|
188
|
-
const localesLabelsDir = path.resolve(
|
|
189
|
-
clientBuildOptions.locales?.labelsDir ?? "./locales/labels",
|
|
190
|
-
);
|
|
191
|
-
const iconsDir = path.resolve(
|
|
192
|
-
clientBuildOptions.staticDirs?.icons ??
|
|
193
|
-
path.join(path.dirname(clientSrcDir), "icons"),
|
|
194
|
-
);
|
|
195
|
-
|
|
196
|
-
const watchPaths = [
|
|
197
|
-
serverSrcDir,
|
|
198
|
-
clientSrcDir,
|
|
199
|
-
localesDocsDir,
|
|
200
|
-
localesLabelsDir,
|
|
201
|
-
iconsDir,
|
|
202
|
-
];
|
|
203
|
-
|
|
204
|
-
watcher = chokidar.watch(watchPaths, {
|
|
205
|
-
ignoreInitial: true,
|
|
206
|
-
persistent: true,
|
|
207
|
-
ignored: ["**/node_modules/**", "**/dist/**", "**/.node-red/**"],
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
const debounceBeforeStart = debounce(
|
|
211
|
-
() => start(false),
|
|
212
|
-
nodeRedLauncher.restartDelay ?? 1000,
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
const handleFileChange = (file: string, event: string) => {
|
|
216
|
-
if (!initialStartDone) return;
|
|
217
|
-
logger.info(`${event}: ${path.relative(process.cwd(), file)}`);
|
|
218
|
-
debounceBeforeStart();
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
watcher.on("change", (file) => handleFileChange(file, "Changed"));
|
|
222
|
-
watcher.on("add", (file) => handleFileChange(file, "Added"));
|
|
223
|
-
watcher.on("unlink", (file) => handleFileChange(file, "Deleted"));
|
|
224
|
-
|
|
225
|
-
process.on("SIGINT", () => {
|
|
226
|
-
const now = Date.now();
|
|
227
|
-
|
|
228
|
-
// NOTE: enable force exit only if more than 1 second has passed since first Ctrl+C because multiple SIGINT are triggered at once
|
|
229
|
-
if (isShuttingDown && now - shutdownStartTime > 1000) {
|
|
230
|
-
const pid = nodeRedLauncher.pid;
|
|
231
|
-
if (pid) {
|
|
232
|
-
try {
|
|
233
|
-
treeKill(pid, "SIGKILL");
|
|
234
|
-
} catch {
|
|
235
|
-
/* empty */
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
logger.endGroup("Node-RED Stopped");
|
|
239
|
-
process.exit(1);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (isShuttingDown) {
|
|
243
|
-
// NOTE: ignoring sequential SIGNINT when it is already shutting down
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
isShuttingDown = true;
|
|
248
|
-
shutdownStartTime = now;
|
|
249
|
-
|
|
250
|
-
logger.warn("Shutting down gracefully... (Ctrl+C again to force)");
|
|
251
|
-
|
|
252
|
-
nodeRedLauncher
|
|
253
|
-
.stop(true)
|
|
254
|
-
.then(() => {
|
|
255
|
-
nodeRedLauncher.cleanup();
|
|
256
|
-
logger.endGroup("Node-RED Stopped");
|
|
257
|
-
process.exit(0);
|
|
258
|
-
})
|
|
259
|
-
.catch(() => {
|
|
260
|
-
process.exit(1);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
},
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export { serverPlugin };
|
package/src/vite/server/build.ts
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import type { InlineConfig } from "vite";
|
|
2
|
-
import { build as viteBuild } from "vite";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import { BuildError } from "../errors";
|
|
6
|
-
import { logger } from "../logger";
|
|
7
|
-
import type { ServerBuildOptions, BuildContext } from "../types";
|
|
8
|
-
import {
|
|
9
|
-
packageJsonGenerator,
|
|
10
|
-
typeGenerator,
|
|
11
|
-
cjsWrapper,
|
|
12
|
-
esmWrapper,
|
|
13
|
-
} from "./plugins";
|
|
14
|
-
|
|
15
|
-
async function build(
|
|
16
|
-
serverOpts: ServerBuildOptions,
|
|
17
|
-
buildContext: BuildContext,
|
|
18
|
-
): Promise<void> {
|
|
19
|
-
const {
|
|
20
|
-
srcDir = "./server",
|
|
21
|
-
entry = "index.ts",
|
|
22
|
-
format = "esm",
|
|
23
|
-
bundled = [],
|
|
24
|
-
types = true,
|
|
25
|
-
nodeTarget = "node22",
|
|
26
|
-
plugins: userPlugins = [],
|
|
27
|
-
} = serverOpts;
|
|
28
|
-
|
|
29
|
-
const entries = Array.isArray(entry) ? entry : [entry];
|
|
30
|
-
const resolvedSrcDir = path.resolve(srcDir);
|
|
31
|
-
const entryPoints = {};
|
|
32
|
-
for (const entry of entries) {
|
|
33
|
-
const entryFilePath = path.join(resolvedSrcDir, entry);
|
|
34
|
-
if (fs.existsSync(entryFilePath)) {
|
|
35
|
-
const fileName = entry.replace(/\.ts$/, "");
|
|
36
|
-
entryPoints[fileName] = entryFilePath;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (Object.keys(entryPoints).length === 0) {
|
|
40
|
-
logger.warn("No server entry points found");
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const isEsm = format === "esm";
|
|
45
|
-
|
|
46
|
-
const plugins = [
|
|
47
|
-
packageJsonGenerator({
|
|
48
|
-
outDir: buildContext.outDir,
|
|
49
|
-
bundled,
|
|
50
|
-
types: types && !buildContext.isDev,
|
|
51
|
-
entryNames: Object.keys(entryPoints),
|
|
52
|
-
format,
|
|
53
|
-
}),
|
|
54
|
-
isEsm ? esmWrapper() : cjsWrapper(),
|
|
55
|
-
...userPlugins,
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
if (types && !buildContext.isDev) {
|
|
59
|
-
plugins.push(
|
|
60
|
-
...typeGenerator({
|
|
61
|
-
srcDir: resolvedSrcDir,
|
|
62
|
-
outDir: buildContext.outDir,
|
|
63
|
-
entryFiles: Object.values(entryPoints),
|
|
64
|
-
}),
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const config: InlineConfig = {
|
|
69
|
-
configFile: false,
|
|
70
|
-
logLevel: "warn",
|
|
71
|
-
plugins,
|
|
72
|
-
build: {
|
|
73
|
-
outDir: buildContext.outDir,
|
|
74
|
-
emptyOutDir: false,
|
|
75
|
-
sourcemap: buildContext.isDev ? "inline" : true,
|
|
76
|
-
minify: !buildContext.isDev,
|
|
77
|
-
lib: {
|
|
78
|
-
entry: entryPoints,
|
|
79
|
-
formats: [isEsm ? "es" : "cjs"],
|
|
80
|
-
},
|
|
81
|
-
rollupOptions: {
|
|
82
|
-
output: {
|
|
83
|
-
entryFileNames: isEsm ? "[name].mjs" : "[name].js",
|
|
84
|
-
exports: isEsm ? undefined : "auto",
|
|
85
|
-
preserveModules: false,
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
esbuild: {
|
|
90
|
-
platform: "node",
|
|
91
|
-
target: nodeTarget,
|
|
92
|
-
keepNames: true,
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
await viteBuild(config);
|
|
98
|
-
|
|
99
|
-
// Generate CJS bridge so Node-RED can require() the ESM bundle
|
|
100
|
-
if (isEsm) {
|
|
101
|
-
const bridgeCode = `\
|
|
102
|
-
'use strict';
|
|
103
|
-
// CJS bridge — auto-generated by @bonsae/nrg/vite
|
|
104
|
-
// Node-RED uses require() to load packages. This bridge delegates to the ESM bundle.
|
|
105
|
-
module.exports = function (RED) {
|
|
106
|
-
(async () => {
|
|
107
|
-
const mod = await import("./index.mjs");
|
|
108
|
-
if (typeof mod.default !== 'function') {
|
|
109
|
-
throw new Error('ESM bundle must export a default function(RED)');
|
|
110
|
-
}
|
|
111
|
-
await mod.default(RED);
|
|
112
|
-
})().catch(function (err) {
|
|
113
|
-
RED.log.error('Failed to load ESM bundle: ' + err.message);
|
|
114
|
-
});
|
|
115
|
-
};
|
|
116
|
-
`;
|
|
117
|
-
fs.writeFileSync(path.join(buildContext.outDir, "index.js"), bridgeCode);
|
|
118
|
-
}
|
|
119
|
-
} catch (error) {
|
|
120
|
-
throw new BuildError("server", error as Error);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export { build };
|
package/src/vite/server/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { build } from "./build";
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from "vite";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Appends a CJS footer so Node-RED can load the package.
|
|
5
|
-
*
|
|
6
|
-
* esbuild CJS output for `export default value` can take two forms:
|
|
7
|
-
* (a) module.exports = value (no __esModule flag)
|
|
8
|
-
* (b) exports.__esModule=true; exports.default = value
|
|
9
|
-
* Both are normalised into `_exp` before branching:
|
|
10
|
-
*
|
|
11
|
-
* - Declarative manifest { nodes }: calls registerTypes(nodes)
|
|
12
|
-
* - Callable function with .nodes: attaches class properties
|
|
13
|
-
*
|
|
14
|
-
* Must be added to the server build in both dev and production modes.
|
|
15
|
-
*/
|
|
16
|
-
function cjsWrapper(): Plugin {
|
|
17
|
-
return {
|
|
18
|
-
name: "vite-plugin-node-red:server:cjs-wrapper",
|
|
19
|
-
renderChunk(code, chunk, outputOptions) {
|
|
20
|
-
if (!chunk.isEntry || outputOptions.format !== "cjs") return null;
|
|
21
|
-
const footer =
|
|
22
|
-
`(function(){` +
|
|
23
|
-
`var _exp=module.exports&&module.exports.__esModule?module.exports.default:module.exports;` +
|
|
24
|
-
`if(_exp&&typeof _exp==="object"&&Array.isArray(_exp.nodes)){` +
|
|
25
|
-
`var _nrg=require("@bonsae/nrg/server");` +
|
|
26
|
-
`module.exports=_nrg.registerTypes(_exp.nodes);` +
|
|
27
|
-
`}` +
|
|
28
|
-
`else if(typeof _exp==="function"&&Array.isArray(_exp.nodes)){` +
|
|
29
|
-
`module.exports=_exp;` +
|
|
30
|
-
`_exp.nodes.forEach(function(cls){` +
|
|
31
|
-
`if(cls&&cls.type){` +
|
|
32
|
-
`_exp[cls.type.replace(/(?:^|[-_])(\\w)/g,function(_,c){return c.toUpperCase();})] = cls;` +
|
|
33
|
-
`}` +
|
|
34
|
-
`});` +
|
|
35
|
-
`}` +
|
|
36
|
-
`})();`;
|
|
37
|
-
return { code: `${code}\n${footer}`, map: null };
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Transforms the ESM bundle so its default export is the Node-RED package
|
|
44
|
-
* function (result of registerTypes), not the raw { nodes: [...] } object.
|
|
45
|
-
*
|
|
46
|
-
* Also injects __dirname/__filename shims so user code that references them
|
|
47
|
-
* works in ESM.
|
|
48
|
-
*
|
|
49
|
-
* The ESM bundle is loaded in two contexts:
|
|
50
|
-
* - At runtime: CJS bridge does `import("./index.mjs")` then `mod.default(RED)`
|
|
51
|
-
* - At build time: node-definitions-inliner does `import("./index.mjs")` then `mod.default.nodes`
|
|
52
|
-
*
|
|
53
|
-
* Both work because registerTypes() returns an async function with a `.nodes` property.
|
|
54
|
-
*
|
|
55
|
-
* Must be added to the server build in both dev and production modes.
|
|
56
|
-
*/
|
|
57
|
-
function esmWrapper(): Plugin {
|
|
58
|
-
return {
|
|
59
|
-
name: "vite-plugin-node-red:server:esm-wrapper",
|
|
60
|
-
renderChunk(code, chunk, outputOptions) {
|
|
61
|
-
if (!chunk.isEntry || outputOptions.format !== "es") return null;
|
|
62
|
-
|
|
63
|
-
// Rollup ES output uses one of these forms:
|
|
64
|
-
// export { something as default };
|
|
65
|
-
// export default something;
|
|
66
|
-
// Replace with a wrapper that pipes through registerTypes.
|
|
67
|
-
const reNamed = /export\s*\{\s*(\w+)\s+as\s+default\s*\}\s*;?/;
|
|
68
|
-
const reDefault = /export\s+default\s+(\w+)\s*;?/;
|
|
69
|
-
|
|
70
|
-
let match = code.match(reNamed);
|
|
71
|
-
let varName: string | undefined;
|
|
72
|
-
let matchStr: string | undefined;
|
|
73
|
-
|
|
74
|
-
if (match) {
|
|
75
|
-
varName = match[1];
|
|
76
|
-
matchStr = match[0];
|
|
77
|
-
} else {
|
|
78
|
-
match = code.match(reDefault);
|
|
79
|
-
if (match) {
|
|
80
|
-
varName = match[1];
|
|
81
|
-
matchStr = match[0];
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (!varName || !matchStr) return null;
|
|
86
|
-
|
|
87
|
-
const header = [
|
|
88
|
-
`import { fileURLToPath as __nrgFileURLToPath } from "url";`,
|
|
89
|
-
`import { dirname as __nrgDirname } from "path";`,
|
|
90
|
-
`var __filename = __nrgFileURLToPath(import.meta.url);`,
|
|
91
|
-
`var __dirname = __nrgDirname(__filename);`,
|
|
92
|
-
`import { registerTypes as __nrgRegisterTypes } from "@bonsae/nrg/server";`,
|
|
93
|
-
``,
|
|
94
|
-
].join("\n");
|
|
95
|
-
const replacement = [
|
|
96
|
-
`var __nrgExport = ${varName};`,
|
|
97
|
-
`if (__nrgExport && typeof __nrgExport === "object" && Array.isArray(__nrgExport.nodes)) {`,
|
|
98
|
-
` __nrgExport = __nrgRegisterTypes(__nrgExport.nodes);`,
|
|
99
|
-
`}`,
|
|
100
|
-
`export { __nrgExport as default };`,
|
|
101
|
-
].join("\n");
|
|
102
|
-
|
|
103
|
-
const newCode = header + code.replace(matchStr, replacement);
|
|
104
|
-
return { code: newCode, map: null };
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export { cjsWrapper, esmWrapper };
|
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import type { Plugin } from "vite";
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { builtinModules } from "node:module";
|
|
5
|
-
import { logger } from "../../logger";
|
|
6
|
-
import type { PackageJson } from "../../types";
|
|
7
|
-
|
|
8
|
-
// NOTE: don't want to recreate it every time resolveId runs
|
|
9
|
-
const nodeBuiltins = new Set([
|
|
10
|
-
...builtinModules,
|
|
11
|
-
...builtinModules.map((m) => `node:${m}`),
|
|
12
|
-
]);
|
|
13
|
-
|
|
14
|
-
function buildTypesPath(entryName: string): string {
|
|
15
|
-
return `./${entryName}.d.ts`;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function buildOutputPath(entryName: string): string {
|
|
19
|
-
return `./${entryName}.js`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function buildExportKey(entryName: string): string {
|
|
23
|
-
return entryName === "index" ? "." : `./${entryName}`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function buildEsmOutputPath(entryName: string): string {
|
|
27
|
-
return `./${entryName}.mjs`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function generateExports(
|
|
31
|
-
entryNames: string[],
|
|
32
|
-
format: "cjs" | "esm" = "cjs",
|
|
33
|
-
): Record<string, unknown> {
|
|
34
|
-
const exports: Record<string, unknown> = {};
|
|
35
|
-
for (const name of entryNames) {
|
|
36
|
-
const key = buildExportKey(name);
|
|
37
|
-
if (format === "esm") {
|
|
38
|
-
exports[key] = {
|
|
39
|
-
types: buildTypesPath(name),
|
|
40
|
-
import: buildEsmOutputPath(name),
|
|
41
|
-
require: buildOutputPath(name),
|
|
42
|
-
default: buildOutputPath(name),
|
|
43
|
-
};
|
|
44
|
-
} else {
|
|
45
|
-
exports[key] = {
|
|
46
|
-
types: buildTypesPath(name),
|
|
47
|
-
require: buildOutputPath(name),
|
|
48
|
-
default: buildOutputPath(name),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return exports;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function patchExportsWithTypes(
|
|
56
|
-
existingExports: Record<string, unknown>,
|
|
57
|
-
entryNames: string[],
|
|
58
|
-
): Record<string, unknown> {
|
|
59
|
-
const patched: Record<string, unknown> = { ...existingExports };
|
|
60
|
-
for (const [key, value] of Object.entries(patched)) {
|
|
61
|
-
const entryName = key === "." ? "index" : key.replace(/^\.\//, "");
|
|
62
|
-
if (!entryNames.includes(entryName)) continue;
|
|
63
|
-
const typesPath = buildTypesPath(entryName);
|
|
64
|
-
if (typeof value === "string") {
|
|
65
|
-
patched[key] = { types: typesPath, require: value, default: value };
|
|
66
|
-
} else if (typeof value === "object" && value !== null) {
|
|
67
|
-
const condition = value as Record<string, unknown>;
|
|
68
|
-
if (!condition.types) {
|
|
69
|
-
patched[key] = { types: typesPath, ...condition };
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return patched;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function packageJsonGenerator(options: {
|
|
77
|
-
outDir: string;
|
|
78
|
-
bundled?: string[];
|
|
79
|
-
types?: boolean;
|
|
80
|
-
entryNames?: string[];
|
|
81
|
-
format?: "cjs" | "esm";
|
|
82
|
-
}): Plugin {
|
|
83
|
-
const {
|
|
84
|
-
outDir,
|
|
85
|
-
bundled = [],
|
|
86
|
-
types = false,
|
|
87
|
-
entryNames = [],
|
|
88
|
-
format = "cjs",
|
|
89
|
-
} = options;
|
|
90
|
-
|
|
91
|
-
const trackedDependencies = new Set<string>();
|
|
92
|
-
return {
|
|
93
|
-
name: "vite-plugin-node-red:server:package-json-generator",
|
|
94
|
-
enforce: "pre",
|
|
95
|
-
|
|
96
|
-
buildStart() {
|
|
97
|
-
trackedDependencies.clear();
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
resolveId: {
|
|
101
|
-
order: "pre",
|
|
102
|
-
handler(source, importer) {
|
|
103
|
-
if (!importer || source.startsWith(".") || source.startsWith("/")) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (nodeBuiltins.has(source)) {
|
|
108
|
-
return { id: source, external: true };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const packageName = source.startsWith("@")
|
|
112
|
-
? source.split("/").slice(0, 2).join("/")
|
|
113
|
-
: source.split("/")[0];
|
|
114
|
-
|
|
115
|
-
if (bundled.includes(packageName)) {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
trackedDependencies.add(packageName);
|
|
120
|
-
return { id: source, external: true };
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
|
|
124
|
-
closeBundle() {
|
|
125
|
-
const rootPackageJsonPath = path.resolve("./package.json");
|
|
126
|
-
if (!fs.existsSync(rootPackageJsonPath)) {
|
|
127
|
-
logger.warn(`package.json not found: ${rootPackageJsonPath}`);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const rootPackageJson = JSON.parse(
|
|
132
|
-
fs.readFileSync(rootPackageJsonPath, "utf-8"),
|
|
133
|
-
) as PackageJson;
|
|
134
|
-
|
|
135
|
-
const sourceDeps: Record<string, string> =
|
|
136
|
-
rootPackageJson.dependencies ?? {};
|
|
137
|
-
const peerDeps: Record<string, string> =
|
|
138
|
-
rootPackageJson.peerDependencies ?? {};
|
|
139
|
-
let distDependencies: Record<string, string> | undefined = {};
|
|
140
|
-
for (const dep of trackedDependencies) {
|
|
141
|
-
if (peerDeps[dep]) {
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
if (sourceDeps[dep]) {
|
|
145
|
-
distDependencies[dep] = sourceDeps[dep];
|
|
146
|
-
} else {
|
|
147
|
-
const dependencyPackageJsonPath = path.resolve(
|
|
148
|
-
`./node_modules/${dep}/package.json`,
|
|
149
|
-
);
|
|
150
|
-
if (fs.existsSync(dependencyPackageJsonPath)) {
|
|
151
|
-
const dependencyPackageJson = JSON.parse(
|
|
152
|
-
fs.readFileSync(dependencyPackageJsonPath, "utf-8"),
|
|
153
|
-
) as PackageJson;
|
|
154
|
-
distDependencies[dep] = `^${dependencyPackageJson.version}`;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (Object.keys(distDependencies).length === 0) {
|
|
160
|
-
distDependencies = undefined;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const distPackageJson: PackageJson = {
|
|
164
|
-
...rootPackageJson,
|
|
165
|
-
main: "index.js",
|
|
166
|
-
type: "commonjs",
|
|
167
|
-
devDependencies: undefined,
|
|
168
|
-
scripts: undefined,
|
|
169
|
-
dependencies: distDependencies,
|
|
170
|
-
keywords: [
|
|
171
|
-
...new Set([...(rootPackageJson.keywords ?? []), "node-red"]),
|
|
172
|
-
],
|
|
173
|
-
"node-red": {
|
|
174
|
-
nodes: { nodes: "index.js" },
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
if (types && entryNames.length > 0) {
|
|
179
|
-
const userExports = rootPackageJson.exports;
|
|
180
|
-
if (userExports && Object.keys(userExports).length > 0) {
|
|
181
|
-
distPackageJson.exports = patchExportsWithTypes(
|
|
182
|
-
userExports,
|
|
183
|
-
entryNames,
|
|
184
|
-
);
|
|
185
|
-
} else {
|
|
186
|
-
distPackageJson.exports = generateExports(entryNames, format);
|
|
187
|
-
if (entryNames.includes("index")) {
|
|
188
|
-
distPackageJson.types = "index.d.ts";
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (!fs.existsSync(outDir)) {
|
|
194
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
fs.writeFileSync(
|
|
198
|
-
path.join(outDir, "package.json"),
|
|
199
|
-
JSON.stringify(distPackageJson, null, 2),
|
|
200
|
-
);
|
|
201
|
-
},
|
|
202
|
-
};
|
|
203
|
-
}
|