Upgrade NestJS from 10.x to 11.x (#15836)

## Overview
This PR upgrades all NestJS dependencies from version 10.x to 11.x,
following the [official migration
guide](https://docs.nestjs.com/migration-guide). This builds on top of
the v9 to v10 upgrade completed in PR #15835.

## Changes

### Dependencies Updated
**Core packages (10.x → 11.x):**
- `@nestjs/common`: 10.4.16 → 11.0.8
- `@nestjs/core`: 10.4.16 → 11.0.8
- `@nestjs/platform-express`: 10.4.16 → 11.0.8
- `@nestjs/config`: 3.2.3 → 3.3.0
- `@nestjs/passport`: 10.0.3 → 11.0.0
- `@nestjs/axios`: 3.0.2 → 3.1.2
- `@nestjs/schedule`: ^3.0.0 → ^4.1.1
- `@nestjs/serve-static`: 4.0.2 → 5.0.1
- `@nestjs/cache-manager`: ^2.2.1 → ^2.3.0
- `@nestjs/jwt`: 10.2.0 → 11.0.0
- `@nestjs/typeorm`: 10.0.2 → 11.0.0
- `@nestjs/terminus`: 11.0.0 (already on v11)
- `@nestjs/event-emitter`: 2.1.0 (compatible)

**DevDependencies:**
- `@nestjs/testing`: ^10.4.16 → ^11.0.8
- `@nestjs/schematics`: ^10.1.0 → ^11.0.2
- `@nestjs/cli`: 10.3.0 → 11.0.0

### Code Changes
**Fixed: TwentyConfigModule conditional imports**
- Updated `TwentyConfigModule.forRoot()` to use spread operator for
conditional imports
- Fixes TypeScript error with NestJS 11's stricter DynamicModule type
checking

**Cleanup: Removed unused package**
- Removed `@revertdotdev/revert-react` (not being used anywhere in the
codebase)

## Breaking Changes Addressed

### 1.  Reflector Type Inference
- **Impact**: None - codebase only uses `reflector.get()` method
- **Analysis**: Does not use `getAllAndMerge()` or `getAllAndOverride()`
(the methods with breaking changes)
- **Files reviewed**: feature-flag.guard.ts,
message-queue-metadata.accessor.ts,
workspace-query-hook-metadata.accessor.ts

### 2.  Lifecycle Hooks Execution Order
- **Change**: Termination hooks (`OnModuleDestroy`,
`BeforeApplicationShutdown`, `OnApplicationShutdown`) now execute in
REVERSE order
- **Analysis**: Reviewed all lifecycle hook implementations
  - Redis client cleanup
  - Database connection cleanup (GlobalWorkspaceDataSource)
  - BullMQ queue/worker cleanup
  - Cache storage cleanup
- **Result**: Dependency order is safe - services using connections
clean up before the connections themselves

### 3.  Middleware Registration Order
- **Change**: Global middleware now executes first regardless of import
order
- **Analysis**: Middleware is not registered as global, so execution
order remains consistent
- **Files reviewed**: app.module.ts, middleware.module.ts

## Testing

All tests passing and build successful:

**Unit Tests (283+ tests):**
-  Health module: 38 tests passed
-  Auth module: 115 tests passed (passport v11 integration)
-  REST API: 90 tests passed (middleware and express platform)
-  Feature flags: 17 tests passed (Reflector usage)
-  Workspace: 23 tests passed

**Build & Quality:**
-  Type checking: Passed
-  Linting: Passed
-  Build: 3,683 files compiled successfully

## Verification

Tested critical NestJS functionality:
-  Authentication & Security (JWT, OAuth, guards)
-  HTTP Platform (Express integration, REST endpoints)
-  Dependency Injection (Services, factories, providers)
-  Cache Management (Redis with @nestjs/cache-manager)
-  GraphQL (Query runners, resolvers)
-  Configuration (Environment config)
-  Scheduling (Cron jobs with @nestjs/schedule v4)
-  Lifecycle Hooks (Module initialization and cleanup)
-  Reflector (Metadata reflection in guards)

## Related PRs
- #15835 - Upgrade NestJS from 9.x to 10.x (completed)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Upgrades NestJS to v11 and updates routing patterns, auth strategies,
GraphQL schema options, and build/dist paths (scripts, Docker, Nx,
migrations, assets), plus enables Devtools in development.
> 
> - **Backend (NestJS 11 upgrade)**:
> - Bump `@nestjs/*` packages (core, platform-express, jwt, passport,
typeorm, serve-static, schedule, cli/testing/schematics) to v11.
> - Update REST/route-trigger/file controllers to new wildcard syntax
(`*path`).
> - Refactor OAuth (Google/Microsoft) and SAML strategies (abstract base
+ explicit `validate`); minor typings.
>   - Enable `DevtoolsModule` in development.
> - **GraphQL**:
> - Add `buildSchemaOptions.orphanedTypes` for client-config types; keep
Yoga/Sentry setup.
> - **Build/Runtime & Config**:
> - Standardize dist layout (remove `src` in paths): update scripts,
Docker `CMD`, Nx `project.json`, render scripts, TypeORM migration
paths, asset resolution.
> - Adjust `nest-cli.json` (watchOptions, asset globs, migrations
outDir, monorepo/root).
> - Improve config module imports (spread conditional); tsconfig
excludes `node_modules`.
>   - Minor Nx default: `start` target caching disabled.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1139fd85a97d0c72314d416d07464cc3c9942783. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
This commit is contained in:
Félix Malfait
2025-11-16 18:20:06 +01:00
committed by GitHub
parent a2a7ff8b4b
commit 5dfb66917c
27 changed files with 1102 additions and 655 deletions
@@ -8,6 +8,7 @@ import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity';
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
import { AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
import { AppTokenService } from 'src/engine/core-modules/app-token/services/app-token.service';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
import { GoogleAPIsAuthController } from 'src/engine/core-modules/auth/controllers/google-apis-auth.controller';
import { GoogleAuthController } from 'src/engine/core-modules/auth/controllers/google-auth.controller';
@@ -62,7 +63,6 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
import { MessagingFolderSyncManagerModule } from 'src/modules/messaging/message-folder-manager/messaging-folder-sync-manager.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { TwoFactorAuthenticationMethodEntity } from '../two-factor-authentication/entities/two-factor-authentication-method.entity';
import { TwoFactorAuthenticationModule } from '../two-factor-authentication/two-factor-authentication.module';
@@ -92,8 +92,8 @@ import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
KeyValuePairEntity,
UserWorkspaceEntity,
TwoFactorAuthenticationMethodEntity,
ObjectMetadataEntity,
]),
TypeOrmModule.forFeature([ObjectMetadataEntity]),
HttpModule,
UserWorkspaceModule,
WorkspaceModule,
@@ -1,18 +1,16 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-google-oauth20';
import { Strategy, type VerifyCallback } from 'passport-google-oauth20';
import { getGoogleApisOauthScopes } from 'src/engine/core-modules/auth/utils/get-google-apis-oauth-scopes';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { type TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
export type GoogleAPIScopeConfig = {
isCalendarEnabled?: boolean;
isMessagingAliasFetchingEnabled?: boolean;
};
@Injectable()
export class GoogleAPIsOauthCommonStrategy extends PassportStrategy(
export abstract class GoogleAPIsOauthCommonStrategy extends PassportStrategy(
Strategy,
'google-apis',
) {
@@ -27,4 +25,12 @@ export class GoogleAPIsOauthCommonStrategy extends PassportStrategy(
passReqToCallback: true,
});
}
abstract validate(
request: Express.Request,
accessToken: string,
refreshToken: string,
profile: unknown,
done: VerifyCallback,
): Promise<void>;
}
@@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common';
import { type VerifyCallback } from 'passport-google-oauth20';
import { GoogleAPIsOauthCommonStrategy } from 'src/engine/core-modules/auth/strategies/google-apis-oauth-common.auth.strategy';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@@ -31,4 +33,16 @@ export class GoogleAPIsOauthRequestCodeStrategy extends GoogleAPIsOauthCommonStr
return super.authenticate(req, options);
}
async validate(
_request: Express.Request,
_accessToken: string,
_refreshToken: string,
_profile: unknown,
done: VerifyCallback,
): Promise<void> {
// This strategy is only used for requesting authorization code
// No validation is performed here
done(null, {});
}
}
@@ -1,18 +1,17 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-microsoft';
import { type VerifyCallback } from 'passport-google-oauth20';
import { getMicrosoftApisOauthScopes } from 'src/engine/core-modules/auth/utils/get-microsoft-apis-oauth-scopes';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { type TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
export type MicrosoftAPIScopeConfig = {
isCalendarEnabled?: boolean;
isMessagingAliasFetchingEnabled?: boolean;
};
@Injectable()
export class MicrosoftAPIsOauthCommonStrategy extends PassportStrategy(
export abstract class MicrosoftAPIsOauthCommonStrategy extends PassportStrategy(
Strategy,
'microsoft-apis',
) {
@@ -28,4 +27,12 @@ export class MicrosoftAPIsOauthCommonStrategy extends PassportStrategy(
passReqToCallback: true,
});
}
abstract validate(
request: Express.Request,
accessToken: string,
refreshToken: string,
profile: unknown,
done: VerifyCallback,
): Promise<void>;
}
@@ -1,5 +1,7 @@
import { Injectable } from '@nestjs/common';
import { type VerifyCallback } from 'passport-google-oauth20';
import { MicrosoftAPIsOauthCommonStrategy } from 'src/engine/core-modules/auth/strategies/microsoft-apis-oauth-common.auth.strategy';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@@ -26,4 +28,16 @@ export class MicrosoftAPIsOauthRequestCodeStrategy extends MicrosoftAPIsOauthCom
return super.authenticate(req, options);
}
async validate(
_request: Express.Request,
_accessToken: string,
_refreshToken: string,
_profile: unknown,
done: VerifyCallback,
): Promise<void> {
// This strategy is only used for requesting authorization code
// No validation is performed here
done(null, {});
}
}
@@ -37,40 +37,46 @@ export class SamlAuthStrategy extends PassportStrategy(
'saml',
) {
constructor(private readonly sSOService: SSOService) {
super({
getSamlOptions: (req, callback) => {
this.sSOService
.findSSOIdentityProviderById(req.params.identityProviderId)
.then((identityProvider) => {
if (
identityProvider &&
this.sSOService.isSAMLIdentityProvider(identityProvider)
) {
const config: SamlConfig = {
entryPoint: identityProvider.ssoURL,
issuer: this.sSOService.buildIssuerURL(identityProvider),
callbackUrl: this.sSOService.buildCallbackUrl(identityProvider),
idpCert: identityProvider.certificate,
// TODO: Improve the feature by sign the response
wantAssertionsSigned: false,
wantAuthnResponseSigned: false,
disableRequestedAuthnContext: true,
signatureAlgorithm: 'sha256',
};
super(
{
getSamlOptions: (req, callback) => {
this.sSOService
.findSSOIdentityProviderById(req.params.identityProviderId)
.then((identityProvider) => {
if (
identityProvider &&
this.sSOService.isSAMLIdentityProvider(identityProvider)
) {
const config: SamlConfig = {
entryPoint: identityProvider.ssoURL,
issuer: this.sSOService.buildIssuerURL(identityProvider),
callbackUrl:
this.sSOService.buildCallbackUrl(identityProvider),
idpCert: identityProvider.certificate,
// TODO: Improve the feature by sign the response
wantAssertionsSigned: false,
wantAuthnResponseSigned: false,
disableRequestedAuthnContext: true,
signatureAlgorithm: 'sha256',
};
return callback(null, config);
}
return callback(null, config);
}
// TODO: improve error management
return callback(new Error('Invalid SAML identity provider'));
})
.catch((err) => {
// TODO: improve error management
return callback(err);
});
// TODO: improve error management
return callback(new Error('Invalid SAML identity provider'));
})
.catch((err) => {
// TODO: improve error management
return callback(err);
});
},
passReqToCallback: true,
} as PassportSamlConfig & MultiStrategyConfig,
async (request: Request, profile, done) => {
await this.validate(request, profile, done);
},
passReqToCallback: true,
} as PassportSamlConfig & MultiStrategyConfig);
);
}
authenticate(req: Request, options: AuthenticateOptions) {