chore: generate portal-shell and portal-bff apps per ADR-0004 / ADR-0005
Add the @nx/angular, @nx/nest, @nx/vite, @nx/eslint plugins, then generate the two apps. Adjust the empty-template tsconfig.base.json to be Angular-compatible (drop project references and customConditions that the empty-template defaults to but Angular doesn't support; keep the strict-TS extensions from ADR-0004). apps/portal-shell (Angular 21): - standalone APIs, routing, SCSS, esbuild - vitest-angular as unitTestRunner, playwright for e2e - strict mode - tags scope:portal-shell, type:app - app.config.ts wired with provideZonelessChangeDetection() per ADR-0004 (Angular 21 + Nx 22 generates without zone.js by default) apps/portal-bff (NestJS 11): - Express adapter (default per ADR-0005) - Jest as unitTestRunner - tags scope:portal-bff, type:app - main.ts wired with a global ValidationPipe configured whitelist + forbidNonWhitelisted + transform per ADR-0005 - Phase-2 security additions (helmet, CORS, sessions, CSRF, rate limit, auth guards, error filter) deferred to their respective ADRs - placeholder comment in main.ts Workspace dependencies: class-validator + class-transformer added (required by NestJS ValidationPipe at runtime). Nx-generated .gitignore additions (.angular, __screenshots__) merged into ours. .vscode/extensions.json and launch.json added by Nx are kept (do not override our existing settings.json).
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
displayName: 'portal-bff',
|
||||
preset: '../../jest.preset.js',
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||
coverageDirectory: '../../coverage/apps/portal-bff',
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "portal-bff",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "apps/portal-bff/src",
|
||||
"projectType": "application",
|
||||
"tags": ["scope:portal-bff", "type:app"],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "webpack-cli build",
|
||||
"args": ["--node-env=production"],
|
||||
"cwd": "apps/portal-bff"
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"args": ["--node-env=development"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"prune-lockfile": {
|
||||
"dependsOn": ["build"],
|
||||
"cache": true,
|
||||
"executor": "@nx/js:prune-lockfile",
|
||||
"outputs": [
|
||||
"{workspaceRoot}/dist/apps/portal-bff/package.json",
|
||||
"{workspaceRoot}/dist/apps/portal-bff/pnpm-lock.yaml"
|
||||
],
|
||||
"options": {
|
||||
"buildTarget": "build"
|
||||
}
|
||||
},
|
||||
"copy-workspace-modules": {
|
||||
"dependsOn": ["build"],
|
||||
"cache": true,
|
||||
"outputs": ["{workspaceRoot}/dist/apps/portal-bff/workspace_modules"],
|
||||
"executor": "@nx/js:copy-workspace-modules",
|
||||
"options": {
|
||||
"buildTarget": "build"
|
||||
}
|
||||
},
|
||||
"prune": {
|
||||
"dependsOn": ["prune-lockfile", "copy-workspace-modules"],
|
||||
"executor": "nx:noop"
|
||||
},
|
||||
"serve": {
|
||||
"continuous": true,
|
||||
"executor": "@nx/js:node",
|
||||
"defaultConfiguration": "development",
|
||||
"dependsOn": ["build"],
|
||||
"options": {
|
||||
"buildTarget": "portal-bff:build",
|
||||
"runBuildTargetDependencies": false
|
||||
},
|
||||
"configurations": {
|
||||
"development": {
|
||||
"buildTarget": "portal-bff:build:development"
|
||||
},
|
||||
"production": {
|
||||
"buildTarget": "portal-bff:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"options": {
|
||||
"passWithNoTests": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
describe('AppController', () => {
|
||||
let app: TestingModule;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await Test.createTestingModule({
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
});
|
||||
|
||||
describe('getData', () => {
|
||||
it('should return "Hello API"', () => {
|
||||
const appController = app.get<AppController>(AppController);
|
||||
expect(appController.getData()).toEqual({ message: 'Hello API' });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getData() {
|
||||
return this.appService.getData();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
})
|
||||
export class AppModule {}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
describe('AppService', () => {
|
||||
let service: AppService;
|
||||
|
||||
beforeAll(async () => {
|
||||
const app = await Test.createTestingModule({
|
||||
providers: [AppService],
|
||||
}).compile();
|
||||
|
||||
service = app.get<AppService>(AppService);
|
||||
});
|
||||
|
||||
describe('getData', () => {
|
||||
it('should return "Hello API"', () => {
|
||||
expect(service.getData()).toEqual({ message: 'Hello API' });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getData(): { message: string } {
|
||||
return { message: 'Hello API' };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Logger, ValidationPipe } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
transform: true,
|
||||
}),
|
||||
);
|
||||
|
||||
// Phase-2 security ADRs will add: helmet, CORS allowlist, cookie-session,
|
||||
// CSRF protection, rate limiting, auth guards, structured error filter.
|
||||
|
||||
const globalPrefix = 'api';
|
||||
app.setGlobalPrefix(globalPrefix);
|
||||
const port = process.env.PORT || 3000;
|
||||
await app.listen(port);
|
||||
Logger.log(`Application is running on: http://localhost:${port}/${globalPrefix}`);
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["node"],
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"target": "es2021",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["jest.config.ts", "jest.config.cts", "src/**/*.spec.ts", "src/**/*.test.ts"]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node10",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"jest.config.cts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin');
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = {
|
||||
output: {
|
||||
path: join(__dirname, '../../dist/apps/portal-bff'),
|
||||
clean: true,
|
||||
...(process.env.NODE_ENV !== 'production' && {
|
||||
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
|
||||
}),
|
||||
},
|
||||
plugins: [
|
||||
new NxAppWebpackPlugin({
|
||||
target: 'node',
|
||||
compiler: 'tsc',
|
||||
main: './src/main.ts',
|
||||
tsConfig: './tsconfig.app.json',
|
||||
assets: ['./src/assets'],
|
||||
optimization: false,
|
||||
outputHashing: 'none',
|
||||
generatePackageJson: true,
|
||||
sourceMap: true,
|
||||
}),
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user