@bonsae/nrg 0.27.0 → 0.28.1

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.
@@ -0,0 +1,48 @@
1
+ // src/eslint/index.ts
2
+ var nodeTypeMatchesFilename = {
3
+ meta: {
4
+ type: "problem",
5
+ docs: {
6
+ description: "require a node's static `type` to equal its filename in server/nodes"
7
+ },
8
+ schema: [],
9
+ messages: {
10
+ mismatch: `Node static type "{{type}}" must match the filename "{{expected}}.ts" \u2014 the type string keys the node's schema, component, icon and locale folder.`
11
+ }
12
+ },
13
+ create(context) {
14
+ const match = /[\\/]server[\\/]nodes[\\/]([^\\/]+)\.ts$/.exec(
15
+ context.filename
16
+ );
17
+ if (!match) return {};
18
+ const expected = match[1];
19
+ return {
20
+ "PropertyDefinition[static=true]"(node) {
21
+ if (node.key?.type === "Identifier" && node.key.name === "type" && node.value?.type === "Literal" && typeof node.value.value === "string" && node.value.value !== expected) {
22
+ context.report({
23
+ node: node.value,
24
+ messageId: "mismatch",
25
+ data: { type: node.value.value, expected }
26
+ });
27
+ }
28
+ }
29
+ };
30
+ }
31
+ };
32
+ var plugin = {
33
+ meta: { name: "@bonsae/nrg" },
34
+ rules: { "node-type-matches-filename": nodeTypeMatchesFilename }
35
+ };
36
+ var nrgConventions = {
37
+ plugins: { "@bonsae/nrg": plugin },
38
+ rules: {
39
+ "@bonsae/nrg/node-type-matches-filename": "error"
40
+ }
41
+ };
42
+ var index_default = nrgConventions;
43
+ export {
44
+ index_default as default,
45
+ nodeTypeMatchesFilename,
46
+ nrgConventions,
47
+ plugin
48
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonsae/nrg",
3
- "version": "0.27.0",
3
+ "version": "0.28.1",
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",
@@ -50,6 +50,7 @@
50
50
  "types": "./types/vite.d.ts",
51
51
  "default": "./vite/index.js"
52
52
  },
53
+ "./eslint": "./eslint/index.js",
53
54
  "./test/server/unit": {
54
55
  "types": "./types/test-server-unit.d.ts",
55
56
  "default": "./test/server/unit/index.js"
@@ -2306,12 +2306,12 @@ async function build2(clientBuildOptions, buildContext) {
2306
2306
  name = "NodeRedNodes",
2307
2307
  format = "es",
2308
2308
  licensePath = "./LICENSE",
2309
- locales,
2310
- staticDirs = {},
2309
+ publicDir,
2311
2310
  external = [],
2312
2311
  globals = {},
2313
2312
  manualChunks
2314
2313
  } = clientBuildOptions;
2314
+ const resourcesDir = buildContext.resourcesDir;
2315
2315
  const cacheDir = path10.resolve(
2316
2316
  "node_modules",
2317
2317
  ".nrg",
@@ -2332,9 +2332,7 @@ async function build2(clientBuildOptions, buildContext) {
2332
2332
  entryPath = cachedEntryPath;
2333
2333
  generatedEntry = true;
2334
2334
  }
2335
- const iconsDir = path10.resolve(
2336
- staticDirs.icons ?? path10.join(path10.dirname(path10.resolve(srcDir)), "icons")
2337
- );
2335
+ const iconsDir = path10.join(resourcesDir, "icons");
2338
2336
  const plugins = [
2339
2337
  vue(),
2340
2338
  nodeDefinitionsInliner(
@@ -2353,33 +2351,31 @@ async function build2(clientBuildOptions, buildContext) {
2353
2351
  licensePath: licensePath ? path10.resolve(licensePath) : void 0
2354
2352
  })
2355
2353
  );
2356
- if (locales) {
2357
- const { docsDir = "./locales/docs", labelsDir = "./locales/labels" } = locales;
2354
+ const localesDir = path10.join(resourcesDir, "locales");
2355
+ if (fs10.existsSync(localesDir)) {
2356
+ const docsDir = path10.join(localesDir, "docs");
2357
+ const labelsDir = path10.join(localesDir, "labels");
2358
2358
  const localesOutDir = path10.join(buildContext.outDir, "locales");
2359
2359
  plugins.push(
2360
- localesGenerator({
2361
- outDir: localesOutDir,
2362
- docsDir: path10.resolve(docsDir),
2363
- labelsDir: path10.resolve(labelsDir)
2364
- })
2360
+ localesGenerator({ outDir: localesOutDir, docsDir, labelsDir })
2365
2361
  );
2366
2362
  plugins.push(
2367
2363
  helpGenerator({
2368
2364
  outDir: buildContext.outDir,
2369
2365
  localesOutDir,
2370
- docsDir: path10.resolve(docsDir),
2371
- labelsDir: path10.resolve(labelsDir),
2366
+ docsDir,
2367
+ labelsDir,
2372
2368
  srcDir: buildContext.serverSrcDir
2373
2369
  })
2374
2370
  );
2375
2371
  }
2376
2372
  const copyTargets = [];
2377
- const publicDir = path10.resolve(
2378
- staticDirs.public ?? path10.join(srcDir, "public")
2373
+ const resolvedPublicDir = path10.resolve(
2374
+ publicDir ?? path10.join(srcDir, "public")
2379
2375
  );
2380
- if (fs10.existsSync(publicDir)) {
2376
+ if (fs10.existsSync(resolvedPublicDir)) {
2381
2377
  copyTargets.push({
2382
- src: publicDir,
2378
+ src: resolvedPublicDir,
2383
2379
  dest: path10.join(buildContext.outDir, "resources")
2384
2380
  });
2385
2381
  }
@@ -3061,7 +3057,8 @@ var NodeRedTestEnvironment = class {
3061
3057
  const buildContext = {
3062
3058
  outDir: this.outDir,
3063
3059
  packageName: this.options.packageName,
3064
- isDev: false
3060
+ isDev: false,
3061
+ resourcesDir: path13.join(this.projectDir, "src/resources")
3065
3062
  };
3066
3063
  const serverOpts = {
3067
3064
  srcDir: path13.join(this.projectDir, "src/server"),
@@ -19,15 +19,8 @@ interface ClientBuildOptions {
19
19
  base?: string;
20
20
  /** Path to LICENSE file to include in the HTML output. @default "./LICENSE" */
21
21
  licensePath?: string;
22
- /** Internationalization options for labels and docs. */
23
- locales?: LocalesOptions;
24
- /** Directories for static assets (icons, public files). */
25
- staticDirs?: {
26
- /** Directory containing node icons ({type}.png). @default "./src/icons" */
27
- icons?: string;
28
- /** Directory for public static files copied to dist/resources/. @default "./src/client/public" */
29
- public?: string;
30
- };
22
+ /** Directory for the editor's public static files, copied to dist/resources/. @default "./src/client/public" */
23
+ publicDir?: string;
31
24
  /** Modules to treat as external (not bundled). @default ["jquery", "node-red", "vue", "@bonsae/nrg/client"] */
32
25
  external?: string[];
33
26
  /** Global variable mappings for external modules. */
@@ -35,12 +28,6 @@ interface ClientBuildOptions {
35
28
  /** Custom chunk splitting function for Rollup. */
36
29
  manualChunks?: (id: string) => string | undefined;
37
30
  }
38
- interface LocalesOptions {
39
- /** Directory containing documentation files ({type}/{lang}.md or .html). @default "./src/locales/docs" */
40
- docsDir?: string;
41
- /** Directory containing label files ({type}/{lang}.json). @default "./src/locales/labels" */
42
- labelsDir?: string;
43
- }
44
31
  interface ServerBuildOptions {
45
32
  /** Source directory for server code. @default "./src/server" */
46
33
  srcDir?: string;
package/types/vite.d.ts CHANGED
@@ -6,6 +6,12 @@ export interface BuildContext {
6
6
  isDev: boolean;
7
7
  /** Resolved server source dir, scanned to recover `Unsafe<T>()` types for docs. */
8
8
  serverSrcDir?: string;
9
+ /**
10
+ * Resolved resources dir (default ./src/resources). Its convention subfolders
11
+ * are auto-handled: `icons/` and `locales/{docs,labels}/` run their pipelines,
12
+ * every other folder is copied verbatim to `dist/<name>`. @default ./src/resources
13
+ */
14
+ resourcesDir: string;
9
15
  }
10
16
  export interface BuildPluginOptions {
11
17
  serverBuildOptions: ServerBuildOptions;
@@ -30,15 +36,8 @@ export interface ClientBuildOptions {
30
36
  base?: string;
31
37
  /** Path to LICENSE file to include in the HTML output. @default "./LICENSE" */
32
38
  licensePath?: string;
33
- /** Internationalization options for labels and docs. */
34
- locales?: LocalesOptions;
35
- /** Directories for static assets (icons, public files). */
36
- staticDirs?: {
37
- /** Directory containing node icons ({type}.png). @default "./src/icons" */
38
- icons?: string;
39
- /** Directory for public static files copied to dist/resources/. @default "./src/client/public" */
40
- public?: string;
41
- };
39
+ /** Directory for the editor's public static files, copied to dist/resources/. @default "./src/client/public" */
40
+ publicDir?: string;
42
41
  /** Modules to treat as external (not bundled). @default ["jquery", "node-red", "vue", "@bonsae/nrg/client"] */
43
42
  external?: string[];
44
43
  /** Global variable mappings for external modules. */
@@ -52,12 +51,6 @@ export interface CopyTarget {
52
51
  /** Destination path relative to the output directory. */
53
52
  dest: string;
54
53
  }
55
- export interface LocalesOptions {
56
- /** Directory containing documentation files ({type}/{lang}.md or .html). @default "./src/locales/docs" */
57
- docsDir?: string;
58
- /** Directory containing label files ({type}/{lang}.json). @default "./src/locales/labels" */
59
- labelsDir?: string;
60
- }
61
54
  export interface LoggerOptions {
62
55
  name: string;
63
56
  prefix?: string;
@@ -93,8 +86,6 @@ export interface BuildOptions {
93
86
  server?: ServerBuildOptions;
94
87
  /** Options for building the client-side editor UI. */
95
88
  client?: ClientBuildOptions;
96
- /** Extra files to copy into the output directory (e.g., LICENSE, README). */
97
- extraFilesCopyTargets?: CopyTarget[];
98
89
  }
99
90
  /**
100
91
  * Options for the `nrg()` Vite plugin.
package/vite/index.js CHANGED
@@ -3,6 +3,8 @@ import path15 from "path";
3
3
 
4
4
  // src/vite/defaults.ts
5
5
  var DEFAULT_OUTPUT_DIR = "./dist";
6
+ var DEFAULT_DEV_OUTPUT_DIR = "./.nrg";
7
+ var DEFAULT_RESOURCES_DIR = "./src/resources";
6
8
  var DEFAULT_CLIENT_BUILD_OPTIONS = {
7
9
  srcDir: "./src/client",
8
10
  entry: "index.ts",
@@ -16,14 +18,7 @@ var DEFAULT_CLIENT_BUILD_OPTIONS = {
16
18
  "node-red": "RED",
17
19
  vue: "Vue"
18
20
  },
19
- locales: {
20
- docsDir: "./src/locales/docs",
21
- labelsDir: "./src/locales/labels"
22
- },
23
- staticDirs: {
24
- icons: "./src/icons",
25
- public: "./src/client/public"
26
- }
21
+ publicDir: "./src/client/public"
27
22
  };
28
23
  var DEFAULT_SERVER_BUILD_OPTIONS = {
29
24
  srcDir: "./src/server",
@@ -45,8 +40,7 @@ var DEFAULT_NODE_RED_LAUNCHER_OPTIONS = {
45
40
  };
46
41
  var DEFAULT_EXTRA_FILES_COPY_TARGETS = [
47
42
  { src: "./LICENSE", dest: "LICENSE" },
48
- { src: "./README.md", dest: "README.md" },
49
- { src: "./src/examples", dest: "examples" }
43
+ { src: "./README.md", dest: "README.md" }
50
44
  ];
51
45
 
52
46
  // src/vite/utils.ts
@@ -74,6 +68,16 @@ function copyFiles(targets, outDir) {
74
68
  }
75
69
  }
76
70
  }
71
+ var RESOURCE_PIPELINE_FOLDERS = /* @__PURE__ */ new Set(["icons", "locales"]);
72
+ function discoverResourceCopyTargets(resourcesDir) {
73
+ if (!fs.existsSync(resourcesDir)) return [];
74
+ return fs.readdirSync(resourcesDir, { withFileTypes: true }).filter(
75
+ (entry) => entry.isDirectory() && !RESOURCE_PIPELINE_FOLDERS.has(entry.name)
76
+ ).map((entry) => ({
77
+ src: path.join(resourcesDir, entry.name),
78
+ dest: entry.name
79
+ }));
80
+ }
77
81
  function getPackageName() {
78
82
  const pkgPath = path.resolve("./package.json");
79
83
  if (fs.existsSync(pkgPath)) {
@@ -2963,12 +2967,12 @@ async function build2(clientBuildOptions, buildContext) {
2963
2967
  name = "NodeRedNodes",
2964
2968
  format = "es",
2965
2969
  licensePath = "./LICENSE",
2966
- locales,
2967
- staticDirs = {},
2970
+ publicDir,
2968
2971
  external = [],
2969
2972
  globals = {},
2970
2973
  manualChunks
2971
2974
  } = clientBuildOptions;
2975
+ const resourcesDir = buildContext.resourcesDir;
2972
2976
  const cacheDir = path13.resolve(
2973
2977
  "node_modules",
2974
2978
  ".nrg",
@@ -2989,9 +2993,7 @@ async function build2(clientBuildOptions, buildContext) {
2989
2993
  entryPath = cachedEntryPath;
2990
2994
  generatedEntry = true;
2991
2995
  }
2992
- const iconsDir = path13.resolve(
2993
- staticDirs.icons ?? path13.join(path13.dirname(path13.resolve(srcDir)), "icons")
2994
- );
2996
+ const iconsDir = path13.join(resourcesDir, "icons");
2995
2997
  const plugins = [
2996
2998
  vue(),
2997
2999
  nodeDefinitionsInliner(
@@ -3010,33 +3012,31 @@ async function build2(clientBuildOptions, buildContext) {
3010
3012
  licensePath: licensePath ? path13.resolve(licensePath) : void 0
3011
3013
  })
3012
3014
  );
3013
- if (locales) {
3014
- const { docsDir = "./locales/docs", labelsDir = "./locales/labels" } = locales;
3015
+ const localesDir = path13.join(resourcesDir, "locales");
3016
+ if (fs14.existsSync(localesDir)) {
3017
+ const docsDir = path13.join(localesDir, "docs");
3018
+ const labelsDir = path13.join(localesDir, "labels");
3015
3019
  const localesOutDir = path13.join(buildContext.outDir, "locales");
3016
3020
  plugins.push(
3017
- localesGenerator({
3018
- outDir: localesOutDir,
3019
- docsDir: path13.resolve(docsDir),
3020
- labelsDir: path13.resolve(labelsDir)
3021
- })
3021
+ localesGenerator({ outDir: localesOutDir, docsDir, labelsDir })
3022
3022
  );
3023
3023
  plugins.push(
3024
3024
  helpGenerator({
3025
3025
  outDir: buildContext.outDir,
3026
3026
  localesOutDir,
3027
- docsDir: path13.resolve(docsDir),
3028
- labelsDir: path13.resolve(labelsDir),
3027
+ docsDir,
3028
+ labelsDir,
3029
3029
  srcDir: buildContext.serverSrcDir
3030
3030
  })
3031
3031
  );
3032
3032
  }
3033
3033
  const copyTargets = [];
3034
- const publicDir = path13.resolve(
3035
- staticDirs.public ?? path13.join(srcDir, "public")
3034
+ const resolvedPublicDir = path13.resolve(
3035
+ publicDir ?? path13.join(srcDir, "public")
3036
3036
  );
3037
- if (fs14.existsSync(publicDir)) {
3037
+ if (fs14.existsSync(resolvedPublicDir)) {
3038
3038
  copyTargets.push({
3039
- src: publicDir,
3039
+ src: resolvedPublicDir,
3040
3040
  dest: path13.join(buildContext.outDir, "resources")
3041
3041
  });
3042
3042
  }
@@ -3294,21 +3294,10 @@ function serverPlugin(options) {
3294
3294
  const clientSrcDir = path14.resolve(
3295
3295
  clientBuildOptions.srcDir ?? "./client"
3296
3296
  );
3297
- const localesDocsDir = path14.resolve(
3298
- clientBuildOptions.locales?.docsDir ?? "./locales/docs"
3299
- );
3300
- const localesLabelsDir = path14.resolve(
3301
- clientBuildOptions.locales?.labelsDir ?? "./locales/labels"
3302
- );
3303
- const iconsDir = path14.resolve(
3304
- clientBuildOptions.staticDirs?.icons ?? path14.join(path14.dirname(clientSrcDir), "icons")
3305
- );
3306
3297
  const watchPaths = [
3307
3298
  serverSrcDir,
3308
3299
  clientSrcDir,
3309
- localesDocsDir,
3310
- localesLabelsDir,
3311
- iconsDir
3300
+ buildContext.resourcesDir
3312
3301
  ];
3313
3302
  watcher = chokidar.watch(watchPaths, {
3314
3303
  ignoreInitial: true,
@@ -3399,6 +3388,9 @@ function buildPlugin(options) {
3399
3388
  }
3400
3389
 
3401
3390
  // src/vite/plugin.ts
3391
+ function resolveIsDev(env) {
3392
+ return env.command === "serve";
3393
+ }
3402
3394
  function nrg(options = {}) {
3403
3395
  const { build: build3 = {}, server = {} } = options;
3404
3396
  const { outDir = DEFAULT_OUTPUT_DIR } = build3;
@@ -3414,19 +3406,39 @@ function nrg(options = {}) {
3414
3406
  DEFAULT_NODE_RED_LAUNCHER_OPTIONS,
3415
3407
  server.nodeRed
3416
3408
  );
3417
- const extraFilesCopyTargets = build3.extraFilesCopyTargets ?? DEFAULT_EXTRA_FILES_COPY_TARGETS;
3409
+ const resourcesDir = path15.resolve(DEFAULT_RESOURCES_DIR);
3410
+ const extraFilesCopyTargets = [
3411
+ ...DEFAULT_EXTRA_FILES_COPY_TARGETS,
3412
+ ...discoverResourceCopyTargets(resourcesDir)
3413
+ ];
3418
3414
  const resolvedOutDir = path15.resolve(outDir);
3415
+ const resolvedDevOutDir = path15.resolve(DEFAULT_DEV_OUTPUT_DIR);
3419
3416
  const buildContext = {
3417
+ // outDir + isDev are authoritative once the `config` hook runs (from vite's
3418
+ // command, see resolveIsDev). These defaults only matter if a build hook
3419
+ // somehow ran before `config`, which vite never does.
3420
3420
  outDir: resolvedOutDir,
3421
3421
  packageName: getPackageName(),
3422
- isDev: process.env.NODE_ENV === "development",
3423
- serverSrcDir: path15.resolve(serverBuildOptions.srcDir ?? "./server")
3422
+ isDev: false,
3423
+ serverSrcDir: path15.resolve(serverBuildOptions.srcDir ?? "./server"),
3424
+ resourcesDir
3424
3425
  };
3425
3426
  const nodeRedLauncher = new NodeRedLauncher(
3426
- resolvedOutDir,
3427
+ resolvedDevOutDir,
3427
3428
  nodeRedLauncherOptions
3428
3429
  );
3429
3430
  return [
3431
+ {
3432
+ // Resolve dev-vs-publish from vite's command before any build/serve hook
3433
+ // runs: the dev server builds to .nrg and imports @bonsae/nrg, while a
3434
+ // production build writes ./dist and rewrites to @bonsae/nrg-runtime.
3435
+ // config() always runs before buildStart/configureServer.
3436
+ name: "vite-plugin-node-red:resolve-mode",
3437
+ config(_config, env) {
3438
+ buildContext.isDev = resolveIsDev(env);
3439
+ buildContext.outDir = buildContext.isDev ? resolvedDevOutDir : resolvedOutDir;
3440
+ }
3441
+ },
3430
3442
  serverPlugin({
3431
3443
  nodeRedLauncher,
3432
3444
  serverBuildOptions,