@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.
- package/README.md +255 -0
- package/bin/cli.js +3 -0
- package/bin/run-hook-mocha.js +3 -0
- package/bin/run-hook-tsc.js +3 -0
- package/bin/wrapper-webpack.js +145 -0
- package/config/tsconfig.base.json +16 -0
- package/dist/bin/run-hook-mocha.d.ts +1 -0
- package/dist/bin/run-hook-mocha.js +49 -0
- package/dist/bin/run-hook-tsc.d.ts +1 -0
- package/dist/bin/run-hook-tsc.js +19 -0
- package/dist/cli-parsers.d.ts +9 -0
- package/dist/cli-parsers.js +131 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +464 -0
- package/dist/commands/form-config.d.ts +5 -0
- package/dist/commands/form-config.js +194 -0
- package/dist/completion/index.d.ts +1 -0
- package/dist/completion/index.js +217 -0
- package/dist/generators/add-method.d.ts +27 -0
- package/dist/generators/add-method.js +235 -0
- package/dist/generators/angular-app.d.ts +24 -0
- package/dist/generators/angular-app.js +342 -0
- package/dist/generators/angular-service.d.ts +21 -0
- package/dist/generators/angular-service.js +154 -0
- package/dist/generators/controller.d.ts +40 -0
- package/dist/generators/controller.js +195 -0
- package/dist/generators/form-component.d.ts +46 -0
- package/dist/generators/form-component.js +906 -0
- package/dist/generators/form-field.d.ts +15 -0
- package/dist/generators/form-field.js +160 -0
- package/dist/generators/model.d.ts +42 -0
- package/dist/generators/model.js +206 -0
- package/dist/generators/service.d.ts +16 -0
- package/dist/generators/service.js +111 -0
- package/dist/runtime-resolver.d.ts +3 -0
- package/dist/runtime-resolver.js +27 -0
- package/dist/templates/hook-archetype.d.ts +12 -0
- package/dist/templates/hook-archetype.js +143 -0
- package/dist/testing.d.ts +7 -0
- package/dist/testing.js +53 -0
- package/dist/utils/config-helper.d.ts +71 -0
- package/dist/utils/config-helper.js +574 -0
- package/dist/utils/generator.d.ts +10 -0
- package/dist/utils/generator.js +68 -0
- package/dist/utils/hook-tsconfig.d.ts +9 -0
- package/dist/utils/hook-tsconfig.js +104 -0
- package/dist/utils/paths.d.ts +10 -0
- package/dist/utils/paths.js +60 -0
- package/index.js +4 -0
- package/package.json +85 -0
- package/runtime-resolver.d.ts +1 -0
- package/runtime-resolver.js +3 -0
- package/skills/redbox-angular-apps/SKILL.md +42 -0
- package/skills/redbox-angular-services/SKILL.md +29 -0
- package/skills/redbox-controllers/SKILL.md +65 -0
- package/skills/redbox-feature-design-planner/SKILL.md +169 -0
- package/skills/redbox-feature-implementation-review/SKILL.md +117 -0
- package/skills/redbox-form-components/SKILL.md +114 -0
- package/skills/redbox-form-config/SKILL.md +34 -0
- package/skills/redbox-hook-development/SKILL.md +66 -0
- package/skills/redbox-services/SKILL.md +54 -0
- package/skills/redbox-test-verification/SKILL.md +38 -0
- package/skills/redbox-testing/SKILL.md +64 -0
- package/skills/redbox-web-verification/SKILL.md +71 -0
- package/skills/redbox-web-verification/redbox-angular-apps/SKILL.md +48 -0
- package/skills/redbox-web-verification/redbox-angular-services/SKILL.md +30 -0
- package/skills/redbox-web-verification/redbox-controllers/SKILL.md +56 -0
- package/skills/redbox-web-verification/redbox-form-config/SKILL.md +29 -0
- package/skills/redbox-web-verification/redbox-hook-development/SKILL.md +61 -0
- package/skills/redbox-web-verification/redbox-services/SKILL.md +45 -0
- package/skills/redbox-web-verification/redbox-testing/SKILL.md +53 -0
- package/skills/redbox-web-verification/redbox-web-verification/SKILL.md +47 -0
- package/templates/hook-archetype/standard/.gitignore.hbs +4 -0
- package/templates/hook-archetype/standard/.npmignore.hbs +3 -0
- package/templates/hook-archetype/standard/.npmrc.hbs +6 -0
- package/templates/hook-archetype/standard/.nvmrc.hbs +1 -0
- package/templates/hook-archetype/standard/AGENTS.md.hbs +34 -0
- package/templates/hook-archetype/standard/Dockerfile.hbs +28 -0
- package/templates/hook-archetype/standard/README.md.hbs +39 -0
- package/templates/hook-archetype/standard/assets/fonts/.gitkeep +1 -0
- package/templates/hook-archetype/standard/language-defaults/.gitkeep +1 -0
- package/templates/hook-archetype/standard/package.json.hbs +40 -0
- package/templates/hook-archetype/standard/src/api/controllers/ExampleController.ts.hbs +21 -0
- package/templates/hook-archetype/standard/src/api/controllers/index.ts.hbs +23 -0
- package/templates/hook-archetype/standard/src/api/services/ExampleService.ts.hbs +22 -0
- package/templates/hook-archetype/standard/src/api/services/index.ts.hbs +23 -0
- package/templates/hook-archetype/standard/src/config/brandingConfigurationDefaults.ts.hbs +9 -0
- package/templates/hook-archetype/standard/src/config/reusableFormDefinitions.ts.hbs +6 -0
- package/templates/hook-archetype/standard/src/form-config/default-1.0-draft.ts.hbs +20 -0
- package/templates/hook-archetype/standard/src/form-config/index.ts.hbs +8 -0
- package/templates/hook-archetype/standard/src/index.ts.hbs +55 -0
- package/templates/hook-archetype/standard/support/bootstrap-data/.gitkeep +1 -0
- package/templates/hook-archetype/standard/support/development/docker-compose.yml.hbs +75 -0
- package/templates/hook-archetype/standard/support/integration-testing/docker-compose.mocha.yml.hbs +84 -0
- package/templates/hook-archetype/standard/support/integration-testing/run-mocha-hook.sh.hbs +9 -0
- package/templates/hook-archetype/standard/test/integration/hook-registration.test.ts.hbs +13 -0
- package/templates/hook-archetype/standard/test/support/globals.ts.hbs +40 -0
- package/templates/hook-archetype/standard/test/tsconfig.json.hbs +8 -0
- package/templates/hook-archetype/standard/test/unit/.mocharc.cjs.hbs +9 -0
- package/templates/hook-archetype/standard/test/unit/index.test.ts.hbs +40 -0
- package/templates/hook-archetype/standard/tsconfig.json.hbs +15 -0
- package/templates/hook-archetype/standard/views/.gitkeep +1 -0
- package/testing.d.ts +1 -0
- 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
|
+
}
|