@researchdatabox/redbox-dev-tools 0.0.1 → 5.0.0-RC2
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 +259 -14
- 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 +168 -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 +495 -0
- package/dist/commands/form-config.d.ts +6 -0
- package/dist/commands/form-config.js +401 -0
- package/dist/commands/sync-translation.d.ts +23 -0
- package/dist/commands/sync-translation.js +348 -0
- package/dist/completion/index.d.ts +1 -0
- package/dist/completion/index.js +218 -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/migration.d.ts +15 -0
- package/dist/generators/migration.js +100 -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 +187 -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 +76 -19
- 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 +5 -0
- package/templates/hook-archetype/standard/.npmignore.hbs +4 -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 +51 -0
- package/templates/hook-archetype/standard/Dockerfile.hbs +29 -0
- package/templates/hook-archetype/standard/README.md.hbs +57 -0
- package/templates/hook-archetype/standard/assets/default/.gitkeep +1 -0
- package/templates/hook-archetype/standard/assets/fonts/.gitkeep +1 -0
- package/templates/hook-archetype/standard/assets/images/.gitkeep +1 -0
- package/templates/hook-archetype/standard/assets/styles/.gitkeep +1 -0
- package/templates/hook-archetype/standard/language-defaults/.gitkeep +1 -0
- package/templates/hook-archetype/standard/language-defaults/README.md.hbs +10 -0
- package/templates/hook-archetype/standard/language-defaults/en/translation.json.hbs +3 -0
- package/templates/hook-archetype/standard/language-defaults/meta.json.hbs +6 -0
- package/templates/hook-archetype/standard/package.json.hbs +44 -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/src/migrations/index.ts.hbs +19 -0
- package/templates/hook-archetype/standard/support/bootstrap-data/.gitkeep +1 -0
- package/templates/hook-archetype/standard/support/bootstrap-data/namedqueries/.gitkeep +1 -0
- package/templates/hook-archetype/standard/support/bootstrap-data/records/.gitkeep +1 -0
- package/templates/hook-archetype/standard/support/bootstrap-data/reports/.gitkeep +1 -0
- package/templates/hook-archetype/standard/support/bootstrap-data/vocabularies/.gitkeep +1 -0
- package/templates/hook-archetype/standard/support/development/core-mount.env.hbs +3 -0
- package/templates/hook-archetype/standard/support/development/docker-compose.core-mount.yml.hbs +37 -0
- package/templates/hook-archetype/standard/support/development/docker-compose.yml.hbs +108 -0
- package/templates/hook-archetype/standard/support/development/prepare-core-mount.sh.hbs +61 -0
- package/templates/hook-archetype/standard/support/integration-testing/docker-compose.mocha.yml.hbs +118 -0
- package/templates/hook-archetype/standard/support/integration-testing/run-mocha-hook.sh.hbs +14 -0
- package/templates/hook-archetype/standard/support/integration-testing/run-mocha-redbox.sh.hbs +100 -0
- package/templates/hook-archetype/standard/support/prepare-minio.sh.hbs +74 -0
- package/templates/hook-archetype/standard/test/integration/hook-registration.test.ts.hbs +15 -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 +46 -0
- package/templates/hook-archetype/standard/tsconfig.json.hbs +15 -0
- package/templates/hook-archetype/standard/views/.gitkeep +1 -0
- package/templates/hook-archetype/standard/views/default/.gitkeep +1 -0
- package/testing.d.ts +1 -0
- package/testing.js +3 -0
- package/.travis.yml +0 -23
- package/commands/initProject.js +0 -129
- package/redbox-dev-tools +0 -70
- package/yarn.lock +0 -1061
|
@@ -0,0 +1,235 @@
|
|
|
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.AddMethodGenerator = 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 AddMethodGenerator extends generator_1.Generator {
|
|
43
|
+
filePath;
|
|
44
|
+
method;
|
|
45
|
+
route;
|
|
46
|
+
http;
|
|
47
|
+
auth;
|
|
48
|
+
navigation;
|
|
49
|
+
languageDefaults;
|
|
50
|
+
paths;
|
|
51
|
+
project;
|
|
52
|
+
constructor(options) {
|
|
53
|
+
super(options);
|
|
54
|
+
this.filePath = path.isAbsolute(options.file) ? options.file : path.resolve(this.root, options.file);
|
|
55
|
+
this.method = options.method;
|
|
56
|
+
this.route = options.route;
|
|
57
|
+
this.http = options.http || 'GET';
|
|
58
|
+
this.auth = options.auth;
|
|
59
|
+
this.navigation = options.navigation || [];
|
|
60
|
+
this.languageDefaults = options.languageDefaults || [];
|
|
61
|
+
this.paths = options.paths;
|
|
62
|
+
this.project = new ts_morph_1.Project({
|
|
63
|
+
manipulationSettings: {
|
|
64
|
+
quoteKind: ts_morph_1.QuoteKind.Single,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async generate() {
|
|
69
|
+
if (!fs.existsSync(this.filePath)) {
|
|
70
|
+
throw new Error(`File not found: ${this.filePath}`);
|
|
71
|
+
}
|
|
72
|
+
const sourceFile = this.project.addSourceFileAtPath(this.filePath);
|
|
73
|
+
const relativePath = path.relative(this.root, this.filePath);
|
|
74
|
+
// 1. Identify if it's a Controller or Service
|
|
75
|
+
const isController = this.filePath.endsWith('Controller.ts');
|
|
76
|
+
const isService = this.filePath.endsWith('Service.ts');
|
|
77
|
+
if (!isController && !isService) {
|
|
78
|
+
throw new Error('Target file must be a Controller or Service (.ts)');
|
|
79
|
+
}
|
|
80
|
+
// 2. Find the class
|
|
81
|
+
const targetClass = sourceFile.getFirstDescendantByKind(ts_morph_1.SyntaxKind.ClassDeclaration);
|
|
82
|
+
if (!targetClass) {
|
|
83
|
+
throw new Error('Could not find a class in the target file');
|
|
84
|
+
}
|
|
85
|
+
this.updateClass(targetClass, isController);
|
|
86
|
+
sourceFile.formatText();
|
|
87
|
+
if (this.dryRun) {
|
|
88
|
+
console.log(` [UPDATE] ${relativePath} (dry run)`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
await sourceFile.save();
|
|
92
|
+
console.log(` [UPDATE] ${relativePath}`);
|
|
93
|
+
}
|
|
94
|
+
const fileName = path.basename(this.filePath, '.ts');
|
|
95
|
+
const className = isController ? fileName.replace('Controller', '') : fileName.replace('Service', '');
|
|
96
|
+
if (!this.dryRun) {
|
|
97
|
+
if (isController) {
|
|
98
|
+
await (0, config_helper_1.updateControllerIndex)({
|
|
99
|
+
project: this.project,
|
|
100
|
+
paths: this.paths,
|
|
101
|
+
root: this.root,
|
|
102
|
+
controllerName: fileName,
|
|
103
|
+
className: className,
|
|
104
|
+
webservice: this.filePath.includes(path.join('controllers', 'webservice')),
|
|
105
|
+
dryRun: this.dryRun
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
await (0, config_helper_1.updateServiceIndex)({
|
|
110
|
+
project: this.project,
|
|
111
|
+
paths: this.paths,
|
|
112
|
+
root: this.root,
|
|
113
|
+
serviceName: fileName,
|
|
114
|
+
className: className,
|
|
115
|
+
dryRun: this.dryRun
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// 3. Update routes and auth if it's a controller and route is provided
|
|
120
|
+
if (isController && this.route) {
|
|
121
|
+
const trimmedRoute = this.route.trim();
|
|
122
|
+
if (!trimmedRoute) {
|
|
123
|
+
throw new Error('Route cannot be empty or whitespace.');
|
|
124
|
+
}
|
|
125
|
+
const controllerName = fileName;
|
|
126
|
+
const webservice = this.filePath.includes(path.join('controllers', 'webservice'));
|
|
127
|
+
await (0, config_helper_1.updateRoutes)({
|
|
128
|
+
project: this.project,
|
|
129
|
+
paths: this.paths,
|
|
130
|
+
root: this.root,
|
|
131
|
+
route: trimmedRoute,
|
|
132
|
+
controllerName: controllerName,
|
|
133
|
+
action: this.method,
|
|
134
|
+
webservice: webservice,
|
|
135
|
+
dryRun: this.dryRun
|
|
136
|
+
});
|
|
137
|
+
if (this.auth && this.auth.length > 0) {
|
|
138
|
+
await (0, config_helper_1.updateAuth)({
|
|
139
|
+
project: this.project,
|
|
140
|
+
paths: this.paths,
|
|
141
|
+
root: this.root,
|
|
142
|
+
route: trimmedRoute,
|
|
143
|
+
auth: this.auth,
|
|
144
|
+
dryRun: this.dryRun
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (this.navigation.length > 0) {
|
|
148
|
+
const routePath = trimmedRoute.split(' ').pop();
|
|
149
|
+
if (!routePath) {
|
|
150
|
+
throw new Error(`Route is missing a path segment: '${trimmedRoute}'.`);
|
|
151
|
+
}
|
|
152
|
+
for (const nav of this.navigation) {
|
|
153
|
+
const actionName = nav.action || this.method;
|
|
154
|
+
if (actionName !== this.method) {
|
|
155
|
+
console.warn(` [NAV] Navigation mapping action '${actionName}' does not match method '${this.method}'. Skipping.`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
await (0, config_helper_1.updateNavigationConfig)({
|
|
159
|
+
project: this.project,
|
|
160
|
+
paths: this.paths,
|
|
161
|
+
root: this.root,
|
|
162
|
+
mapping: {
|
|
163
|
+
...nav,
|
|
164
|
+
href: routePath
|
|
165
|
+
},
|
|
166
|
+
dryRun: this.dryRun
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (this.languageDefaults.length > 0) {
|
|
172
|
+
await (0, config_helper_1.updateLanguageDefaults)({
|
|
173
|
+
root: this.root,
|
|
174
|
+
entries: this.languageDefaults,
|
|
175
|
+
dryRun: this.dryRun
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
updateClass(klass, isController) {
|
|
180
|
+
// 1. Add method stub if it doesn't exist
|
|
181
|
+
if (!klass.getMethod(this.method)) {
|
|
182
|
+
if (isController) {
|
|
183
|
+
klass.addMethod({
|
|
184
|
+
name: this.method,
|
|
185
|
+
scope: ts_morph_1.Scope.Public,
|
|
186
|
+
isAsync: true,
|
|
187
|
+
returnType: 'Promise<unknown>',
|
|
188
|
+
parameters: [
|
|
189
|
+
{ name: 'req', type: 'Sails.Req' },
|
|
190
|
+
{ name: 'res', type: 'Sails.Res' }
|
|
191
|
+
],
|
|
192
|
+
statements: `return this.sendResp(req, res, { data: { message: 'Action ${this.method} in ${klass.getName()}' } });`
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
klass.addMethod({
|
|
197
|
+
name: this.method,
|
|
198
|
+
scope: ts_morph_1.Scope.Public,
|
|
199
|
+
isAsync: true,
|
|
200
|
+
returnType: 'Promise<Record<string, unknown>>',
|
|
201
|
+
parameters: [
|
|
202
|
+
{ name: '..._args', type: 'unknown[]' }
|
|
203
|
+
],
|
|
204
|
+
statements: [
|
|
205
|
+
`sails.log.verbose(\`\${this.logHeader} ${this.method} called\`);`,
|
|
206
|
+
`return { message: 'Method ${this.method} in ${klass.getName()}' };`
|
|
207
|
+
]
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// 2. Update _exportedMethods
|
|
212
|
+
const exportedMethodsProp = klass.getProperty('_exportedMethods');
|
|
213
|
+
if (exportedMethodsProp) {
|
|
214
|
+
const initializer = exportedMethodsProp.getInitializerIfKind(ts_morph_1.SyntaxKind.ArrayLiteralExpression);
|
|
215
|
+
if (initializer) {
|
|
216
|
+
if (!initializer.getElements().some((e) => e.getText().replace(/['"]/g, '') === this.method)) {
|
|
217
|
+
initializer.addElement(`'${this.method}'`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
// Create _exportedMethods if missing
|
|
223
|
+
const hasInit = !!klass.getMethod('init');
|
|
224
|
+
const methods = hasInit ? ['init', this.method] : [this.method];
|
|
225
|
+
klass.addProperty({
|
|
226
|
+
name: '_exportedMethods',
|
|
227
|
+
scope: ts_morph_1.Scope.Protected,
|
|
228
|
+
hasOverrideKeyword: true,
|
|
229
|
+
type: 'string[]',
|
|
230
|
+
initializer: `[${methods.map(m => `'${m}'`).join(', ')}]`
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
exports.AddMethodGenerator = AddMethodGenerator;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Generator, GeneratorOptions } from '../utils/generator';
|
|
2
|
+
import { RedboxPaths } from '../utils/paths';
|
|
3
|
+
export interface AngularAppGeneratorOptions extends GeneratorOptions {
|
|
4
|
+
name: string;
|
|
5
|
+
ejsView: string;
|
|
6
|
+
auth?: string[];
|
|
7
|
+
paths: RedboxPaths;
|
|
8
|
+
}
|
|
9
|
+
export declare class AngularAppGenerator extends Generator {
|
|
10
|
+
private name;
|
|
11
|
+
private ejsView;
|
|
12
|
+
private auth?;
|
|
13
|
+
private paths;
|
|
14
|
+
private project;
|
|
15
|
+
constructor(options: AngularAppGeneratorOptions);
|
|
16
|
+
generate(): Promise<void>;
|
|
17
|
+
private scaffoldAngularApp;
|
|
18
|
+
private updateAngularJson;
|
|
19
|
+
private generateEjsView;
|
|
20
|
+
private updateRouteConfig;
|
|
21
|
+
private updateAuthConfig;
|
|
22
|
+
private toClassName;
|
|
23
|
+
private getSafeEjsViewName;
|
|
24
|
+
}
|
|
@@ -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
|
+
}
|