@bonsae/nrg 0.4.0 → 0.5.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.
@@ -1211,7 +1211,7 @@ function getDefaultsFromSchema(schema) {
1211
1211
  result[key] = {
1212
1212
  required: false,
1213
1213
  value: prop.default ?? void 0,
1214
- type: prop["node-type"]
1214
+ type: prop["x-nrg-node-type"]
1215
1215
  };
1216
1216
  }
1217
1217
  return result;
@@ -1443,11 +1443,13 @@ async function build2(clientBuildOptions, buildContext) {
1443
1443
  if (fs10.existsSync(physicalEntryPath)) {
1444
1444
  entryPath = physicalEntryPath;
1445
1445
  } else {
1446
- if (!fs10.existsSync(path10.dirname(physicalEntryPath))) {
1447
- fs10.mkdirSync(path10.dirname(physicalEntryPath), { recursive: true });
1446
+ const cacheDir = path10.resolve("node_modules", ".nrg", "client");
1447
+ const cachedEntryPath = path10.resolve(cacheDir, entry);
1448
+ if (!fs10.existsSync(cacheDir)) {
1449
+ fs10.mkdirSync(cacheDir, { recursive: true });
1448
1450
  }
1449
- fs10.writeFileSync(physicalEntryPath, "// auto-generated entry\n");
1450
- entryPath = physicalEntryPath;
1451
+ fs10.writeFileSync(cachedEntryPath, "// auto-generated entry\n");
1452
+ entryPath = cachedEntryPath;
1451
1453
  generatedEntry = true;
1452
1454
  }
1453
1455
  const iconsDir = path10.resolve(
@@ -1592,7 +1594,7 @@ async function build2(clientBuildOptions, buildContext) {
1592
1594
  throw new BuildError("client", error);
1593
1595
  } finally {
1594
1596
  if (generatedEntry) {
1595
- fs10.unlinkSync(physicalEntryPath);
1597
+ fs10.unlinkSync(entryPath);
1596
1598
  }
1597
1599
  }
1598
1600
  }
@@ -1841,10 +1843,22 @@ function buildPlugin(options) {
1841
1843
  const tsconfigsToCheck = [serverTsconfig, clientTsconfig].filter(
1842
1844
  (p) => fs11.existsSync(p)
1843
1845
  );
1844
- for (const tsconfig of tsconfigsToCheck) {
1845
- execSync(`npx tsc -p ${tsconfig} --noEmit`, { stdio: "inherit" });
1846
+ try {
1847
+ for (const tsconfig of tsconfigsToCheck) {
1848
+ execSync(`npx tsc -p ${tsconfig} --noEmit`, {
1849
+ stdio: ["inherit", "pipe", "pipe"],
1850
+ encoding: "utf-8"
1851
+ });
1852
+ }
1853
+ logger.stopSpinner("Type checked");
1854
+ } catch (e) {
1855
+ logger.stopSpinner("Type check failed");
1856
+ const output = (e.stdout || "") + (e.stderr || "");
1857
+ if (output) {
1858
+ console.error(output);
1859
+ }
1860
+ throw new BuildError("type-check", e);
1846
1861
  }
1847
- logger.stopSpinner("Type checked");
1848
1862
  logger.startSpinner("Cleaning");
1849
1863
  cleanDir(buildContext.outDir);
1850
1864
  logger.stopSpinner("Cleaned");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonsae/nrg",
3
- "version": "0.4.0",
3
+ "version": "0.5.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",
@@ -14,7 +14,7 @@
14
14
  localNode.validateInput = ($event.target as HTMLInputElement).checked
15
15
  "
16
16
  />
17
- <span class="nrg-label" style="width: auto">Validate Input</span>
17
+ <NodeRedInputLabel label="Validate Input" style="width: auto" />
18
18
  </template>
19
19
  <template v-if="features.hasOutputSchema">
20
20
  <input
@@ -26,7 +26,7 @@
26
26
  localNode.validateOutput = ($event.target as HTMLInputElement).checked
27
27
  "
28
28
  />
29
- <span class="nrg-label" style="width: auto">Validate Output</span>
29
+ <NodeRedInputLabel label="Validate Output" style="width: auto" />
30
30
  </template>
31
31
  </div>
32
32
  <div style="width: 100%; padding-bottom: 12px">
@@ -185,12 +185,6 @@ export default defineComponent({
185
185
  color: var(--red-ui-text-color-error);
186
186
  }
187
187
 
188
- :deep(.nrg-label) {
189
- display: inline-block;
190
- width: 100%;
191
- cursor: default;
192
- }
193
-
194
188
  :deep(.form-row input[type="text"]),
195
189
  :deep(.form-row input[type="number"]),
196
190
  :deep(.form-row input[type="password"]) {
@@ -1,5 +1,13 @@
1
1
  <template>
2
2
  <div style="display: flex; flex-direction: column; width: 100%">
3
+ <slot name="label">
4
+ <NodeRedInputLabel
5
+ v-if="label"
6
+ :label="label"
7
+ :icon="icon"
8
+ :required="required"
9
+ />
10
+ </slot>
3
11
  <input :id="inputId" type="text" style="width: 100%" />
4
12
  <div v-if="error" class="node-red-vue-input-error-message">
5
13
  {{ error }}
@@ -9,7 +17,9 @@
9
17
 
10
18
  <script lang="ts">
11
19
  import { defineComponent } from "vue";
20
+ import NodeRedInputLabel from "./node-red-input-label.vue";
12
21
  export default defineComponent({
22
+ components: { NodeRedInputLabel },
13
23
  props: {
14
24
  value: {
15
25
  type: String,
@@ -27,6 +37,18 @@ export default defineComponent({
27
37
  type: String,
28
38
  required: true,
29
39
  },
40
+ label: {
41
+ type: String,
42
+ default: "",
43
+ },
44
+ icon: {
45
+ type: String,
46
+ default: "",
47
+ },
48
+ required: {
49
+ type: Boolean,
50
+ default: false,
51
+ },
30
52
  error: {
31
53
  type: String,
32
54
  default: "",
@@ -1,13 +1,23 @@
1
1
  <template>
2
2
  <div ref="container" class="container">
3
- <button
4
- ref="expand-button"
5
- class="red-ui-button red-ui-button-small expand-button"
6
- @click="onClickExpand"
7
- >
8
- <i class="fa fa-expand"></i>
9
- </button>
10
- <div :id="editorId" ref="editor"></div>
3
+ <slot name="label">
4
+ <NodeRedInputLabel
5
+ v-if="label"
6
+ :label="label"
7
+ :icon="icon"
8
+ :required="required"
9
+ />
10
+ </slot>
11
+ <div class="editor-wrapper">
12
+ <button
13
+ ref="expand-button"
14
+ class="red-ui-button red-ui-button-small expand-button"
15
+ @click="onClickExpand"
16
+ >
17
+ <i class="fa fa-expand"></i>
18
+ </button>
19
+ <div :id="editorId" ref="editor"></div>
20
+ </div>
11
21
  <div v-show="error" class="node-red-vue-input-error-message">
12
22
  {{ error }}
13
23
  </div>
@@ -17,7 +27,9 @@
17
27
  <script lang="ts">
18
28
  // TODO: expose editor apis
19
29
  import { defineComponent } from "vue";
30
+ import NodeRedInputLabel from "./node-red-input-label.vue";
20
31
  export default defineComponent({
32
+ components: { NodeRedInputLabel },
21
33
  props: {
22
34
  value: {
23
35
  type: String,
@@ -121,6 +133,18 @@ export default defineComponent({
121
133
  return isValid;
122
134
  },
123
135
  },
136
+ label: {
137
+ type: String,
138
+ default: "",
139
+ },
140
+ icon: {
141
+ type: String,
142
+ default: "",
143
+ },
144
+ required: {
145
+ type: Boolean,
146
+ default: false,
147
+ },
124
148
  error: {
125
149
  type: String,
126
150
  default: "",
@@ -268,7 +292,7 @@ export default defineComponent({
268
292
  });
269
293
  </script>
270
294
  <style scoped>
271
- .container {
295
+ .editor-wrapper {
272
296
  position: relative;
273
297
  }
274
298
 
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <span class="nrg-label">
3
+ <i v-if="icon" :class="iconClass"></i>
4
+ <slot>{{ label }}</slot>
5
+ <span v-if="required" class="nrg-required">*</span>
6
+ </span>
7
+ </template>
8
+
9
+ <script lang="ts">
10
+ import { defineComponent } from "vue";
11
+
12
+ export default defineComponent({
13
+ name: "NodeRedInputLabel",
14
+ props: {
15
+ label: {
16
+ type: String,
17
+ default: "",
18
+ },
19
+ icon: {
20
+ type: String,
21
+ default: "",
22
+ },
23
+ required: {
24
+ type: Boolean,
25
+ default: false,
26
+ },
27
+ },
28
+ computed: {
29
+ iconClass(): string {
30
+ if (!this.icon) return "";
31
+ const name = this.icon.startsWith("fa-") ? this.icon : `fa-${this.icon}`;
32
+ return `fa ${name}`;
33
+ },
34
+ },
35
+ });
36
+ </script>
37
+
38
+ <style scoped>
39
+ .nrg-label {
40
+ display: inline-block;
41
+ width: 100%;
42
+ cursor: default;
43
+ }
44
+
45
+ .nrg-label i {
46
+ margin-right: 5px;
47
+ }
48
+
49
+ .nrg-required {
50
+ color: var(--red-ui-text-color-error);
51
+ margin-left: 2px;
52
+ }
53
+ </style>
@@ -1,5 +1,13 @@
1
1
  <template>
2
2
  <div style="display: flex; flex-direction: column; width: 100%">
3
+ <slot name="label">
4
+ <NodeRedInputLabel
5
+ v-if="label"
6
+ :label="label"
7
+ :icon="icon"
8
+ :required="required"
9
+ />
10
+ </slot>
3
11
  <input
4
12
  ref="inputField"
5
13
  :type="type"
@@ -18,10 +26,12 @@
18
26
 
19
27
  <script lang="ts">
20
28
  import { defineComponent } from "vue";
29
+ import NodeRedInputLabel from "./node-red-input-label.vue";
21
30
 
22
31
  const SECRET_PATTERN = "*************";
23
32
 
24
33
  export default defineComponent({
34
+ components: { NodeRedInputLabel },
25
35
  props: {
26
36
  value: {
27
37
  type: String,
@@ -35,6 +45,18 @@ export default defineComponent({
35
45
  type: String,
36
46
  default: "",
37
47
  },
48
+ label: {
49
+ type: String,
50
+ default: "",
51
+ },
52
+ icon: {
53
+ type: String,
54
+ default: "",
55
+ },
56
+ required: {
57
+ type: Boolean,
58
+ default: false,
59
+ },
38
60
  error: {
39
61
  type: String,
40
62
  default: "",
@@ -1,36 +1,44 @@
1
1
  <template>
2
2
  <div>
3
3
  <div v-for="field in configFields" :key="field.key" class="form-row">
4
- <span class="nrg-label">
5
- {{ field.label }}
6
- <span v-if="field.required" class="nrg-required">*</span>
7
- </span>
8
-
9
4
  <NodeRedInput
10
5
  v-if="field.inputType === 'text' || field.inputType === 'number'"
11
6
  :value="node[field.key]"
12
7
  :type="field.htmlType"
8
+ :label="field.label"
9
+ :icon="field.icon"
10
+ :required="field.required"
13
11
  :error="errors[`node.${field.key}`]"
14
12
  @update:value="node[field.key] = $event"
15
13
  />
16
14
 
17
- <input
18
- v-else-if="field.inputType === 'boolean'"
19
- type="checkbox"
20
- :checked="node[field.key]"
21
- style="width: auto; margin: 0"
22
- @change="
23
- (e) => {
24
- node[field.key] = (e.target as HTMLInputElement).checked;
25
- }
26
- "
27
- />
15
+ <div v-else-if="field.inputType === 'boolean'">
16
+ <NodeRedInputLabel
17
+ :label="field.label"
18
+ :icon="field.icon"
19
+ :required="field.required"
20
+ style="width: auto"
21
+ />
22
+ <input
23
+ type="checkbox"
24
+ :checked="node[field.key]"
25
+ style="width: auto; margin: 0"
26
+ @change="
27
+ (e) => {
28
+ node[field.key] = (e.target as HTMLInputElement).checked;
29
+ }
30
+ "
31
+ />
32
+ </div>
28
33
 
29
34
  <NodeRedSelectInput
30
35
  v-else-if="field.inputType === 'select'"
31
36
  :value="node[field.key]"
32
37
  :options="field.options!"
33
38
  :multiple="field.multiple"
39
+ :label="field.label"
40
+ :icon="field.icon"
41
+ :required="field.required"
34
42
  :error="errors[`node.${field.key}`]"
35
43
  @update:value="node[field.key] = $event"
36
44
  />
@@ -39,6 +47,9 @@
39
47
  v-else-if="field.inputType === 'typed'"
40
48
  :value="node[field.key]"
41
49
  :types="field.types"
50
+ :label="field.label"
51
+ :icon="field.icon"
52
+ :required="field.required"
42
53
  :error="errors[`node.${field.key}`]"
43
54
  @update:value="node[field.key] = $event"
44
55
  />
@@ -49,11 +60,19 @@
49
60
  :type="field.configType!"
50
61
  :node="node"
51
62
  :prop-name="field.key"
63
+ :label="field.label"
64
+ :icon="field.icon"
65
+ :required="field.required"
52
66
  :error="errors[`node.${field.key}`]"
53
67
  @update:value="node[field.key] = $event"
54
68
  />
55
69
 
56
70
  <div v-else-if="field.inputType === 'array-text'">
71
+ <NodeRedInputLabel
72
+ :label="field.label"
73
+ :icon="field.icon"
74
+ :required="field.required"
75
+ />
57
76
  <span
58
77
  style="
59
78
  display: block;
@@ -95,6 +114,9 @@
95
114
  v-else-if="field.inputType === 'editor'"
96
115
  :value="node[field.key]"
97
116
  :language="field.language"
117
+ :label="field.label"
118
+ :icon="field.icon"
119
+ :required="field.required"
98
120
  :error="errors[`node.${field.key}`]"
99
121
  @update:value="node[field.key] = $event"
100
122
  />
@@ -105,14 +127,12 @@
105
127
  :key="`cred-${field.key}`"
106
128
  class="form-row"
107
129
  >
108
- <span class="nrg-label">
109
- {{ field.label }}
110
- <span v-if="field.required" class="nrg-required">*</span>
111
- </span>
112
-
113
130
  <NodeRedInput
114
131
  :value="node.credentials[field.key]"
115
132
  :type="field.htmlType"
133
+ :label="field.label"
134
+ :icon="field.icon"
135
+ :required="field.required"
116
136
  :error="errors[`node.credentials.${field.key}`]"
117
137
  @update:value="node.credentials[field.key] = $event"
118
138
  />
@@ -123,6 +143,7 @@
123
143
  <script lang="ts">
124
144
  import type { PropType } from "vue";
125
145
  import { defineComponent } from "vue";
146
+ import NodeRedInputLabel from "./node-red-input-label.vue";
126
147
  import NodeRedInput from "./node-red-input.vue";
127
148
  import NodeRedSelectInput from "./node-red-select-input.vue";
128
149
  import NodeRedTypedInput from "./node-red-typed-input.vue";
@@ -144,6 +165,12 @@ const SKIP_FIELDS = new Set([
144
165
  "validateOutput",
145
166
  ]);
146
167
 
168
+ interface NrgFormOptions {
169
+ icon?: string;
170
+ typedInputTypes?: string[];
171
+ editorLanguage?: string;
172
+ }
173
+
147
174
  interface FieldSchema {
148
175
  type?: string | string[];
149
176
  properties?: Record<string, FieldSchema>;
@@ -154,15 +181,15 @@ interface FieldSchema {
154
181
  description?: string;
155
182
  default?: any;
156
183
  items?: FieldSchema;
157
- "node-type"?: string;
158
- "x-typed-types"?: string[];
159
- "x-editor-language"?: string;
184
+ "x-nrg-node-type"?: string;
185
+ "x-nrg-form"?: NrgFormOptions;
160
186
  [key: string]: any;
161
187
  }
162
188
 
163
189
  interface FormField {
164
190
  key: string;
165
191
  label: string;
192
+ icon: string;
166
193
  inputType:
167
194
  | "text"
168
195
  | "number"
@@ -201,15 +228,18 @@ function buildField(
201
228
  required: boolean,
202
229
  ): FormField {
203
230
  const label = schema.title || formatLabel(key);
231
+ const form = schema["x-nrg-form"] ?? {};
232
+ const icon = form.icon || "";
204
233
 
205
234
  // NodeRef → config input
206
- if (schema["node-type"]) {
235
+ if (schema["x-nrg-node-type"]) {
207
236
  return {
208
237
  key,
209
238
  label,
239
+ icon,
210
240
  inputType: "config",
211
241
  required,
212
- configType: schema["node-type"],
242
+ configType: schema["x-nrg-node-type"],
213
243
  };
214
244
  }
215
245
 
@@ -218,9 +248,10 @@ function buildField(
218
248
  return {
219
249
  key,
220
250
  label,
251
+ icon,
221
252
  inputType: "typed",
222
253
  required,
223
- types: schema["x-typed-types"],
254
+ types: form.typedInputTypes,
224
255
  };
225
256
  }
226
257
 
@@ -229,6 +260,7 @@ function buildField(
229
260
  return {
230
261
  key,
231
262
  label,
263
+ icon,
232
264
  inputType: "select",
233
265
  required,
234
266
  multiple: true,
@@ -244,6 +276,7 @@ function buildField(
244
276
  return {
245
277
  key,
246
278
  label,
279
+ icon,
247
280
  inputType: "select",
248
281
  required,
249
282
  multiple: false,
@@ -258,53 +291,71 @@ function buildField(
258
291
 
259
292
  switch (rawType) {
260
293
  case "boolean":
261
- return { key, label, inputType: "boolean", required };
294
+ return { key, label, icon, inputType: "boolean", required };
262
295
 
263
296
  case "number":
264
297
  case "integer":
265
- return { key, label, inputType: "number", required, htmlType: "number" };
298
+ return {
299
+ key,
300
+ label,
301
+ icon,
302
+ inputType: "number",
303
+ required,
304
+ htmlType: "number",
305
+ };
266
306
 
267
307
  case "array":
268
- if (schema["x-editor-language"]) {
308
+ if (form.editorLanguage) {
269
309
  return {
270
310
  key,
271
311
  label,
312
+ icon,
272
313
  inputType: "editor",
273
314
  required,
274
- language: schema["x-editor-language"],
315
+ language: form.editorLanguage,
275
316
  };
276
317
  }
277
318
  // Plain array of strings → comma-separated text input
278
- return { key, label, inputType: "array-text", required };
319
+ return { key, label, icon, inputType: "array-text", required };
279
320
 
280
321
  case "object":
281
- if (schema["x-editor-language"]) {
322
+ if (form.editorLanguage) {
282
323
  return {
283
324
  key,
284
325
  label,
326
+ icon,
285
327
  inputType: "editor",
286
328
  required,
287
- language: schema["x-editor-language"],
329
+ language: form.editorLanguage,
288
330
  };
289
331
  }
290
332
  // Plain object → text input (stored as JSON string)
291
- return { key, label, inputType: "text", required, htmlType: "text" };
333
+ return {
334
+ key,
335
+ label,
336
+ icon,
337
+ inputType: "text",
338
+ required,
339
+ htmlType: "text",
340
+ };
292
341
 
293
342
  default:
294
343
  // string with editor language → code editor
295
- if (schema["x-editor-language"]) {
344
+ if (form.editorLanguage) {
296
345
  return {
297
346
  key,
298
347
  label,
348
+ icon,
299
349
  inputType: "editor",
300
350
  required,
301
- language: schema["x-editor-language"],
351
+ language: form.editorLanguage,
302
352
  };
303
353
  }
304
354
  // string (or untyped)
305
355
  return {
306
356
  key,
307
357
  label,
358
+ icon,
308
359
  inputType: "text",
309
360
  required,
310
361
  htmlType: schema.format === "password" ? "password" : "text",
@@ -315,6 +366,7 @@ function buildField(
315
366
  export default defineComponent({
316
367
  name: "NodeRedJsonSchemaForm",
317
368
  components: {
369
+ NodeRedInputLabel,
318
370
  NodeRedInput,
319
371
  NodeRedSelectInput,
320
372
  NodeRedTypedInput,
@@ -370,10 +422,3 @@ export default defineComponent({
370
422
  },
371
423
  });
372
424
  </script>
373
-
374
- <style scoped>
375
- .nrg-required {
376
- color: var(--red-ui-text-color-error);
377
- margin-left: 2px;
378
- }
379
- </style>
@@ -1,5 +1,13 @@
1
1
  <template>
2
2
  <div style="display: flex; flex-direction: column; width: 100%">
3
+ <slot name="label">
4
+ <NodeRedInputLabel
5
+ v-if="label"
6
+ :label="label"
7
+ :icon="icon"
8
+ :required="required"
9
+ />
10
+ </slot>
3
11
  <input
4
12
  ref="selectInput"
5
13
  type="text"
@@ -14,7 +22,9 @@
14
22
 
15
23
  <script lang="ts">
16
24
  import { defineComponent } from "vue";
25
+ import NodeRedInputLabel from "./node-red-input-label.vue";
17
26
  export default defineComponent({
27
+ components: { NodeRedInputLabel },
18
28
  props: {
19
29
  value: {
20
30
  type: [String, Array],
@@ -53,6 +63,18 @@ export default defineComponent({
53
63
  type: Boolean,
54
64
  default: false,
55
65
  },
66
+ label: {
67
+ type: String,
68
+ default: "",
69
+ },
70
+ icon: {
71
+ type: String,
72
+ default: "",
73
+ },
74
+ required: {
75
+ type: Boolean,
76
+ default: false,
77
+ },
56
78
  error: {
57
79
  type: String,
58
80
  default: "",