@bonsae/nrg 0.27.0 → 0.28.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.
@@ -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.0",
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,7 @@ import path15 from "path";
3
3
 
4
4
  // src/vite/defaults.ts
5
5
  var DEFAULT_OUTPUT_DIR = "./dist";
6
+ var DEFAULT_RESOURCES_DIR = "./src/resources";
6
7
  var DEFAULT_CLIENT_BUILD_OPTIONS = {
7
8
  srcDir: "./src/client",
8
9
  entry: "index.ts",
@@ -16,14 +17,7 @@ var DEFAULT_CLIENT_BUILD_OPTIONS = {
16
17
  "node-red": "RED",
17
18
  vue: "Vue"
18
19
  },
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
- }
20
+ publicDir: "./src/client/public"
27
21
  };
28
22
  var DEFAULT_SERVER_BUILD_OPTIONS = {
29
23
  srcDir: "./src/server",
@@ -45,8 +39,7 @@ var DEFAULT_NODE_RED_LAUNCHER_OPTIONS = {
45
39
  };
46
40
  var DEFAULT_EXTRA_FILES_COPY_TARGETS = [
47
41
  { src: "./LICENSE", dest: "LICENSE" },
48
- { src: "./README.md", dest: "README.md" },
49
- { src: "./src/examples", dest: "examples" }
42
+ { src: "./README.md", dest: "README.md" }
50
43
  ];
51
44
 
52
45
  // src/vite/utils.ts
@@ -74,6 +67,16 @@ function copyFiles(targets, outDir) {
74
67
  }
75
68
  }
76
69
  }
70
+ var RESOURCE_PIPELINE_FOLDERS = /* @__PURE__ */ new Set(["icons", "locales"]);
71
+ function discoverResourceCopyTargets(resourcesDir) {
72
+ if (!fs.existsSync(resourcesDir)) return [];
73
+ return fs.readdirSync(resourcesDir, { withFileTypes: true }).filter(
74
+ (entry) => entry.isDirectory() && !RESOURCE_PIPELINE_FOLDERS.has(entry.name)
75
+ ).map((entry) => ({
76
+ src: path.join(resourcesDir, entry.name),
77
+ dest: entry.name
78
+ }));
79
+ }
77
80
  function getPackageName() {
78
81
  const pkgPath = path.resolve("./package.json");
79
82
  if (fs.existsSync(pkgPath)) {
@@ -2963,12 +2966,12 @@ async function build2(clientBuildOptions, buildContext) {
2963
2966
  name = "NodeRedNodes",
2964
2967
  format = "es",
2965
2968
  licensePath = "./LICENSE",
2966
- locales,
2967
- staticDirs = {},
2969
+ publicDir,
2968
2970
  external = [],
2969
2971
  globals = {},
2970
2972
  manualChunks
2971
2973
  } = clientBuildOptions;
2974
+ const resourcesDir = buildContext.resourcesDir;
2972
2975
  const cacheDir = path13.resolve(
2973
2976
  "node_modules",
2974
2977
  ".nrg",
@@ -2989,9 +2992,7 @@ async function build2(clientBuildOptions, buildContext) {
2989
2992
  entryPath = cachedEntryPath;
2990
2993
  generatedEntry = true;
2991
2994
  }
2992
- const iconsDir = path13.resolve(
2993
- staticDirs.icons ?? path13.join(path13.dirname(path13.resolve(srcDir)), "icons")
2994
- );
2995
+ const iconsDir = path13.join(resourcesDir, "icons");
2995
2996
  const plugins = [
2996
2997
  vue(),
2997
2998
  nodeDefinitionsInliner(
@@ -3010,33 +3011,31 @@ async function build2(clientBuildOptions, buildContext) {
3010
3011
  licensePath: licensePath ? path13.resolve(licensePath) : void 0
3011
3012
  })
3012
3013
  );
3013
- if (locales) {
3014
- const { docsDir = "./locales/docs", labelsDir = "./locales/labels" } = locales;
3014
+ const localesDir = path13.join(resourcesDir, "locales");
3015
+ if (fs14.existsSync(localesDir)) {
3016
+ const docsDir = path13.join(localesDir, "docs");
3017
+ const labelsDir = path13.join(localesDir, "labels");
3015
3018
  const localesOutDir = path13.join(buildContext.outDir, "locales");
3016
3019
  plugins.push(
3017
- localesGenerator({
3018
- outDir: localesOutDir,
3019
- docsDir: path13.resolve(docsDir),
3020
- labelsDir: path13.resolve(labelsDir)
3021
- })
3020
+ localesGenerator({ outDir: localesOutDir, docsDir, labelsDir })
3022
3021
  );
3023
3022
  plugins.push(
3024
3023
  helpGenerator({
3025
3024
  outDir: buildContext.outDir,
3026
3025
  localesOutDir,
3027
- docsDir: path13.resolve(docsDir),
3028
- labelsDir: path13.resolve(labelsDir),
3026
+ docsDir,
3027
+ labelsDir,
3029
3028
  srcDir: buildContext.serverSrcDir
3030
3029
  })
3031
3030
  );
3032
3031
  }
3033
3032
  const copyTargets = [];
3034
- const publicDir = path13.resolve(
3035
- staticDirs.public ?? path13.join(srcDir, "public")
3033
+ const resolvedPublicDir = path13.resolve(
3034
+ publicDir ?? path13.join(srcDir, "public")
3036
3035
  );
3037
- if (fs14.existsSync(publicDir)) {
3036
+ if (fs14.existsSync(resolvedPublicDir)) {
3038
3037
  copyTargets.push({
3039
- src: publicDir,
3038
+ src: resolvedPublicDir,
3040
3039
  dest: path13.join(buildContext.outDir, "resources")
3041
3040
  });
3042
3041
  }
@@ -3294,21 +3293,10 @@ function serverPlugin(options) {
3294
3293
  const clientSrcDir = path14.resolve(
3295
3294
  clientBuildOptions.srcDir ?? "./client"
3296
3295
  );
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
3296
  const watchPaths = [
3307
3297
  serverSrcDir,
3308
3298
  clientSrcDir,
3309
- localesDocsDir,
3310
- localesLabelsDir,
3311
- iconsDir
3299
+ buildContext.resourcesDir
3312
3300
  ];
3313
3301
  watcher = chokidar.watch(watchPaths, {
3314
3302
  ignoreInitial: true,
@@ -3414,13 +3402,18 @@ function nrg(options = {}) {
3414
3402
  DEFAULT_NODE_RED_LAUNCHER_OPTIONS,
3415
3403
  server.nodeRed
3416
3404
  );
3417
- const extraFilesCopyTargets = build3.extraFilesCopyTargets ?? DEFAULT_EXTRA_FILES_COPY_TARGETS;
3405
+ const resourcesDir = path15.resolve(DEFAULT_RESOURCES_DIR);
3406
+ const extraFilesCopyTargets = [
3407
+ ...DEFAULT_EXTRA_FILES_COPY_TARGETS,
3408
+ ...discoverResourceCopyTargets(resourcesDir)
3409
+ ];
3418
3410
  const resolvedOutDir = path15.resolve(outDir);
3419
3411
  const buildContext = {
3420
3412
  outDir: resolvedOutDir,
3421
3413
  packageName: getPackageName(),
3422
3414
  isDev: process.env.NODE_ENV === "development",
3423
- serverSrcDir: path15.resolve(serverBuildOptions.srcDir ?? "./server")
3415
+ serverSrcDir: path15.resolve(serverBuildOptions.srcDir ?? "./server"),
3416
+ resourcesDir
3424
3417
  };
3425
3418
  const nodeRedLauncher = new NodeRedLauncher(
3426
3419
  resolvedOutDir,