@researchdatabox/redbox-dev-tools 0.0.1-beta.10173

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.
Files changed (106) hide show
  1. package/README.md +255 -0
  2. package/bin/cli.js +3 -0
  3. package/bin/run-hook-mocha.js +3 -0
  4. package/bin/run-hook-tsc.js +3 -0
  5. package/bin/wrapper-webpack.js +168 -0
  6. package/config/tsconfig.base.json +16 -0
  7. package/dist/bin/run-hook-mocha.d.ts +1 -0
  8. package/dist/bin/run-hook-mocha.js +49 -0
  9. package/dist/bin/run-hook-tsc.d.ts +1 -0
  10. package/dist/bin/run-hook-tsc.js +19 -0
  11. package/dist/cli-parsers.d.ts +9 -0
  12. package/dist/cli-parsers.js +131 -0
  13. package/dist/cli.d.ts +1 -0
  14. package/dist/cli.js +491 -0
  15. package/dist/commands/form-config.d.ts +6 -0
  16. package/dist/commands/form-config.js +404 -0
  17. package/dist/completion/index.d.ts +1 -0
  18. package/dist/completion/index.js +218 -0
  19. package/dist/generators/add-method.d.ts +27 -0
  20. package/dist/generators/add-method.js +235 -0
  21. package/dist/generators/angular-app.d.ts +24 -0
  22. package/dist/generators/angular-app.js +342 -0
  23. package/dist/generators/angular-service.d.ts +21 -0
  24. package/dist/generators/angular-service.js +154 -0
  25. package/dist/generators/controller.d.ts +40 -0
  26. package/dist/generators/controller.js +195 -0
  27. package/dist/generators/form-component.d.ts +46 -0
  28. package/dist/generators/form-component.js +906 -0
  29. package/dist/generators/form-field.d.ts +15 -0
  30. package/dist/generators/form-field.js +160 -0
  31. package/dist/generators/migration.d.ts +15 -0
  32. package/dist/generators/migration.js +100 -0
  33. package/dist/generators/model.d.ts +42 -0
  34. package/dist/generators/model.js +206 -0
  35. package/dist/generators/service.d.ts +16 -0
  36. package/dist/generators/service.js +111 -0
  37. package/dist/runtime-resolver.d.ts +3 -0
  38. package/dist/runtime-resolver.js +27 -0
  39. package/dist/templates/hook-archetype.d.ts +12 -0
  40. package/dist/templates/hook-archetype.js +186 -0
  41. package/dist/testing.d.ts +7 -0
  42. package/dist/testing.js +53 -0
  43. package/dist/utils/config-helper.d.ts +71 -0
  44. package/dist/utils/config-helper.js +574 -0
  45. package/dist/utils/generator.d.ts +10 -0
  46. package/dist/utils/generator.js +68 -0
  47. package/dist/utils/hook-tsconfig.d.ts +9 -0
  48. package/dist/utils/hook-tsconfig.js +104 -0
  49. package/dist/utils/paths.d.ts +10 -0
  50. package/dist/utils/paths.js +60 -0
  51. package/index.js +4 -0
  52. package/package.json +85 -0
  53. package/runtime-resolver.d.ts +1 -0
  54. package/runtime-resolver.js +3 -0
  55. package/skills/redbox-angular-apps/SKILL.md +42 -0
  56. package/skills/redbox-angular-services/SKILL.md +29 -0
  57. package/skills/redbox-controllers/SKILL.md +65 -0
  58. package/skills/redbox-feature-design-planner/SKILL.md +169 -0
  59. package/skills/redbox-feature-implementation-review/SKILL.md +117 -0
  60. package/skills/redbox-form-components/SKILL.md +114 -0
  61. package/skills/redbox-form-config/SKILL.md +34 -0
  62. package/skills/redbox-hook-development/SKILL.md +66 -0
  63. package/skills/redbox-services/SKILL.md +54 -0
  64. package/skills/redbox-test-verification/SKILL.md +38 -0
  65. package/skills/redbox-testing/SKILL.md +64 -0
  66. package/skills/redbox-web-verification/SKILL.md +71 -0
  67. package/skills/redbox-web-verification/redbox-angular-apps/SKILL.md +48 -0
  68. package/skills/redbox-web-verification/redbox-angular-services/SKILL.md +30 -0
  69. package/skills/redbox-web-verification/redbox-controllers/SKILL.md +56 -0
  70. package/skills/redbox-web-verification/redbox-form-config/SKILL.md +29 -0
  71. package/skills/redbox-web-verification/redbox-hook-development/SKILL.md +61 -0
  72. package/skills/redbox-web-verification/redbox-services/SKILL.md +45 -0
  73. package/skills/redbox-web-verification/redbox-testing/SKILL.md +53 -0
  74. package/skills/redbox-web-verification/redbox-web-verification/SKILL.md +47 -0
  75. package/templates/hook-archetype/standard/.gitignore.hbs +4 -0
  76. package/templates/hook-archetype/standard/.npmignore.hbs +3 -0
  77. package/templates/hook-archetype/standard/.npmrc.hbs +6 -0
  78. package/templates/hook-archetype/standard/.nvmrc.hbs +1 -0
  79. package/templates/hook-archetype/standard/AGENTS.md.hbs +34 -0
  80. package/templates/hook-archetype/standard/Dockerfile.hbs +28 -0
  81. package/templates/hook-archetype/standard/README.md.hbs +39 -0
  82. package/templates/hook-archetype/standard/assets/fonts/.gitkeep +1 -0
  83. package/templates/hook-archetype/standard/language-defaults/.gitkeep +1 -0
  84. package/templates/hook-archetype/standard/package.json.hbs +40 -0
  85. package/templates/hook-archetype/standard/src/api/controllers/ExampleController.ts.hbs +21 -0
  86. package/templates/hook-archetype/standard/src/api/controllers/index.ts.hbs +23 -0
  87. package/templates/hook-archetype/standard/src/api/services/ExampleService.ts.hbs +22 -0
  88. package/templates/hook-archetype/standard/src/api/services/index.ts.hbs +23 -0
  89. package/templates/hook-archetype/standard/src/config/brandingConfigurationDefaults.ts.hbs +9 -0
  90. package/templates/hook-archetype/standard/src/config/reusableFormDefinitions.ts.hbs +6 -0
  91. package/templates/hook-archetype/standard/src/form-config/default-1.0-draft.ts.hbs +20 -0
  92. package/templates/hook-archetype/standard/src/form-config/index.ts.hbs +8 -0
  93. package/templates/hook-archetype/standard/src/index.ts.hbs +55 -0
  94. package/templates/hook-archetype/standard/support/bootstrap-data/.gitkeep +1 -0
  95. package/templates/hook-archetype/standard/support/development/docker-compose.yml.hbs +75 -0
  96. package/templates/hook-archetype/standard/support/integration-testing/docker-compose.mocha.yml.hbs +84 -0
  97. package/templates/hook-archetype/standard/support/integration-testing/run-mocha-hook.sh.hbs +9 -0
  98. package/templates/hook-archetype/standard/test/integration/hook-registration.test.ts.hbs +13 -0
  99. package/templates/hook-archetype/standard/test/support/globals.ts.hbs +40 -0
  100. package/templates/hook-archetype/standard/test/tsconfig.json.hbs +8 -0
  101. package/templates/hook-archetype/standard/test/unit/.mocharc.cjs.hbs +9 -0
  102. package/templates/hook-archetype/standard/test/unit/index.test.ts.hbs +40 -0
  103. package/templates/hook-archetype/standard/tsconfig.json.hbs +15 -0
  104. package/templates/hook-archetype/standard/views/.gitkeep +1 -0
  105. package/testing.d.ts +1 -0
  106. package/testing.js +3 -0
@@ -0,0 +1,906 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FormComponentGenerator = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const generator_1 = require("../utils/generator");
40
+ class FormComponentGenerator extends generator_1.Generator {
41
+ rawName;
42
+ app;
43
+ withService;
44
+ paths;
45
+ kebabName;
46
+ pascalName;
47
+ componentClassName;
48
+ modelClassName;
49
+ componentNameConstant;
50
+ modelNameConstant;
51
+ constructor(options) {
52
+ super(options);
53
+ this.rawName = options.name;
54
+ this.app = options.app ?? 'form';
55
+ this.withService = !!options.withService;
56
+ this.paths = options.paths;
57
+ this.kebabName = this.toKebabCase(this.rawName);
58
+ this.pascalName = this.toPascalCase(this.kebabName);
59
+ this.componentClassName = `${this.pascalName}Component`;
60
+ this.modelClassName = `${this.pascalName}Model`;
61
+ this.componentNameConstant = `${this.pascalName}ComponentName`;
62
+ this.modelNameConstant = `${this.pascalName}ModelName`;
63
+ }
64
+ async generate() {
65
+ this.generateSailsNgCommonComponentFiles();
66
+ this.generateAngularComponentFiles();
67
+ if (this.withService) {
68
+ this.generateAngularServiceFiles();
69
+ }
70
+ this.updateSailsNgCommonDictionaries();
71
+ this.updateSailsNgCommonIndex();
72
+ this.updateBaseVisitors();
73
+ this.updateConcreteVisitors();
74
+ this.updateAngularWiring();
75
+ }
76
+ toKebabCase(value) {
77
+ const trimmed = String(value ?? '').trim();
78
+ if (!trimmed) {
79
+ throw new Error('Form component name cannot be empty.');
80
+ }
81
+ const normalized = trimmed
82
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
83
+ .replace(/[^A-Za-z0-9]+/g, '-')
84
+ .replace(/^-+|-+$/g, '')
85
+ .toLowerCase();
86
+ if (!normalized) {
87
+ throw new Error(`Form component name is invalid: '${value}'`);
88
+ }
89
+ return normalized;
90
+ }
91
+ toPascalCase(value) {
92
+ return value
93
+ .split('-')
94
+ .filter(Boolean)
95
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
96
+ .join('');
97
+ }
98
+ readFile(filePath) {
99
+ if (!fs.existsSync(filePath)) {
100
+ throw new Error(`Required file not found: ${filePath}`);
101
+ }
102
+ return fs.readFileSync(filePath, 'utf-8');
103
+ }
104
+ updateTextFile(filePath, updater) {
105
+ const content = this.readFile(filePath);
106
+ const updated = updater(content);
107
+ if (updated !== content) {
108
+ this.writeFile(filePath, updated);
109
+ }
110
+ }
111
+ insertBeforeMarker(content, marker, addition) {
112
+ const signature = addition
113
+ .split('\n')
114
+ .map(line => line.trim())
115
+ .find(line => line.length > 0);
116
+ if ((signature && content.includes(signature)) || content.includes(addition.trim())) {
117
+ return content;
118
+ }
119
+ const idx = content.indexOf(marker);
120
+ if (idx === -1) {
121
+ throw new Error(`Unable to find insertion marker '${marker}'.`);
122
+ }
123
+ return `${content.slice(0, idx)}${addition}${content.slice(idx)}`;
124
+ }
125
+ ensureImport(content, importLine) {
126
+ const sourceMatch = importLine.match(/from\s+["']([^"']+)["']/);
127
+ if (sourceMatch?.[1]) {
128
+ const source = sourceMatch[1];
129
+ if (content.includes(`from "${source}"`) || content.includes(`from '${source}'`)) {
130
+ return content;
131
+ }
132
+ }
133
+ if (content.includes(importLine)) {
134
+ return content;
135
+ }
136
+ const lines = content.split('\n');
137
+ let insertAt = -1;
138
+ for (let i = 0; i < lines.length; i += 1) {
139
+ if (lines[i].startsWith('import ')) {
140
+ insertAt = i;
141
+ }
142
+ else if (insertAt !== -1) {
143
+ break;
144
+ }
145
+ }
146
+ if (insertAt === -1) {
147
+ throw new Error('Unable to find import section.');
148
+ }
149
+ lines.splice(insertAt + 1, 0, importLine);
150
+ return `${lines.join('\n')}`;
151
+ }
152
+ appendToObjectLiteral(content, objectDeclRegex, lineToAdd) {
153
+ if (content.includes(lineToAdd.trim())) {
154
+ return content;
155
+ }
156
+ const match = objectDeclRegex.exec(content);
157
+ if (!match || typeof match.index !== 'number') {
158
+ throw new Error(`Unable to find object literal using pattern ${objectDeclRegex}.`);
159
+ }
160
+ const startIdx = content.indexOf('{', match.index);
161
+ if (startIdx === -1) {
162
+ throw new Error('Unable to find object opening brace.');
163
+ }
164
+ const endIdx = this.findMatchingDelimiter(content, startIdx, '{', '}');
165
+ if (endIdx === -1) {
166
+ throw new Error('Unable to locate end of object literal.');
167
+ }
168
+ return `${content.slice(0, endIdx)}${lineToAdd}${content.slice(endIdx)}`;
169
+ }
170
+ appendToArrayLiteral(content, arrayDeclRegex, lineToAdd) {
171
+ if (content.includes(lineToAdd.trim())) {
172
+ return content;
173
+ }
174
+ const match = arrayDeclRegex.exec(content);
175
+ if (!match || typeof match.index !== 'number') {
176
+ throw new Error(`Unable to find array literal using pattern ${arrayDeclRegex}.`);
177
+ }
178
+ const startIdx = content.indexOf('[', match.index);
179
+ if (startIdx === -1) {
180
+ throw new Error('Unable to find array opening bracket.');
181
+ }
182
+ const endIdx = this.findMatchingDelimiter(content, startIdx, '[', ']');
183
+ if (endIdx === -1) {
184
+ throw new Error('Unable to locate end of array literal.');
185
+ }
186
+ return `${content.slice(0, endIdx)}${lineToAdd}${content.slice(endIdx)}`;
187
+ }
188
+ findMatchingDelimiter(content, startIdx, openChar, closeChar) {
189
+ let depth = 0;
190
+ let inSingleQuote = false;
191
+ let inDoubleQuote = false;
192
+ let inTemplateLiteral = false;
193
+ let inLineComment = false;
194
+ let inBlockComment = false;
195
+ let isEscaped = false;
196
+ for (let i = startIdx; i < content.length; i += 1) {
197
+ const ch = content[i];
198
+ const nextCh = content[i + 1] ?? '';
199
+ if (inLineComment) {
200
+ if (ch === '\n' || ch === '\r') {
201
+ inLineComment = false;
202
+ }
203
+ continue;
204
+ }
205
+ if (inBlockComment) {
206
+ if (ch === '*' && nextCh === '/') {
207
+ inBlockComment = false;
208
+ i += 1;
209
+ }
210
+ continue;
211
+ }
212
+ if (inSingleQuote) {
213
+ if (ch === '\\' && !isEscaped) {
214
+ isEscaped = true;
215
+ continue;
216
+ }
217
+ if (ch === "'" && !isEscaped) {
218
+ inSingleQuote = false;
219
+ }
220
+ isEscaped = false;
221
+ continue;
222
+ }
223
+ if (inDoubleQuote) {
224
+ if (ch === '\\' && !isEscaped) {
225
+ isEscaped = true;
226
+ continue;
227
+ }
228
+ if (ch === '"' && !isEscaped) {
229
+ inDoubleQuote = false;
230
+ }
231
+ isEscaped = false;
232
+ continue;
233
+ }
234
+ if (inTemplateLiteral) {
235
+ if (ch === '\\' && !isEscaped) {
236
+ isEscaped = true;
237
+ continue;
238
+ }
239
+ if (ch === '`' && !isEscaped) {
240
+ inTemplateLiteral = false;
241
+ }
242
+ isEscaped = false;
243
+ continue;
244
+ }
245
+ if (ch === '/' && nextCh === '/') {
246
+ inLineComment = true;
247
+ i += 1;
248
+ continue;
249
+ }
250
+ if (ch === '/' && nextCh === '*') {
251
+ inBlockComment = true;
252
+ i += 1;
253
+ continue;
254
+ }
255
+ if (ch === "'") {
256
+ inSingleQuote = true;
257
+ isEscaped = false;
258
+ continue;
259
+ }
260
+ if (ch === '"') {
261
+ inDoubleQuote = true;
262
+ isEscaped = false;
263
+ continue;
264
+ }
265
+ if (ch === '`') {
266
+ inTemplateLiteral = true;
267
+ isEscaped = false;
268
+ continue;
269
+ }
270
+ if (ch === openChar) {
271
+ depth += 1;
272
+ }
273
+ else if (ch === closeChar) {
274
+ depth -= 1;
275
+ if (depth === 0) {
276
+ return i;
277
+ }
278
+ }
279
+ }
280
+ return -1;
281
+ }
282
+ generateSailsNgCommonComponentFiles() {
283
+ const componentDir = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'config', 'component');
284
+ const outlinePath = path.join(componentDir, `${this.kebabName}.outline.ts`);
285
+ const modelPath = path.join(componentDir, `${this.kebabName}.model.ts`);
286
+ const outlineContent = `import {
287
+ FieldComponentConfigFrameKindType, FieldComponentConfigKindType,
288
+ FieldComponentDefinitionFrameKindType, FieldComponentDefinitionKindType,
289
+ FieldModelConfigFrameKindType,
290
+ FieldModelConfigKindType, FieldModelDefinitionFrameKindType,
291
+ FieldModelDefinitionKindType, FormComponentDefinitionFrameKindType, FormComponentDefinitionKindType
292
+ } from "../shared.outline";
293
+ import {
294
+ FieldComponentConfigFrame,
295
+ FieldComponentConfigOutline,
296
+ FieldComponentDefinitionFrame, FieldComponentDefinitionOutline
297
+ } from "../field-component.outline";
298
+ import {
299
+ FieldModelConfigFrame,
300
+ FieldModelConfigOutline,
301
+ FieldModelDefinitionFrame,
302
+ FieldModelDefinitionOutline
303
+ } from "../field-model.outline";
304
+ import {FormComponentDefinitionFrame, FormComponentDefinitionOutline} from "../form-component.outline";
305
+ import {AvailableFieldLayoutDefinitionFrames, AvailableFieldLayoutDefinitionOutlines} from "../dictionary.outline";
306
+
307
+ /* ${this.pascalName} Component */
308
+ export const ${this.componentNameConstant} = "${this.componentClassName}" as const;
309
+ export type ${this.componentNameConstant}Type = typeof ${this.componentNameConstant};
310
+
311
+ export interface ${this.pascalName}FieldComponentConfigFrame extends FieldComponentConfigFrame {
312
+ }
313
+
314
+ export interface ${this.pascalName}FieldComponentConfigOutline extends ${this.pascalName}FieldComponentConfigFrame, FieldComponentConfigOutline {
315
+ }
316
+
317
+ export interface ${this.pascalName}FieldComponentDefinitionFrame extends FieldComponentDefinitionFrame {
318
+ class: ${this.componentNameConstant}Type;
319
+ config?: ${this.pascalName}FieldComponentConfigFrame;
320
+ }
321
+
322
+ export interface ${this.pascalName}FieldComponentDefinitionOutline extends ${this.pascalName}FieldComponentDefinitionFrame, FieldComponentDefinitionOutline {
323
+ class: ${this.componentNameConstant}Type;
324
+ config?: ${this.pascalName}FieldComponentConfigOutline;
325
+ }
326
+
327
+ /* ${this.pascalName} Model */
328
+ export const ${this.modelNameConstant} = "${this.modelClassName}" as const;
329
+ export type ${this.modelNameConstant}Type = typeof ${this.modelNameConstant};
330
+ export type ${this.pascalName}ModelValueType = unknown;
331
+
332
+ export interface ${this.pascalName}FieldModelConfigFrame extends FieldModelConfigFrame<${this.pascalName}ModelValueType> {
333
+ }
334
+
335
+ export interface ${this.pascalName}FieldModelConfigOutline extends ${this.pascalName}FieldModelConfigFrame, FieldModelConfigOutline<${this.pascalName}ModelValueType> {
336
+ }
337
+
338
+ export interface ${this.pascalName}FieldModelDefinitionFrame extends FieldModelDefinitionFrame<${this.pascalName}ModelValueType> {
339
+ class: ${this.modelNameConstant}Type;
340
+ config?: ${this.pascalName}FieldModelConfigFrame;
341
+ }
342
+
343
+ export interface ${this.pascalName}FieldModelDefinitionOutline extends ${this.pascalName}FieldModelDefinitionFrame, FieldModelDefinitionOutline<${this.pascalName}ModelValueType> {
344
+ class: ${this.modelNameConstant}Type;
345
+ config?: ${this.pascalName}FieldModelConfigOutline;
346
+ }
347
+
348
+ /* ${this.pascalName} Form Component */
349
+ export interface ${this.pascalName}FormComponentDefinitionFrame extends FormComponentDefinitionFrame {
350
+ component: ${this.pascalName}FieldComponentDefinitionFrame;
351
+ model?: ${this.pascalName}FieldModelDefinitionFrame;
352
+ layout?: AvailableFieldLayoutDefinitionFrames;
353
+ }
354
+
355
+ export interface ${this.pascalName}FormComponentDefinitionOutline extends ${this.pascalName}FormComponentDefinitionFrame, FormComponentDefinitionOutline {
356
+ component: ${this.pascalName}FieldComponentDefinitionOutline;
357
+ model?: ${this.pascalName}FieldModelDefinitionOutline;
358
+ layout?: AvailableFieldLayoutDefinitionOutlines;
359
+ }
360
+
361
+ export type ${this.pascalName}Types =
362
+ | { kind: FieldComponentConfigFrameKindType, class: ${this.pascalName}FieldComponentConfigFrame }
363
+ | { kind: FieldComponentDefinitionFrameKindType, class: ${this.pascalName}FieldComponentDefinitionFrame }
364
+ | { kind: FieldModelConfigFrameKindType, class: ${this.pascalName}FieldModelConfigFrame }
365
+ | { kind: FieldModelDefinitionFrameKindType, class: ${this.pascalName}FieldModelDefinitionFrame }
366
+ | { kind: FormComponentDefinitionFrameKindType, class: ${this.pascalName}FormComponentDefinitionFrame }
367
+ | { kind: FieldComponentConfigKindType, class: ${this.pascalName}FieldComponentConfigOutline }
368
+ | { kind: FieldComponentDefinitionKindType, class: ${this.pascalName}FieldComponentDefinitionOutline }
369
+ | { kind: FieldModelConfigKindType, class: ${this.pascalName}FieldModelConfigOutline }
370
+ | { kind: FieldModelDefinitionKindType, class: ${this.pascalName}FieldModelDefinitionOutline }
371
+ | { kind: FormComponentDefinitionKindType, class: ${this.pascalName}FormComponentDefinitionOutline }
372
+ ;
373
+ `;
374
+ const modelContent = `import {FieldModelConfig, FieldModelDefinition} from "../field-model.model";
375
+ import {FormComponentDefinition} from "../form-component.model";
376
+ import {FormConfigVisitorOutline} from "../visitor/base.outline";
377
+ import {
378
+ FieldComponentConfigKind,
379
+ FieldComponentDefinitionKind,
380
+ FieldModelConfigKind,
381
+ FieldModelDefinitionKind, FormComponentDefinitionKind
382
+ } from "../shared.outline";
383
+ import {FieldComponentConfig, FieldComponentDefinition} from "../field-component.model";
384
+ import {AvailableFieldLayoutDefinitionOutlines} from "../dictionary.outline";
385
+ import {
386
+ ${this.componentNameConstant},
387
+ ${this.pascalName}FieldComponentConfigOutline,
388
+ ${this.pascalName}FieldComponentDefinitionOutline,
389
+ ${this.pascalName}FieldModelConfigOutline,
390
+ ${this.pascalName}FieldModelDefinitionOutline,
391
+ ${this.pascalName}FormComponentDefinitionOutline,
392
+ ${this.modelNameConstant},
393
+ ${this.pascalName}ModelValueType
394
+ } from "./${this.kebabName}.outline";
395
+
396
+ /* ${this.pascalName} Component */
397
+
398
+ export class ${this.pascalName}FieldComponentConfig extends FieldComponentConfig implements ${this.pascalName}FieldComponentConfigOutline {
399
+ constructor() {
400
+ super();
401
+ }
402
+ }
403
+
404
+ export class ${this.pascalName}FieldComponentDefinition extends FieldComponentDefinition implements ${this.pascalName}FieldComponentDefinitionOutline {
405
+ class = ${this.componentNameConstant};
406
+ config?: ${this.pascalName}FieldComponentConfigOutline;
407
+
408
+ constructor() {
409
+ super();
410
+ }
411
+
412
+ async accept(visitor: FormConfigVisitorOutline) {
413
+ await visitor.visit${this.pascalName}FieldComponentDefinition(this);
414
+ }
415
+ }
416
+
417
+ /* ${this.pascalName} Model */
418
+
419
+ export class ${this.pascalName}FieldModelConfig extends FieldModelConfig<${this.pascalName}ModelValueType> implements ${this.pascalName}FieldModelConfigOutline {
420
+ constructor() {
421
+ super();
422
+ }
423
+ }
424
+
425
+ export class ${this.pascalName}FieldModelDefinition extends FieldModelDefinition<${this.pascalName}ModelValueType> implements ${this.pascalName}FieldModelDefinitionOutline {
426
+ class = ${this.modelNameConstant};
427
+ config?: ${this.pascalName}FieldModelConfigOutline;
428
+
429
+ constructor() {
430
+ super();
431
+ }
432
+
433
+ async accept(visitor: FormConfigVisitorOutline) {
434
+ await visitor.visit${this.pascalName}FieldModelDefinition(this);
435
+ }
436
+ }
437
+
438
+ /* ${this.pascalName} Form Component */
439
+
440
+ export class ${this.pascalName}FormComponentDefinition extends FormComponentDefinition implements ${this.pascalName}FormComponentDefinitionOutline {
441
+ public component!: ${this.pascalName}FieldComponentDefinitionOutline;
442
+ public model?: ${this.pascalName}FieldModelDefinitionOutline;
443
+ public layout?: AvailableFieldLayoutDefinitionOutlines;
444
+
445
+ constructor() {
446
+ super();
447
+ }
448
+
449
+ async accept(visitor: FormConfigVisitorOutline) {
450
+ await visitor.visit${this.pascalName}FormComponentDefinition(this);
451
+ }
452
+ }
453
+
454
+ export const ${this.pascalName}Map = [
455
+ {kind: FieldComponentConfigKind, def: ${this.pascalName}FieldComponentConfig},
456
+ {kind: FieldComponentDefinitionKind, def: ${this.pascalName}FieldComponentDefinition, class: ${this.componentNameConstant}},
457
+ {kind: FieldModelConfigKind, def: ${this.pascalName}FieldModelConfig},
458
+ {kind: FieldModelDefinitionKind, def: ${this.pascalName}FieldModelDefinition, class: ${this.modelNameConstant}},
459
+ {kind: FormComponentDefinitionKind, def: ${this.pascalName}FormComponentDefinition, class: ${this.componentNameConstant}},
460
+ ];
461
+ export const ${this.pascalName}Defaults = {
462
+ [FormComponentDefinitionKind]: {
463
+ [${this.componentNameConstant}]: {
464
+ [FieldComponentDefinitionKind]: ${this.componentNameConstant},
465
+ [FieldModelDefinitionKind]: ${this.modelNameConstant},
466
+ }
467
+ }
468
+ };
469
+ `;
470
+ this.writeFile(outlinePath, outlineContent);
471
+ this.writeFile(modelPath, modelContent);
472
+ }
473
+ generateAngularComponentFiles() {
474
+ const appDir = path.join(this.paths.angular, 'projects', 'researchdatabox', this.app, 'src', 'app');
475
+ const componentPath = path.join(appDir, 'component', `${this.kebabName}.component.ts`);
476
+ const specPath = path.join(appDir, 'component', `${this.kebabName}.component.spec.ts`);
477
+ const componentContent = `import { Component, Input, Injector, inject } from "@angular/core";
478
+ import { FormFieldBaseComponent, FormFieldCompMapEntry, FormFieldModel } from "@researchdatabox/portal-ng-common";
479
+ import {
480
+ ${this.componentNameConstant},
481
+ ${this.pascalName}FieldComponentConfig,
482
+ ${this.modelNameConstant},
483
+ ${this.pascalName}ModelValueType
484
+ } from "@researchdatabox/sails-ng-common";
485
+ import { FormComponent } from "../form.component";
486
+
487
+ export class ${this.modelClassName} extends FormFieldModel<${this.pascalName}ModelValueType> {
488
+ protected override logName = ${this.modelNameConstant};
489
+ }
490
+
491
+ @Component({
492
+ selector: "redbox-${this.kebabName}",
493
+ template: \`
494
+ @if (isVisible) {
495
+ <ng-container *ngTemplateOutlet="getTemplateRef('before')" />
496
+ <div class="redbox-${this.kebabName}">
497
+ <!-- TODO: implement ${this.componentClassName} UI -->
498
+ </div>
499
+ <ng-container *ngTemplateOutlet="getTemplateRef('after')" />
500
+ }
501
+ \`,
502
+ standalone: false
503
+ })
504
+ export class ${this.componentClassName} extends FormFieldBaseComponent<${this.pascalName}ModelValueType> {
505
+ protected override logName = ${this.componentNameConstant};
506
+ private readonly injector = inject(Injector);
507
+
508
+ @Input() public override model?: ${this.modelClassName};
509
+
510
+ protected get getFormComponent(): FormComponent {
511
+ return this.formComponent;
512
+ }
513
+
514
+ protected override setPropertiesFromComponentMapEntry(formFieldCompMapEntry: FormFieldCompMapEntry): void {
515
+ super.setPropertiesFromComponentMapEntry(formFieldCompMapEntry);
516
+ const cfg = (this.componentDefinition?.config as ${this.pascalName}FieldComponentConfig) ?? new ${this.pascalName}FieldComponentConfig();
517
+ void cfg;
518
+ }
519
+
520
+ protected override async initData(): Promise<void> {
521
+ // TODO: initialize async data for ${this.componentClassName} when needed.
522
+ }
523
+ }
524
+ `;
525
+ const specContent = `import { TestBed } from "@angular/core/testing";
526
+ import { ${this.componentClassName} } from "./${this.kebabName}.component";
527
+ import { createTestbedModule } from "../helpers.spec";
528
+
529
+ describe("${this.componentClassName}", () => {
530
+ beforeEach(async () => {
531
+ await createTestbedModule({
532
+ declarations: {
533
+ "${this.componentClassName}": ${this.componentClassName}
534
+ }
535
+ });
536
+ });
537
+
538
+ it("should create component", () => {
539
+ const fixture = TestBed.createComponent(${this.componentClassName});
540
+ const component = fixture.componentInstance;
541
+ expect(component).toBeDefined();
542
+ });
543
+ });
544
+ `;
545
+ this.writeFile(componentPath, componentContent);
546
+ this.writeFile(specPath, specContent);
547
+ }
548
+ generateAngularServiceFiles() {
549
+ const appDir = path.join(this.paths.angular, 'projects', 'researchdatabox', this.app, 'src', 'app');
550
+ const servicePath = path.join(appDir, 'service', `${this.kebabName}.service.ts`);
551
+ const specPath = path.join(appDir, 'service', `${this.kebabName}.service.spec.ts`);
552
+ const serviceClassName = `${this.pascalName}Service`;
553
+ const serviceContent = `import { APP_BASE_HREF } from "@angular/common";
554
+ import { HttpClient } from "@angular/common/http";
555
+ import { Inject, Injectable } from "@angular/core";
556
+ import { ConfigService, HttpClientService, UtilityService } from "@researchdatabox/portal-ng-common";
557
+
558
+ @Injectable({ providedIn: "root" })
559
+ export class ${serviceClassName} extends HttpClientService {
560
+ constructor(
561
+ @Inject(HttpClient) protected override http: HttpClient,
562
+ @Inject(APP_BASE_HREF) public override rootContext: string,
563
+ @Inject(UtilityService) protected override utilService: UtilityService,
564
+ @Inject(ConfigService) protected override configService: ConfigService
565
+ ) {
566
+ super(http, rootContext, utilService, configService);
567
+ }
568
+
569
+ public override async waitForInit(): Promise<this> {
570
+ await super.waitForInit();
571
+ this.enableCsrfHeader();
572
+ return this;
573
+ }
574
+ }
575
+ `;
576
+ const specContent = `import { TestBed } from "@angular/core/testing";
577
+ import { APP_BASE_HREF } from "@angular/common";
578
+ import { provideHttpClient } from "@angular/common/http";
579
+ import {
580
+ ConfigService,
581
+ getStubConfigService,
582
+ LoggerService,
583
+ UtilityService
584
+ } from "@researchdatabox/portal-ng-common";
585
+ import { ${serviceClassName} } from "./${this.kebabName}.service";
586
+
587
+ describe("${serviceClassName}", () => {
588
+ it("should create service", () => {
589
+ TestBed.configureTestingModule({
590
+ providers: [
591
+ { provide: APP_BASE_HREF, useValue: "" },
592
+ { provide: ConfigService, useValue: getStubConfigService() },
593
+ LoggerService,
594
+ UtilityService,
595
+ provideHttpClient(),
596
+ ${serviceClassName}
597
+ ]
598
+ });
599
+
600
+ const service = TestBed.inject(${serviceClassName});
601
+ expect(service).toBeDefined();
602
+ });
603
+ });
604
+ `;
605
+ this.writeFile(servicePath, serviceContent);
606
+ this.writeFile(specPath, specContent);
607
+ }
608
+ updateSailsNgCommonDictionaries() {
609
+ const dictionaryOutlinePath = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'config', 'dictionary.outline.ts');
610
+ this.updateTextFile(dictionaryOutlinePath, content => {
611
+ let updated = this.ensureImport(content, `import {${this.pascalName}Types} from "./component/${this.kebabName}.outline";`);
612
+ updated = this.insertBeforeMarker(updated, ' ;', ` | ${this.pascalName}Types\n`);
613
+ return updated;
614
+ });
615
+ const dictionaryModelPath = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'config', 'dictionary.model.ts');
616
+ this.updateTextFile(dictionaryModelPath, content => {
617
+ let updated = this.ensureImport(content, `import {${this.pascalName}Defaults, ${this.pascalName}Map} from "./component/${this.kebabName}.model";`);
618
+ updated = this.appendToArrayLiteral(updated, /export const AllDefs\s*=\s*\[/, ` ...${this.pascalName}Map,\n`);
619
+ updated = this.appendToArrayLiteral(updated, /const RawDefaults\s*=\s*\[/, ` ${this.pascalName}Defaults,\n`);
620
+ return updated;
621
+ });
622
+ }
623
+ updateSailsNgCommonIndex() {
624
+ const indexPath = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'index.ts');
625
+ this.updateTextFile(indexPath, content => {
626
+ let updated = content;
627
+ const exportModelLine = `export * from "./config/component/${this.kebabName}.model";`;
628
+ const exportOutlineLine = `export * from "./config/component/${this.kebabName}.outline";`;
629
+ if (!updated.includes(exportModelLine)) {
630
+ updated = this.insertBeforeMarker(updated, '// validation', `${exportModelLine}\n`);
631
+ }
632
+ if (!updated.includes(exportOutlineLine)) {
633
+ updated = this.insertBeforeMarker(updated, '// validation', `${exportOutlineLine}\n`);
634
+ }
635
+ return updated;
636
+ });
637
+ }
638
+ updateBaseVisitors() {
639
+ const baseOutlinePath = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'config', 'visitor', 'base.outline.ts');
640
+ this.updateTextFile(baseOutlinePath, content => {
641
+ let updated = this.ensureImport(content, `import {\n ${this.pascalName}FieldComponentDefinitionOutline,\n ${this.pascalName}FieldModelDefinitionOutline,\n ${this.pascalName}FormComponentDefinitionOutline\n} from "../component/${this.kebabName}.outline";`);
642
+ const methodsBlock = `
643
+ /* ${this.pascalName} */
644
+
645
+ visit${this.pascalName}FieldComponentDefinition(item: ${this.pascalName}FieldComponentDefinitionOutline): void;
646
+
647
+ visit${this.pascalName}FieldModelDefinition(item: ${this.pascalName}FieldModelDefinitionOutline): void;
648
+
649
+ visit${this.pascalName}FormComponentDefinition(item: ${this.pascalName}FormComponentDefinitionOutline): void;
650
+ `;
651
+ updated = this.insertBeforeMarker(updated, '\n}', methodsBlock);
652
+ return updated;
653
+ });
654
+ const baseModelPath = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'config', 'visitor', 'base.model.ts');
655
+ this.updateTextFile(baseModelPath, content => {
656
+ let updated = this.ensureImport(content, `import {\n ${this.pascalName}FieldComponentDefinitionOutline,\n ${this.pascalName}FieldModelDefinitionOutline,\n ${this.pascalName}FormComponentDefinitionOutline\n} from "../component/${this.kebabName}.outline";`);
657
+ const methodsBlock = `
658
+ /* ${this.pascalName} */
659
+
660
+ visit${this.pascalName}FieldComponentDefinition(item: ${this.pascalName}FieldComponentDefinitionOutline): void {
661
+ this.notImplemented();
662
+ }
663
+
664
+ visit${this.pascalName}FieldModelDefinition(item: ${this.pascalName}FieldModelDefinitionOutline): void {
665
+ this.notImplemented();
666
+ }
667
+
668
+ visit${this.pascalName}FormComponentDefinition(item: ${this.pascalName}FormComponentDefinitionOutline): void {
669
+ this.notImplemented();
670
+ }
671
+ `;
672
+ updated = this.insertBeforeMarker(updated, '\n /* Shared */', methodsBlock);
673
+ return updated;
674
+ });
675
+ }
676
+ updateConcreteVisitors() {
677
+ this.updateClientVisitor();
678
+ this.updateDataValueVisitor();
679
+ this.updateJsonTypeDefVisitor();
680
+ this.updateValidatorVisitor();
681
+ this.updateTemplateVisitor();
682
+ this.updateConstructVisitor();
683
+ this.updateMigrateVisitor();
684
+ }
685
+ updateClientVisitor() {
686
+ const filePath = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'config', 'visitor', 'client.visitor.ts');
687
+ this.updateTextFile(filePath, content => {
688
+ let updated = this.ensureImport(content, `import {\n ${this.pascalName}FieldComponentDefinitionOutline,\n ${this.pascalName}FieldModelDefinitionOutline,\n ${this.pascalName}FormComponentDefinitionOutline\n} from "../component/${this.kebabName}.outline";`);
689
+ const block = `
690
+ /* ${this.pascalName} */
691
+
692
+ visit${this.pascalName}FieldComponentDefinition(item: ${this.pascalName}FieldComponentDefinitionOutline): void {
693
+ this.processFieldComponentDefinition(item);
694
+ }
695
+
696
+ visit${this.pascalName}FieldModelDefinition(item: ${this.pascalName}FieldModelDefinitionOutline): void {
697
+ this.processFieldModelDefinition(item);
698
+ }
699
+
700
+ visit${this.pascalName}FormComponentDefinition(item: ${this.pascalName}FormComponentDefinitionOutline): void {
701
+ this.acceptCheckConstraintsCurrentPath(item);
702
+ this.processFormComponentDefinition(item);
703
+ }
704
+ `;
705
+ updated = this.insertBeforeMarker(updated, '\n /* Dropdown Input */', block);
706
+ return updated;
707
+ });
708
+ }
709
+ updateDataValueVisitor() {
710
+ const filePath = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'config', 'visitor', 'data-value.visitor.ts');
711
+ this.updateTextFile(filePath, content => {
712
+ let updated = this.ensureImport(content, `import {\n ${this.pascalName}FieldComponentDefinitionOutline,\n ${this.pascalName}FieldModelDefinitionOutline,\n ${this.pascalName}FormComponentDefinitionOutline\n} from "../component/${this.kebabName}.outline";`);
713
+ const block = `
714
+ /* ${this.pascalName} */
715
+
716
+ visit${this.pascalName}FieldComponentDefinition(item: ${this.pascalName}FieldComponentDefinitionOutline): void {
717
+ this.acceptFieldComponentDefinition(item);
718
+ }
719
+
720
+ visit${this.pascalName}FieldModelDefinition(item: ${this.pascalName}FieldModelDefinitionOutline): void {
721
+ this.acceptFieldModelDefinition(item);
722
+ }
723
+
724
+ visit${this.pascalName}FormComponentDefinition(item: ${this.pascalName}FormComponentDefinitionOutline): void {
725
+ this.acceptFormComponentDefinition(item);
726
+ }
727
+ `;
728
+ updated = this.insertBeforeMarker(updated, '\n /* Dropdown Input */', block);
729
+ return updated;
730
+ });
731
+ }
732
+ updateJsonTypeDefVisitor() {
733
+ const filePath = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'config', 'visitor', 'json-type-def.visitor.ts');
734
+ this.updateTextFile(filePath, content => {
735
+ let updated = this.ensureImport(content, `import {\n ${this.pascalName}FieldComponentDefinitionOutline,\n ${this.pascalName}FieldModelDefinitionOutline,\n ${this.pascalName}FormComponentDefinitionOutline\n} from "../component/${this.kebabName}.outline";`);
736
+ const block = `
737
+ /* ${this.pascalName} */
738
+
739
+ visit${this.pascalName}FieldComponentDefinition(item: ${this.pascalName}FieldComponentDefinitionOutline): void {
740
+ this.acceptFieldComponentDefinition(item);
741
+ }
742
+
743
+ visit${this.pascalName}FieldModelDefinition(item: ${this.pascalName}FieldModelDefinitionOutline): void {
744
+ this.acceptFieldModelDefinition(item);
745
+ }
746
+
747
+ visit${this.pascalName}FormComponentDefinition(item: ${this.pascalName}FormComponentDefinitionOutline): void {
748
+ this.acceptFormComponentDefinition(item);
749
+ }
750
+ `;
751
+ updated = this.insertBeforeMarker(updated, '\n /* Dropdown Input */', block);
752
+ return updated;
753
+ });
754
+ }
755
+ updateValidatorVisitor() {
756
+ const corePath = path.join(this.root, 'packages', 'redbox-core', 'src', 'visitor', 'validator.visitor.ts');
757
+ const legacyPath = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'config', 'visitor', 'validator.visitor.ts');
758
+ const filePath = fs.existsSync(corePath) ? corePath : legacyPath;
759
+ this.updateTextFile(filePath, content => {
760
+ let updated = content;
761
+ const isCoreValidator = filePath === corePath;
762
+ if (!isCoreValidator) {
763
+ updated = this.ensureImport(content, `import {\n ${this.pascalName}FieldComponentDefinitionOutline,\n ${this.pascalName}FieldModelDefinitionOutline,\n ${this.pascalName}FormComponentDefinitionOutline\n} from "../component/${this.kebabName}.outline";`);
764
+ }
765
+ const block = `
766
+ /* ${this.pascalName} */
767
+
768
+ visit${this.pascalName}FieldComponentDefinition(item: ${isCoreValidator ? 'any' : `${this.pascalName}FieldComponentDefinitionOutline`}): void {
769
+ }
770
+
771
+ visit${this.pascalName}FieldModelDefinition(item: ${isCoreValidator ? 'any' : `${this.pascalName}FieldModelDefinitionOutline`}): void {
772
+ }
773
+
774
+ visit${this.pascalName}FormComponentDefinition(item: ${isCoreValidator ? 'any' : `${this.pascalName}FormComponentDefinitionOutline`}): void {
775
+ this.acceptFormComponentDefinition(item);
776
+ }
777
+ `;
778
+ updated = this.insertBeforeMarker(updated, '\n /* Dropdown Input */', block);
779
+ return updated;
780
+ });
781
+ }
782
+ updateTemplateVisitor() {
783
+ const filePath = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'config', 'visitor', 'template.visitor.ts');
784
+ this.updateTextFile(filePath, content => {
785
+ let updated = this.ensureImport(content, `import {\n ${this.pascalName}FieldComponentDefinitionOutline,\n ${this.pascalName}FieldModelDefinitionOutline,\n ${this.pascalName}FormComponentDefinitionOutline\n} from "../component/${this.kebabName}.outline";`);
786
+ const block = `
787
+ /* ${this.pascalName} */
788
+
789
+ visit${this.pascalName}FieldComponentDefinition(item: ${this.pascalName}FieldComponentDefinitionOutline): void {
790
+ }
791
+
792
+ visit${this.pascalName}FieldModelDefinition(item: ${this.pascalName}FieldModelDefinitionOutline): void {
793
+ }
794
+
795
+ visit${this.pascalName}FormComponentDefinition(item: ${this.pascalName}FormComponentDefinitionOutline): void {
796
+ this.acceptFormComponentDefinition(item);
797
+ }
798
+ `;
799
+ updated = this.insertBeforeMarker(updated, '\n /* Dropdown Input */', block);
800
+ return updated;
801
+ });
802
+ }
803
+ updateConstructVisitor() {
804
+ const filePath = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'config', 'visitor', 'construct.visitor.ts');
805
+ this.updateTextFile(filePath, content => {
806
+ let updated = this.ensureImport(content, `import {\n ${this.componentNameConstant},\n ${this.pascalName}FieldComponentDefinitionFrame,\n ${this.pascalName}FieldComponentDefinitionOutline,\n ${this.pascalName}FieldModelDefinitionFrame,\n ${this.pascalName}FieldModelDefinitionOutline,\n ${this.pascalName}FormComponentDefinitionOutline,\n ${this.modelNameConstant}\n} from "../component/${this.kebabName}.outline";`);
807
+ updated = this.ensureImport(updated, `import {${this.pascalName}FieldComponentConfig, ${this.pascalName}FieldModelConfig} from "../component/${this.kebabName}.model";`);
808
+ const block = `
809
+ /* ${this.pascalName} */
810
+
811
+ visit${this.pascalName}FieldComponentDefinition(item: ${this.pascalName}FieldComponentDefinitionOutline): void {
812
+ // Get the current raw data for constructing the class instance.
813
+ const currentData = this.getData();
814
+ if (!isTypeFieldDefinitionName<${this.pascalName}FieldComponentDefinitionFrame>(currentData, ${this.componentNameConstant})) {
815
+ throw new Error(\`Invalid ${this.componentClassName} at '\${this.formPathHelper.formPath.formConfig}': \${JSON.stringify(currentData)}\`);
816
+ }
817
+ const config = currentData?.config;
818
+
819
+ // Create the class instance for the config
820
+ item.config = new ${this.pascalName}FieldComponentConfig();
821
+ this.sharedProps.sharedPopulateFieldComponentConfig(item.config, config);
822
+ }
823
+
824
+ visit${this.pascalName}FieldModelDefinition(item: ${this.pascalName}FieldModelDefinitionOutline): void {
825
+ // Get the current raw data for constructing the class instance.
826
+ const currentData = this.getData();
827
+ if (!isTypeFieldDefinitionName<${this.pascalName}FieldModelDefinitionFrame>(currentData, ${this.modelNameConstant})) {
828
+ throw new Error(\`Invalid ${this.modelClassName} at '\${this.formPathHelper.formPath.formConfig}': \${JSON.stringify(currentData)}\`);
829
+ }
830
+
831
+ // Create the class instance for the config
832
+ item.config = new ${this.pascalName}FieldModelConfig();
833
+ this.sharedProps.sharedPopulateFieldModelConfig(item.config, currentData?.config);
834
+ this.setModelValue(item, currentData?.config);
835
+ }
836
+
837
+ visit${this.pascalName}FormComponentDefinition(item: ${this.pascalName}FormComponentDefinitionOutline): void {
838
+ this.populateFormComponent(item);
839
+ }
840
+ `;
841
+ updated = this.insertBeforeMarker(updated, '\n /* Dropdown Input */', block);
842
+ return updated;
843
+ });
844
+ }
845
+ updateMigrateVisitor() {
846
+ const filePath = path.join(this.root, 'packages', 'sails-ng-common', 'src', 'config', 'visitor', 'migrate-config-v4-v5.visitor.ts');
847
+ this.updateTextFile(filePath, content => {
848
+ let updated = this.ensureImport(content, `import {\n ${this.pascalName}FieldComponentDefinitionOutline,\n ${this.pascalName}FieldModelDefinitionOutline,\n ${this.pascalName}FormComponentDefinitionOutline\n} from "../component/${this.kebabName}.outline";`);
849
+ updated = this.ensureImport(updated, `import {${this.pascalName}FieldComponentConfig, ${this.pascalName}FieldModelConfig} from "../component/${this.kebabName}.model";`);
850
+ const block = `
851
+ /* ${this.pascalName} */
852
+
853
+ visit${this.pascalName}FieldComponentDefinition(item: ${this.pascalName}FieldComponentDefinitionOutline): void {
854
+ const field = this.getV4Data();
855
+ item.config = new ${this.pascalName}FieldComponentConfig();
856
+ this.sharedPopulateFieldComponentConfig(item.config, field);
857
+ }
858
+
859
+ visit${this.pascalName}FieldModelDefinition(item: ${this.pascalName}FieldModelDefinitionOutline): void {
860
+ const field = this.getV4Data();
861
+ item.config = new ${this.pascalName}FieldModelConfig();
862
+ this.sharedPopulateFieldModelConfig(item.config, field);
863
+ }
864
+
865
+ visit${this.pascalName}FormComponentDefinition(item: ${this.pascalName}FormComponentDefinitionOutline): void {
866
+ this.populateFormComponent(item);
867
+ }
868
+ `;
869
+ updated = this.insertBeforeMarker(updated, '\n /* Dropdown Input */', block);
870
+ return updated;
871
+ });
872
+ }
873
+ updateAngularWiring() {
874
+ const staticDictionaryPath = path.join(this.paths.angular, 'projects', 'researchdatabox', this.app, 'src', 'app', 'static-comp-field.dictionary.ts');
875
+ this.updateTextFile(staticDictionaryPath, content => {
876
+ let updated = content;
877
+ const compImport = `import {${this.componentClassName}, ${this.modelClassName}} from "./component/${this.kebabName}.component";`;
878
+ const constantsImport = ` ${this.modelNameConstant},`;
879
+ const constantsCompImport = ` ${this.componentNameConstant},`;
880
+ updated = this.ensureImport(updated, compImport);
881
+ const marker = '} from "@researchdatabox/sails-ng-common";';
882
+ const insertMissingConstant = (source, constantLine) => {
883
+ if (source.includes(constantLine)) {
884
+ return source;
885
+ }
886
+ const idx = source.indexOf(marker);
887
+ if (idx === -1) {
888
+ throw new Error(`Unable to find sails-ng-common import in ${staticDictionaryPath}`);
889
+ }
890
+ return `${source.slice(0, idx)}${constantLine}\n${source.slice(idx)}`;
891
+ };
892
+ updated = insertMissingConstant(updated, constantsImport);
893
+ updated = insertMissingConstant(updated, constantsCompImport);
894
+ updated = this.appendToObjectLiteral(updated, /export const StaticComponentClassMap[^=]*=\s*\{/, ` [${this.componentNameConstant}]: ${this.componentClassName},\n`);
895
+ updated = this.appendToObjectLiteral(updated, /export const StaticModelClassMap[^=]*=\s*\{/, ` [${this.modelNameConstant}]: ${this.modelClassName},\n`);
896
+ return updated;
897
+ });
898
+ const formModulePath = path.join(this.paths.angular, 'projects', 'researchdatabox', this.app, 'src', 'app', 'form.module.ts');
899
+ this.updateTextFile(formModulePath, content => {
900
+ let updated = this.ensureImport(content, `import { ${this.componentClassName} } from './component/${this.kebabName}.component';`);
901
+ updated = this.appendToArrayLiteral(updated, /declarations:\s*\[/, ` ${this.componentClassName},\n`);
902
+ return updated;
903
+ });
904
+ }
905
+ }
906
+ exports.FormComponentGenerator = FormComponentGenerator;