@bonsae/nrg 0.18.1 → 0.18.3

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 CHANGED
@@ -145,13 +145,21 @@ See the [consumer template](https://github.com/AllanOricil/node-red-vue-template
145
145
 
146
146
  ## Testing
147
147
 
148
- NRG provides four test libraries and bundles all test infrastructure (happy-dom, Playwright, Vue plugin, browser utilities) as direct dependencies. The only package you need to install yourself is `vitest`:
148
+ NRG provides four test libraries and bundles most test infrastructure as direct dependencies. Install `vitest` plus any optional peer dependencies you need:
149
149
 
150
150
  ```bash
151
151
  pnpm add -D vitest
152
152
  ```
153
153
 
154
- Coverage providers are optional peer dependencies — install `@vitest/coverage-v8` or `@vitest/coverage-istanbul` only if you run with `--coverage`.
154
+ Optional peer dependencies:
155
+
156
+ | Package | When to install |
157
+ | --- | --- |
158
+ | `@vitest/browser-playwright` | Component tests (Playwright browser provider for Vitest) |
159
+ | `playwright` | Component tests or E2E tests (direct `import` in test files) |
160
+ | `vitest-browser-vue` | Component tests (`render` helper for Vue components) |
161
+ | `@vitest/coverage-v8` | Coverage with `--coverage` (V8 provider) |
162
+ | `@vitest/coverage-istanbul` | Coverage with `--coverage` (Istanbul provider) |
155
163
 
156
164
  - `@bonsae/nrg/test/server/unit` — server-side unit tests
157
165
  - `@bonsae/nrg/test/client/unit` — client-side unit tests (TypeScript logic)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonsae/nrg",
3
- "version": "0.18.1",
3
+ "version": "0.18.3",
4
4
  "description": "NRG framework — build Node-RED nodes with Vue 3, TypeScript, and JSON Schema",
5
5
  "author": "Allan Oricil <allanoricil@duck.com>",
6
6
  "license": "MIT",
@@ -81,7 +81,10 @@
81
81
  "vitest": "^4.0.0",
82
82
  "vue": "^3.5.14",
83
83
  "@vitest/coverage-istanbul": "^4.0.0",
84
- "@vitest/coverage-v8": "^4.0.0"
84
+ "@vitest/coverage-v8": "^4.0.0",
85
+ "@vitest/browser-playwright": "^4.0.0",
86
+ "playwright": "^1.50.0",
87
+ "vitest-browser-vue": "^2.0.0"
85
88
  },
86
89
  "peerDependenciesMeta": {
87
90
  "@vitest/coverage-istanbul": {
@@ -89,6 +92,15 @@
89
92
  },
90
93
  "@vitest/coverage-v8": {
91
94
  "optional": true
95
+ },
96
+ "@vitest/browser-playwright": {
97
+ "optional": true
98
+ },
99
+ "playwright": {
100
+ "optional": true
101
+ },
102
+ "vitest-browser-vue": {
103
+ "optional": true
92
104
  }
93
105
  },
94
106
  "dependencies": {
@@ -1995,7 +1995,7 @@ async function build2(clientBuildOptions, buildContext) {
1995
1995
  }
1996
1996
 
1997
1997
  // src/vite/node-red-launcher.ts
1998
- import { spawn } from "child_process";
1998
+ import { spawn, execSync } from "child_process";
1999
1999
  import getPort from "get-port";
2000
2000
  import detect from "detect-port";
2001
2001
  import { builtinModules as builtinModules2 } from "module";
@@ -2003,6 +2003,7 @@ import treeKill from "tree-kill";
2003
2003
  import fs10 from "fs";
2004
2004
  import os from "os";
2005
2005
  import path10 from "path";
2006
+ import { pathToFileURL as pathToFileURL3 } from "url";
2006
2007
  import { build as esbuild } from "esbuild";
2007
2008
 
2008
2009
  // src/vite/async-utils.ts
@@ -2114,7 +2115,7 @@ var NodeRedLauncher = class {
2114
2115
  define: {
2115
2116
  "import.meta.dirname": JSON.stringify(settingsDir),
2116
2117
  "import.meta.filename": JSON.stringify(settingsFile),
2117
- "import.meta.url": JSON.stringify(`file://${settingsFile}`)
2118
+ "import.meta.url": JSON.stringify(pathToFileURL3(settingsFile).href)
2118
2119
  },
2119
2120
  external: [...nodeBuiltins2, "node-red", "@node-red/*"]
2120
2121
  });
@@ -2164,6 +2165,53 @@ module.exports = settings;
2164
2165
  this.compiledRuntimeSettingsFilepath = finalRuntimeSettingsFilepath;
2165
2166
  return finalRuntimeSettingsFilepath;
2166
2167
  }
2168
+ resolveNodeRedEntryPoint() {
2169
+ const resolverScript = path10.join(
2170
+ os.tmpdir(),
2171
+ `nrg-resolve-node-red-${process.pid}.cjs`
2172
+ );
2173
+ fs10.writeFileSync(
2174
+ resolverScript,
2175
+ `const fs = require("fs");
2176
+ const path = require("path");
2177
+ const isWin = process.platform === "win32";
2178
+ const binName = isWin ? "node-red.cmd" : "node-red";
2179
+ const dirs = process.env.PATH.split(path.delimiter);
2180
+ for (const d of dirs) {
2181
+ const f = path.join(d, binName);
2182
+ if (fs.existsSync(f)) {
2183
+ if (isWin) {
2184
+ const nodeRedDir = path.resolve(d, "..", "node-red");
2185
+ const pkg = JSON.parse(fs.readFileSync(path.join(nodeRedDir, "package.json"), "utf-8"));
2186
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin["node-red"];
2187
+ process.stdout.write(path.resolve(nodeRedDir, bin));
2188
+ } else {
2189
+ process.stdout.write(fs.realpathSync(f));
2190
+ }
2191
+ break;
2192
+ }
2193
+ }`
2194
+ );
2195
+ try {
2196
+ const entryPoint = execSync(
2197
+ `npx --yes -p ${this.nodeRedCommand} -c "node ${resolverScript}"`,
2198
+ { encoding: "utf-8", timeout: 12e4 }
2199
+ ).trim();
2200
+ if (!entryPoint || !fs10.existsSync(entryPoint)) {
2201
+ throw new NodeRedStartError(
2202
+ new Error(
2203
+ `Could not resolve node-red entry point: ${entryPoint || "(empty)"}`
2204
+ )
2205
+ );
2206
+ }
2207
+ return entryPoint;
2208
+ } finally {
2209
+ try {
2210
+ fs10.unlinkSync(resolverScript);
2211
+ } catch {
2212
+ }
2213
+ }
2214
+ }
2167
2215
  log(line) {
2168
2216
  if (line.includes("Server now running at")) {
2169
2217
  return;
@@ -2189,6 +2237,7 @@ module.exports = settings;
2189
2237
  this.port = await getPort({ port: this.preferredPort });
2190
2238
  }
2191
2239
  }
2240
+ const nodeRedEntryPoint = this.resolveNodeRedEntryPoint();
2192
2241
  const startProcess = () => {
2193
2242
  return new Promise(async (resolve, reject) => {
2194
2243
  try {
@@ -2197,11 +2246,10 @@ module.exports = settings;
2197
2246
  this.bufferedLogs = [];
2198
2247
  this.isReady = false;
2199
2248
  this.process = spawn(
2200
- "npx",
2201
- [this.nodeRedCommand, "-s", settingsPath, ...args],
2249
+ process.execPath,
2250
+ [nodeRedEntryPoint, "-s", settingsPath, ...args],
2202
2251
  {
2203
- stdio: ["ignore", "pipe", "pipe"],
2204
- shell: true
2252
+ stdio: ["ignore", "pipe", "pipe"]
2205
2253
  }
2206
2254
  );
2207
2255
  this.process.stdout?.on("data", (data) => {
package/vite/index.js CHANGED
@@ -107,7 +107,7 @@ function mergeOptions(defaults, overrides) {
107
107
  }
108
108
 
109
109
  // src/vite/node-red-launcher.ts
110
- import { spawn } from "child_process";
110
+ import { spawn, execSync } from "child_process";
111
111
  import getPort from "get-port";
112
112
  import detect from "detect-port";
113
113
  import { builtinModules } from "module";
@@ -115,6 +115,7 @@ import treeKill from "tree-kill";
115
115
  import fs2 from "fs";
116
116
  import os from "os";
117
117
  import path2 from "path";
118
+ import { pathToFileURL } from "url";
118
119
  import { build as esbuild } from "esbuild";
119
120
 
120
121
  // src/vite/async-utils.ts
@@ -332,7 +333,7 @@ var NodeRedLauncher = class {
332
333
  define: {
333
334
  "import.meta.dirname": JSON.stringify(settingsDir),
334
335
  "import.meta.filename": JSON.stringify(settingsFile),
335
- "import.meta.url": JSON.stringify(`file://${settingsFile}`)
336
+ "import.meta.url": JSON.stringify(pathToFileURL(settingsFile).href)
336
337
  },
337
338
  external: [...nodeBuiltins2, "node-red", "@node-red/*"]
338
339
  });
@@ -382,6 +383,53 @@ module.exports = settings;
382
383
  this.compiledRuntimeSettingsFilepath = finalRuntimeSettingsFilepath;
383
384
  return finalRuntimeSettingsFilepath;
384
385
  }
386
+ resolveNodeRedEntryPoint() {
387
+ const resolverScript = path2.join(
388
+ os.tmpdir(),
389
+ `nrg-resolve-node-red-${process.pid}.cjs`
390
+ );
391
+ fs2.writeFileSync(
392
+ resolverScript,
393
+ `const fs = require("fs");
394
+ const path = require("path");
395
+ const isWin = process.platform === "win32";
396
+ const binName = isWin ? "node-red.cmd" : "node-red";
397
+ const dirs = process.env.PATH.split(path.delimiter);
398
+ for (const d of dirs) {
399
+ const f = path.join(d, binName);
400
+ if (fs.existsSync(f)) {
401
+ if (isWin) {
402
+ const nodeRedDir = path.resolve(d, "..", "node-red");
403
+ const pkg = JSON.parse(fs.readFileSync(path.join(nodeRedDir, "package.json"), "utf-8"));
404
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin["node-red"];
405
+ process.stdout.write(path.resolve(nodeRedDir, bin));
406
+ } else {
407
+ process.stdout.write(fs.realpathSync(f));
408
+ }
409
+ break;
410
+ }
411
+ }`
412
+ );
413
+ try {
414
+ const entryPoint = execSync(
415
+ `npx --yes -p ${this.nodeRedCommand} -c "node ${resolverScript}"`,
416
+ { encoding: "utf-8", timeout: 12e4 }
417
+ ).trim();
418
+ if (!entryPoint || !fs2.existsSync(entryPoint)) {
419
+ throw new NodeRedStartError(
420
+ new Error(
421
+ `Could not resolve node-red entry point: ${entryPoint || "(empty)"}`
422
+ )
423
+ );
424
+ }
425
+ return entryPoint;
426
+ } finally {
427
+ try {
428
+ fs2.unlinkSync(resolverScript);
429
+ } catch {
430
+ }
431
+ }
432
+ }
385
433
  log(line) {
386
434
  if (line.includes("Server now running at")) {
387
435
  return;
@@ -407,6 +455,7 @@ module.exports = settings;
407
455
  this.port = await getPort({ port: this.preferredPort });
408
456
  }
409
457
  }
458
+ const nodeRedEntryPoint = this.resolveNodeRedEntryPoint();
410
459
  const startProcess = () => {
411
460
  return new Promise(async (resolve, reject) => {
412
461
  try {
@@ -415,11 +464,10 @@ module.exports = settings;
415
464
  this.bufferedLogs = [];
416
465
  this.isReady = false;
417
466
  this.process = spawn(
418
- "npx",
419
- [this.nodeRedCommand, "-s", settingsPath, ...args],
467
+ process.execPath,
468
+ [nodeRedEntryPoint, "-s", settingsPath, ...args],
420
469
  {
421
- stdio: ["ignore", "pipe", "pipe"],
422
- shell: true
470
+ stdio: ["ignore", "pipe", "pipe"]
423
471
  }
424
472
  );
425
473
  this.process.stdout?.on("data", (data) => {
@@ -1262,7 +1310,7 @@ import path11 from "path";
1262
1310
  // src/vite/client/plugins/help-generator.ts
1263
1311
  import fs6 from "fs";
1264
1312
  import path6 from "path";
1265
- import { pathToFileURL } from "url";
1313
+ import { pathToFileURL as pathToFileURL2 } from "url";
1266
1314
  import { createRequire } from "module";
1267
1315
 
1268
1316
  // src/vite/client/plugins/help-i18n.ts
@@ -1700,7 +1748,7 @@ function helpGenerator(options) {
1700
1748
  let packageFn;
1701
1749
  try {
1702
1750
  if (fs6.existsSync(esmPath)) {
1703
- const fileUrl = pathToFileURL(esmPath).href + `?t=${Date.now()}`;
1751
+ const fileUrl = pathToFileURL2(esmPath).href + `?t=${Date.now()}`;
1704
1752
  const mod = await import(fileUrl);
1705
1753
  packageFn = mod?.default ?? mod;
1706
1754
  } else if (fs6.existsSync(cjsPath)) {
@@ -2049,7 +2097,7 @@ function minifier() {
2049
2097
 
2050
2098
  // src/vite/client/plugins/node-definitions-inliner.ts
2051
2099
  import { createRequire as createRequire2 } from "module";
2052
- import { pathToFileURL as pathToFileURL2 } from "url";
2100
+ import { pathToFileURL as pathToFileURL3 } from "url";
2053
2101
  import path9 from "path";
2054
2102
  import fs9 from "fs";
2055
2103
  import mime2 from "mime-types";
@@ -2105,7 +2153,7 @@ function nodeDefinitionsInliner(serverOutDir, entryPath, iconsDir, componentsDir
2105
2153
  const cjsEntryPath = path9.resolve(serverOutDir, "index.js");
2106
2154
  let packageFn;
2107
2155
  if (fs9.existsSync(esmEntryPath)) {
2108
- const fileUrl = pathToFileURL2(esmEntryPath).href + `?t=${Date.now()}`;
2156
+ const fileUrl = pathToFileURL3(esmEntryPath).href + `?t=${Date.now()}`;
2109
2157
  const mod = await import(fileUrl);
2110
2158
  packageFn = mod?.default ?? mod;
2111
2159
  } else if (fs9.existsSync(cjsEntryPath)) {