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

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 (104) 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 +145 -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 +464 -0
  15. package/dist/commands/form-config.d.ts +5 -0
  16. package/dist/commands/form-config.js +194 -0
  17. package/dist/completion/index.d.ts +1 -0
  18. package/dist/completion/index.js +217 -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/model.d.ts +42 -0
  32. package/dist/generators/model.js +206 -0
  33. package/dist/generators/service.d.ts +16 -0
  34. package/dist/generators/service.js +111 -0
  35. package/dist/runtime-resolver.d.ts +3 -0
  36. package/dist/runtime-resolver.js +27 -0
  37. package/dist/templates/hook-archetype.d.ts +12 -0
  38. package/dist/templates/hook-archetype.js +143 -0
  39. package/dist/testing.d.ts +7 -0
  40. package/dist/testing.js +53 -0
  41. package/dist/utils/config-helper.d.ts +71 -0
  42. package/dist/utils/config-helper.js +574 -0
  43. package/dist/utils/generator.d.ts +10 -0
  44. package/dist/utils/generator.js +68 -0
  45. package/dist/utils/hook-tsconfig.d.ts +9 -0
  46. package/dist/utils/hook-tsconfig.js +104 -0
  47. package/dist/utils/paths.d.ts +10 -0
  48. package/dist/utils/paths.js +60 -0
  49. package/index.js +4 -0
  50. package/package.json +85 -0
  51. package/runtime-resolver.d.ts +1 -0
  52. package/runtime-resolver.js +3 -0
  53. package/skills/redbox-angular-apps/SKILL.md +42 -0
  54. package/skills/redbox-angular-services/SKILL.md +29 -0
  55. package/skills/redbox-controllers/SKILL.md +65 -0
  56. package/skills/redbox-feature-design-planner/SKILL.md +169 -0
  57. package/skills/redbox-feature-implementation-review/SKILL.md +117 -0
  58. package/skills/redbox-form-components/SKILL.md +114 -0
  59. package/skills/redbox-form-config/SKILL.md +34 -0
  60. package/skills/redbox-hook-development/SKILL.md +66 -0
  61. package/skills/redbox-services/SKILL.md +54 -0
  62. package/skills/redbox-test-verification/SKILL.md +38 -0
  63. package/skills/redbox-testing/SKILL.md +64 -0
  64. package/skills/redbox-web-verification/SKILL.md +71 -0
  65. package/skills/redbox-web-verification/redbox-angular-apps/SKILL.md +48 -0
  66. package/skills/redbox-web-verification/redbox-angular-services/SKILL.md +30 -0
  67. package/skills/redbox-web-verification/redbox-controllers/SKILL.md +56 -0
  68. package/skills/redbox-web-verification/redbox-form-config/SKILL.md +29 -0
  69. package/skills/redbox-web-verification/redbox-hook-development/SKILL.md +61 -0
  70. package/skills/redbox-web-verification/redbox-services/SKILL.md +45 -0
  71. package/skills/redbox-web-verification/redbox-testing/SKILL.md +53 -0
  72. package/skills/redbox-web-verification/redbox-web-verification/SKILL.md +47 -0
  73. package/templates/hook-archetype/standard/.gitignore.hbs +4 -0
  74. package/templates/hook-archetype/standard/.npmignore.hbs +3 -0
  75. package/templates/hook-archetype/standard/.npmrc.hbs +6 -0
  76. package/templates/hook-archetype/standard/.nvmrc.hbs +1 -0
  77. package/templates/hook-archetype/standard/AGENTS.md.hbs +34 -0
  78. package/templates/hook-archetype/standard/Dockerfile.hbs +28 -0
  79. package/templates/hook-archetype/standard/README.md.hbs +39 -0
  80. package/templates/hook-archetype/standard/assets/fonts/.gitkeep +1 -0
  81. package/templates/hook-archetype/standard/language-defaults/.gitkeep +1 -0
  82. package/templates/hook-archetype/standard/package.json.hbs +40 -0
  83. package/templates/hook-archetype/standard/src/api/controllers/ExampleController.ts.hbs +21 -0
  84. package/templates/hook-archetype/standard/src/api/controllers/index.ts.hbs +23 -0
  85. package/templates/hook-archetype/standard/src/api/services/ExampleService.ts.hbs +22 -0
  86. package/templates/hook-archetype/standard/src/api/services/index.ts.hbs +23 -0
  87. package/templates/hook-archetype/standard/src/config/brandingConfigurationDefaults.ts.hbs +9 -0
  88. package/templates/hook-archetype/standard/src/config/reusableFormDefinitions.ts.hbs +6 -0
  89. package/templates/hook-archetype/standard/src/form-config/default-1.0-draft.ts.hbs +20 -0
  90. package/templates/hook-archetype/standard/src/form-config/index.ts.hbs +8 -0
  91. package/templates/hook-archetype/standard/src/index.ts.hbs +55 -0
  92. package/templates/hook-archetype/standard/support/bootstrap-data/.gitkeep +1 -0
  93. package/templates/hook-archetype/standard/support/development/docker-compose.yml.hbs +75 -0
  94. package/templates/hook-archetype/standard/support/integration-testing/docker-compose.mocha.yml.hbs +84 -0
  95. package/templates/hook-archetype/standard/support/integration-testing/run-mocha-hook.sh.hbs +9 -0
  96. package/templates/hook-archetype/standard/test/integration/hook-registration.test.ts.hbs +13 -0
  97. package/templates/hook-archetype/standard/test/support/globals.ts.hbs +40 -0
  98. package/templates/hook-archetype/standard/test/tsconfig.json.hbs +8 -0
  99. package/templates/hook-archetype/standard/test/unit/.mocharc.cjs.hbs +9 -0
  100. package/templates/hook-archetype/standard/test/unit/index.test.ts.hbs +40 -0
  101. package/templates/hook-archetype/standard/tsconfig.json.hbs +15 -0
  102. package/templates/hook-archetype/standard/views/.gitkeep +1 -0
  103. package/testing.d.ts +1 -0
  104. package/testing.js +3 -0
@@ -0,0 +1,342 @@
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.AngularAppGenerator = void 0;
37
+ const path = __importStar(require("path"));
38
+ const fs = __importStar(require("fs"));
39
+ const ts_morph_1 = require("ts-morph");
40
+ const generator_1 = require("../utils/generator");
41
+ const config_helper_1 = require("../utils/config-helper");
42
+ class AngularAppGenerator extends generator_1.Generator {
43
+ name;
44
+ ejsView;
45
+ auth;
46
+ paths;
47
+ project;
48
+ constructor(options) {
49
+ super(options);
50
+ this.name = options.name.toLowerCase();
51
+ this.ejsView = options.ejsView;
52
+ this.auth = options.auth;
53
+ this.paths = options.paths;
54
+ this.project = new ts_morph_1.Project();
55
+ }
56
+ async generate() {
57
+ // 1. Scaffold Angular app
58
+ this.scaffoldAngularApp();
59
+ // 2. Update angular.json
60
+ this.updateAngularJson();
61
+ // 3. Generate EJS view
62
+ this.generateEjsView();
63
+ // 4. Update route config
64
+ await this.updateRouteConfig();
65
+ // 5. Update auth config
66
+ if (this.auth && this.auth.length > 0) {
67
+ await this.updateAuthConfig();
68
+ }
69
+ }
70
+ scaffoldAngularApp() {
71
+ const projectRoot = path.join(this.paths.angular, 'projects', 'researchdatabox', this.name);
72
+ const srcRoot = path.join(projectRoot, 'src');
73
+ const appRoot = path.join(srcRoot, 'app');
74
+ const files = {
75
+ 'tsconfig.app.json': JSON.stringify({
76
+ "extends": "../../../tsconfig.json",
77
+ "compilerOptions": {
78
+ "outDir": "../../../dist/out-tsc",
79
+ "types": []
80
+ },
81
+ "files": [
82
+ "src/main.ts"
83
+ ],
84
+ "include": [
85
+ "src/**/*.d.ts"
86
+ ]
87
+ }, null, 2),
88
+ 'tsconfig.spec.json': JSON.stringify({
89
+ "extends": "../../../tsconfig.json",
90
+ "compilerOptions": {
91
+ "outDir": "../../../dist/out-tsc",
92
+ "types": [
93
+ "jasmine"
94
+ ]
95
+ },
96
+ "include": [
97
+ "src/**/*.spec.ts",
98
+ "src/**/*.d.ts"
99
+ ]
100
+ }, null, 2),
101
+ 'src/main.ts': `import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
102
+ import { ${this.toClassName(this.name)}Module } from './app/${this.name}.module';
103
+
104
+ platformBrowserDynamic().bootstrapModule(${this.toClassName(this.name)}Module)
105
+ .catch(err => console.error(err));
106
+ `,
107
+ 'src/styles.scss': `/* Add application styles & imports to this file! */\n`,
108
+ 'src/favicon.ico': '',
109
+ 'src/assets/.gitkeep': '',
110
+ 'src/index.html': `<!doctype html>
111
+ <html lang="en">
112
+ <head>
113
+ <meta charset="utf-8">
114
+ <title>${this.toClassName(this.name)}</title>
115
+ <base href="/">
116
+ <meta name="viewport" content="width=device-width, initial-scale=1">
117
+ <link rel="icon" type="image/x-icon" href="favicon.ico">
118
+ </head>
119
+ <body>
120
+ <${this.name}></${this.name}>
121
+ </body>
122
+ </html>
123
+ `,
124
+ [`src/app/${this.name}.module.ts`]: `import { NgModule } from '@angular/core';
125
+ import { BrowserModule } from '@angular/platform-browser';
126
+ import { APP_BASE_HREF, PlatformLocation, CommonModule } from '@angular/common';
127
+ import { RedboxPortalCoreModule, trimLastSlashFromUrl } from '@researchdatabox/portal-ng-common';
128
+ import { ${this.toClassName(this.name)}Component } from './${this.name}.component';
129
+
130
+ @NgModule({
131
+ declarations: [
132
+ ${this.toClassName(this.name)}Component
133
+ ],
134
+ imports: [
135
+ BrowserModule,
136
+ CommonModule,
137
+ RedboxPortalCoreModule
138
+ ],
139
+ providers: [
140
+ {
141
+ provide: APP_BASE_HREF,
142
+ useFactory: (s: PlatformLocation) => trimLastSlashFromUrl(s.getBaseHrefFromDOM()),
143
+ deps: [PlatformLocation]
144
+ }
145
+ ],
146
+ bootstrap: [${this.toClassName(this.name)}Component]
147
+ })
148
+ export class ${this.toClassName(this.name)}Module { }
149
+ `,
150
+ [`src/app/${this.name}.component.ts`]: `import { Component } from '@angular/core';
151
+
152
+ @Component({
153
+ selector: '${this.name}',
154
+ templateUrl: './${this.name}.component.html',
155
+ styleUrls: ['./${this.name}.component.scss'],
156
+ standalone: false
157
+ })
158
+ export class ${this.toClassName(this.name)}Component {
159
+ title = '${this.name}';
160
+ }
161
+ `,
162
+ [`src/app/${this.name}.component.html`]: `<div class="container-fluid">
163
+ <h1>${this.toClassName(this.name)} App</h1>
164
+ <p>${this.name} works!</p>
165
+ </div>
166
+ `,
167
+ [`src/app/${this.name}.component.scss`]: ``,
168
+ };
169
+ for (const [relPath, content] of Object.entries(files)) {
170
+ this.writeFile(path.join(projectRoot, relPath), content);
171
+ }
172
+ }
173
+ updateAngularJson() {
174
+ const angularJsonPath = path.join(this.paths.angular, 'angular.json');
175
+ if (!fs.existsSync(angularJsonPath)) {
176
+ console.warn(`Warning: Could not find angular.json at ${angularJsonPath}`);
177
+ return;
178
+ }
179
+ let angularJson;
180
+ try {
181
+ angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf-8'));
182
+ }
183
+ catch (error) {
184
+ const message = error instanceof Error ? error.message : String(error);
185
+ console.error(`Error parsing angular.json at ${angularJsonPath}: ${message}`);
186
+ return;
187
+ }
188
+ const projectName = `@researchdatabox/${this.name}`;
189
+ if (typeof angularJson.projects !== 'object' || angularJson.projects === null) {
190
+ angularJson.projects = {};
191
+ }
192
+ if (angularJson.projects[projectName]) {
193
+ console.log(` [SKIP] angular.json project ${projectName} already exists`);
194
+ return;
195
+ }
196
+ angularJson.projects[projectName] = {
197
+ "projectType": "application",
198
+ "schematics": {
199
+ "@schematics/angular:component": {
200
+ "style": "scss"
201
+ }
202
+ },
203
+ "root": `projects/researchdatabox/${this.name}`,
204
+ "sourceRoot": `projects/researchdatabox/${this.name}/src`,
205
+ "prefix": "app",
206
+ "architect": {
207
+ "build": {
208
+ "builder": "@angular-devkit/build-angular:application",
209
+ "options": {
210
+ "outputPath": {
211
+ "base": `../assets/angular/${this.name}`
212
+ },
213
+ "index": `projects/researchdatabox/${this.name}/src/index.html`,
214
+ "polyfills": [
215
+ "zone.js"
216
+ ],
217
+ "tsConfig": `projects/researchdatabox/${this.name}/tsconfig.app.json`,
218
+ "inlineStyleLanguage": "scss",
219
+ "assets": [
220
+ `projects/researchdatabox/${this.name}/src/favicon.ico`,
221
+ `projects/researchdatabox/${this.name}/src/assets`
222
+ ],
223
+ "styles": [
224
+ `projects/researchdatabox/${this.name}/src/styles.scss`
225
+ ],
226
+ "scripts": [],
227
+ "browser": `projects/researchdatabox/${this.name}/src/main.ts`
228
+ },
229
+ "configurations": {
230
+ "production": {
231
+ "budgets": [
232
+ {
233
+ "type": "initial",
234
+ "maximumWarning": "500kb",
235
+ "maximumError": "1mb"
236
+ },
237
+ {
238
+ "type": "anyComponentStyle",
239
+ "maximumWarning": "2kb",
240
+ "maximumError": "4kb"
241
+ }
242
+ ],
243
+ "outputHashing": "all"
244
+ },
245
+ "development": {
246
+ "optimization": false,
247
+ "extractLicenses": false,
248
+ "sourceMap": true
249
+ }
250
+ },
251
+ "defaultConfiguration": "production"
252
+ }
253
+ }
254
+ };
255
+ this.writeFile(angularJsonPath, JSON.stringify(angularJson, null, 2));
256
+ }
257
+ generateEjsView() {
258
+ const safeViewName = this.getSafeEjsViewName();
259
+ const baseDir = path.join(this.root, 'views', 'default', 'default', 'admin');
260
+ const viewPath = path.resolve(baseDir, `${safeViewName}.ejs`);
261
+ if (!viewPath.startsWith(path.resolve(baseDir) + path.sep)) {
262
+ throw new Error(`EJS view name resolves outside the admin views directory: ${safeViewName}`);
263
+ }
264
+ const content = `<% let appName = '${this.name}'; %>
265
+ <div class="row admin-layout">
266
+ <div class="col-md-3 col-lg-2">
267
+ <%- include('layout/sidebar.ejs') %>
268
+ </div>
269
+ <div class="col-md-9 col-lg-10 admin-main-content">
270
+ <${this.name} <% if (typeof contentSecurityPolicyNonce !== "undefined" && contentSecurityPolicyNonce) {%>ngCspNonce="<%= contentSecurityPolicyNonce %>"<% } %>>
271
+ <div class="col-xs-12">
272
+ <img class="center-block" src="<%= BrandingService.getRootContext() %>/images/loading.svg" alt="<%= TranslationService.t('loading-alt-text') %>">
273
+ </div>
274
+ </${this.name}>
275
+ </div>
276
+ </div>
277
+ <script src="<%= BrandingService.getRootContext() %>/angular/<%=appName%>/browser/polyfills<%=CacheService.getNgAppFileHash(appName, 'polyfills', '-') %>.js"
278
+ type="module"></script>
279
+
280
+ <script src="<%= BrandingService.getRootContext() %>/angular/<%=appName%>/browser/main<%=CacheService.getNgAppFileHash(appName, 'main', '-') %>.js"
281
+ type="module"></script>
282
+ <link rel="stylesheet" href="<%= BrandingService.getRootContext() %>/angular/<%= appName %>/browser/styles<%=CacheService.getNgAppFileHash(appName, 'styles', '-')%>.css">
283
+ `;
284
+ this.writeFile(viewPath, content);
285
+ }
286
+ async updateRouteConfig() {
287
+ const route = `/:branding/:portal/admin/${this.name}`; // Defaulting to admin path
288
+ const safeViewName = this.getSafeEjsViewName();
289
+ // We can't use updateRoutes directly because it assumes a controller.action string
290
+ // But here we want a RouteTargetObject with locals.view
291
+ const routesPath = path.join(this.paths.coreTypes, 'src', 'config', 'routes.config.ts');
292
+ if (!fs.existsSync(routesPath))
293
+ return;
294
+ const sourceFile = this.project.addSourceFileAtPath(routesPath);
295
+ const routesVar = sourceFile.getVariableDeclaration('routes');
296
+ if (!routesVar)
297
+ return;
298
+ const initializer = routesVar.getInitializerIfKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
299
+ if (!initializer)
300
+ return;
301
+ if (!initializer.getProperty(`'${route}'`) && !initializer.getProperty(`"${route}"`)) {
302
+ initializer.addPropertyAssignment({
303
+ name: `'${route}'`,
304
+ initializer: `{
305
+ controller: 'RenderViewController',
306
+ action: 'render',
307
+ locals: { 'view': 'admin/${safeViewName}' }
308
+ }`
309
+ });
310
+ }
311
+ sourceFile.formatText();
312
+ if (!this.dryRun) {
313
+ await sourceFile.save();
314
+ console.log(` [UPDATE] ${path.relative(this.root, routesPath)}`);
315
+ }
316
+ }
317
+ async updateAuthConfig() {
318
+ const route = `/:branding/:portal/admin/${this.name}`;
319
+ await (0, config_helper_1.updateAuth)({
320
+ project: this.project,
321
+ paths: this.paths,
322
+ root: this.root,
323
+ route: route,
324
+ auth: this.auth,
325
+ dryRun: this.dryRun
326
+ });
327
+ }
328
+ toClassName(name) {
329
+ return name.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join('');
330
+ }
331
+ getSafeEjsViewName() {
332
+ const trimmed = this.ejsView.trim();
333
+ if (!trimmed) {
334
+ throw new Error('EJS view name cannot be empty.');
335
+ }
336
+ if (!/^[A-Za-z0-9_-]+$/.test(trimmed)) {
337
+ throw new Error(`EJS view name contains invalid characters: '${this.ejsView}'`);
338
+ }
339
+ return trimmed;
340
+ }
341
+ }
342
+ exports.AngularAppGenerator = AngularAppGenerator;
@@ -0,0 +1,21 @@
1
+ import { Generator, GeneratorOptions } from '../utils/generator';
2
+ import { RedboxPaths } from '../utils/paths';
3
+ export interface AngularServiceGeneratorOptions extends GeneratorOptions {
4
+ app: string;
5
+ name: string;
6
+ methods?: string[];
7
+ paths: RedboxPaths;
8
+ }
9
+ export declare class AngularServiceGenerator extends Generator {
10
+ private app;
11
+ private name;
12
+ private methods;
13
+ private paths;
14
+ constructor(options: AngularServiceGeneratorOptions);
15
+ generate(): Promise<void>;
16
+ private generateServiceContent;
17
+ private toClassName;
18
+ private capitalize;
19
+ private isValidTsIdentifier;
20
+ private toSafeMethodName;
21
+ }
@@ -0,0 +1,154 @@
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.AngularServiceGenerator = void 0;
37
+ const path = __importStar(require("path"));
38
+ const fs = __importStar(require("fs"));
39
+ const generator_1 = require("../utils/generator");
40
+ class AngularServiceGenerator extends generator_1.Generator {
41
+ app;
42
+ name;
43
+ methods;
44
+ paths;
45
+ constructor(options) {
46
+ super(options);
47
+ this.app = options.app.toLowerCase();
48
+ this.name = options.name.toLowerCase();
49
+ this.methods = options.methods || [];
50
+ this.paths = options.paths;
51
+ }
52
+ async generate() {
53
+ const projectRoot = path.join(this.paths.angular, 'projects', 'researchdatabox', this.app);
54
+ if (!fs.existsSync(projectRoot)) {
55
+ throw new Error(`Angular app project not found at: ${projectRoot}`);
56
+ }
57
+ const serviceClassName = this.toClassName(this.name);
58
+ const fileName = `${this.name}.service.ts`;
59
+ const servicePath = path.join(projectRoot, 'src', 'app', fileName);
60
+ const content = this.generateServiceContent(serviceClassName);
61
+ this.writeFile(servicePath, content);
62
+ }
63
+ generateServiceContent(serviceClassName) {
64
+ const className = `${serviceClassName}Service`;
65
+ let methodsContent = '';
66
+ if (this.methods.length > 0) {
67
+ const methodInfos = this.methods.map(method => ({
68
+ raw: method,
69
+ identifier: this.toSafeMethodName(method)
70
+ }));
71
+ methodsContent = methodInfos.map(method => `
72
+ /**
73
+ * ${this.capitalize(method.identifier)}
74
+ */
75
+ public async ${method.identifier}(): Promise<any> {
76
+ const url = \`\${this.brandingAndPortalUrl}/app/${this.app}/${method.raw}\`;
77
+ const result$ = this.http.get(url, { ...this.reqOptsJsonBodyOnly, context: this.httpContext });
78
+ return await firstValueFrom(result$);
79
+ }
80
+ `).join('\n');
81
+ }
82
+ return `import { Injectable, Inject } from '@angular/core';
83
+ import { HttpClient } from '@angular/common/http';
84
+ import { APP_BASE_HREF } from '@angular/common';
85
+ import { HttpClientService, ConfigService, UtilityService } from '@researchdatabox/portal-ng-common';
86
+ import { firstValueFrom } from 'rxjs';
87
+
88
+ /**
89
+ * ${serviceClassName} Service
90
+ *
91
+ * Generated by ReDBox Hook Kit
92
+ */
93
+ @Injectable({
94
+ providedIn: 'root'
95
+ })
96
+ export class ${className} extends HttpClientService {
97
+
98
+ constructor(
99
+ @Inject(HttpClient) http: HttpClient,
100
+ @Inject(APP_BASE_HREF) rootContext: string,
101
+ @Inject(UtilityService) utilService: UtilityService,
102
+ @Inject(ConfigService) configService: ConfigService
103
+ ) {
104
+ super(http, rootContext, utilService, configService);
105
+ }
106
+
107
+ public override async waitForInit(): Promise<any> {
108
+ await super.waitForInit();
109
+ this.enableCsrfHeader();
110
+ return this;
111
+ }
112
+ ${methodsContent}
113
+ }
114
+ `;
115
+ }
116
+ toClassName(name) {
117
+ return name.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join('');
118
+ }
119
+ capitalize(s) {
120
+ return s.charAt(0).toUpperCase() + s.slice(1);
121
+ }
122
+ isValidTsIdentifier(name) {
123
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
124
+ }
125
+ toSafeMethodName(method) {
126
+ const trimmed = method.trim();
127
+ if (!trimmed) {
128
+ throw new Error('Method name cannot be empty.');
129
+ }
130
+ if (this.isValidTsIdentifier(trimmed)) {
131
+ return trimmed;
132
+ }
133
+ const parts = trimmed.split(/[^A-Za-z0-9_$]+/).filter(Boolean);
134
+ const camel = parts.map((part, index) => {
135
+ const lower = part.toLowerCase();
136
+ if (index === 0)
137
+ return lower;
138
+ return lower.charAt(0).toUpperCase() + lower.slice(1);
139
+ }).join('');
140
+ let candidate = camel || trimmed.replace(/[^A-Za-z0-9_$]/g, '');
141
+ if (!candidate) {
142
+ throw new Error(`Method name '${method}' cannot be converted to a valid identifier.`);
143
+ }
144
+ if (!/^[A-Za-z_$]/.test(candidate)) {
145
+ candidate = `_${candidate}`;
146
+ }
147
+ if (!this.isValidTsIdentifier(candidate)) {
148
+ throw new Error(`Method name '${method}' cannot be converted to a valid identifier.`);
149
+ }
150
+ console.warn(` [WARN] Sanitized method name '${method}' to '${candidate}'.`);
151
+ return candidate;
152
+ }
153
+ }
154
+ exports.AngularServiceGenerator = AngularServiceGenerator;
@@ -0,0 +1,40 @@
1
+ import { Generator, GeneratorOptions } from '../utils/generator';
2
+ import { RedboxPaths } from '../utils/paths';
3
+ import { NavigationMapping, LanguageDefaultEntry } from '../utils/config-helper';
4
+ export interface RouteMapping {
5
+ action: string;
6
+ verb?: string;
7
+ path: string;
8
+ auth?: string[];
9
+ csrf?: boolean;
10
+ }
11
+ export interface ControllerGeneratorOptions extends GeneratorOptions {
12
+ name: string;
13
+ actions?: string[];
14
+ webservice?: boolean;
15
+ /** @deprecated Use routes instead */
16
+ route?: string;
17
+ routes?: RouteMapping[];
18
+ auth?: string[];
19
+ navigation?: NavigationMapping[];
20
+ languageDefaults?: LanguageDefaultEntry[];
21
+ className?: string;
22
+ paths: RedboxPaths;
23
+ }
24
+ export declare class ControllerGenerator extends Generator {
25
+ private name;
26
+ private className;
27
+ private actions;
28
+ private webservice;
29
+ private routes;
30
+ private auth?;
31
+ private navigation;
32
+ private languageDefaults;
33
+ private paths;
34
+ private project;
35
+ constructor(options: ControllerGeneratorOptions);
36
+ generate(): Promise<void>;
37
+ private generateControllerContent;
38
+ private validateActions;
39
+ private isValidIdentifier;
40
+ }