From 27861a0063dbd49b4a5d6e18b168b32c412bf875 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 15 Sep 2026 19:01:05 -0700 Subject: [PATCH 1/2] feat(build): compile with the TypeScript 7 native compiler Install TypeScript side by side, as recommended for TypeScript 7: `typescript` is aliased to `@typescript/typescript6` so that tools consuming the JS compiler API keep working, and `@typescript/native` provides the native compiler that `lb-tsc` runs. A cold build of the monorepo drops from ~10.7s to ~2.1s. `lb-tsc` prefers the native compiler and falls back to `typescript/lib/tsc` when `@typescript/native` is not installed. TypeScript 6/7 changes that required source updates: - `moduleResolution: node` (node10) was removed. It is dropped from `tsconfig.common.json` and from the CLI project template. The default resolver for `module: commonjs` honors `exports` maps, so `@loopback/graphql` no longer imports `Middleware` from a deep `type-graphql` path and declares it instead. - `@types/*` packages are no longer included automatically, so `tsconfig.common.json` now lists `types: ["node", "mocha"]`. - Reading a parent class field through `super` is an error (TS2855), so `@loopback/cron` calls `fireOnTick` through `BaseCronJob.prototype`. - Declaration emit no longer adds `/// ` for ambient files, so `@loopback/testlab` exports the should.js types from a module rather than a global declaration file. Without it, `expect` resolves to `any` in every consuming package and api-extractor cannot follow the `Internal` symbol. `@typescript-eslint` is upgraded to v8, v7 does not support TypeScript 6. Rules that v8 renamed or split are disabled to keep the rule set this configuration already had, and the remaining new findings are fixed. Two defects surfaced by the stricter compiler are fixed as well: `belongs-to-repository-factory.unit.ts` never assigned `companyRepo` and clobbered `customerRepo` instead, and `rest.application.ts` declared the same `route()` overload twice. BREAKING CHANGE: `@loopback/build` compiles with TypeScript 7 when `@typescript/native` is installed and with TypeScript 6 otherwise. Projects extending `@loopback/build/config/tsconfig.common.json` have to remove `moduleResolution: node` and list ambient type packages in `types`. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Raymond Feng --- .../repository-cloudant/docker.setup.js | 2 +- acceptance/repository-cloudant/package.json | 2 +- .../src/__tests__/benchmark.integration.ts | 2 +- benchmark/src/rest-routing/routing-table.ts | 2 +- bin/sync-dev-deps.js | 20 +- bodyparsers/rest-msgpack/package.json | 2 +- .../access-control-migration/package.json | 2 +- examples/binding-resolution/package.json | 2 +- examples/context/package.json | 2 +- .../src/custom-configuration-resolver.ts | 2 +- examples/express-composition/package.json | 2 +- examples/file-transfer/package.json | 2 +- .../src/services/file-upload.service.ts | 6 +- examples/graphql/package.json | 2 +- examples/greeter-extension/package.json | 2 +- examples/greeting-app/package.json | 2 +- examples/hello-world/package.json | 2 +- examples/lb3-application/package.json | 2 +- examples/log-extension/package.json | 2 +- .../src/decorators/log.decorator.ts | 2 +- examples/metrics-prometheus/package.json | 2 +- examples/multi-tenancy/package.json | 2 +- examples/passport-login/package.json | 2 +- .../src/authentication-strategies/basic.ts | 2 +- .../src/authentication-strategies/local.ts | 2 +- examples/references-many/package.json | 2 +- examples/rest-crud/package.json | 2 +- examples/rpc-server/package.json | 2 +- examples/rpc-server/src/application.ts | 2 +- examples/rpc-server/src/rpc.router.ts | 4 +- examples/soap-calculator/package.json | 2 +- examples/socketio/package.json | 2 +- examples/todo-jwt/package.json | 2 +- examples/todo-list/package.json | 2 +- .../todo-list-image.repository.integration.ts | 1 + .../todo.repository.integration.ts | 4 + examples/todo/package.json | 2 +- examples/validation-app/package.json | 2 +- examples/webpack/package.json | 2 +- extensions/authentication-jwt/package.json | 2 +- .../src/services/refreshtoken.service.ts | 2 +- extensions/cron/src/types.ts | 7 +- extensions/graphql/src/graphql.container.ts | 4 +- extensions/graphql/src/graphql.server.ts | 15 +- extensions/graphql/src/keys.ts | 4 +- extensions/graphql/src/types.ts | 14 +- extensions/pooling/package.json | 2 +- extensions/sequelize/package.json | 2 +- .../sequelize/sequelize.repository.base.ts | 15 +- extensions/socketio/src/socketio.server.ts | 14 +- .../src/mock-oauth2-social-app.ts | 115 +-- package-lock.json | 756 ++++++++++++++---- package.json | 6 +- .../src/__tests__/fixtures/helper.ts | 2 +- .../authorization.options.acceptance.ts | 4 +- .../boot/src/booters/base-artifact.booter.ts | 6 +- packages/boot/src/bootstrapper.ts | 4 +- packages/booter-lb3app/src/lb3app.booter.ts | 2 +- packages/build/README.md | 29 + packages/build/bin/compile-package.js | 32 +- packages/build/bin/utils.js | 2 +- packages/build/config/tsconfig.common.json | 5 +- packages/build/package.json | 3 +- .../test/integration/scripts.integration.js | 3 +- packages/cli/.yo-rc.json | 126 +-- packages/cli/bin/cli-main.js | 30 +- .../generators/app/templates/src/index.ts.ejs | 2 +- .../project/templates/tsconfig.json.ejs | 1 - packages/cli/lib/utils.js | 2 +- packages/cli/package.json | 13 +- .../__tests__/unit/try-catch-finally.unit.ts | 6 +- packages/context/src/binding-inspector.ts | 10 +- packages/context/src/context-view.ts | 4 +- packages/context/src/inject-config.ts | 2 +- packages/context/src/resolution-session.ts | 4 +- packages/context/src/value-promise.ts | 4 +- packages/core/src/extension-point.ts | 2 +- packages/core/src/lifecycle-registry.ts | 8 +- packages/eslint-config/eslintrc.js | 6 + packages/eslint-config/package.json | 4 +- packages/express/package.json | 2 +- packages/filter/package.json | 2 +- packages/filter/src/query.ts | 10 +- .../src/http-caching-proxy.ts | 2 +- packages/metadata/src/decorator-factory.ts | 4 +- .../src/openapi-spec-builder.ts | 26 +- packages/openapi-v3/src/controller-spec.ts | 24 +- .../src/decorators/parameter.decorator.ts | 2 +- .../src/decorators/request-body.decorator.ts | 6 +- .../src/build-schema.ts | 4 +- .../src/crud/transactions.suite.ts | 2 +- .../belongs-to-repository-factory.unit.ts | 2 +- .../repository/src/mixins/repository.mixin.ts | 4 +- packages/repository/src/model.ts | 4 +- packages/repository/src/types/buffer.ts | 4 +- .../caching-interceptor.ts | 2 +- packages/rest/src/rest.application.ts | 18 - packages/rest/src/rest.server.ts | 32 +- packages/rest/src/router/controller-route.ts | 8 +- packages/rest/src/router/router-spec.ts | 6 +- packages/rest/src/router/routing-table.ts | 4 +- packages/testlab/package.json | 1 - packages/testlab/src/expect.ts | 3 +- packages/testlab/src/http-server-config.ts | 8 +- .../should-as-function.ts} | 37 +- 105 files changed, 990 insertions(+), 604 deletions(-) rename packages/testlab/{should-as-function.d.ts => src/should-as-function.ts} (90%) diff --git a/acceptance/repository-cloudant/docker.setup.js b/acceptance/repository-cloudant/docker.setup.js index 9fb1453a331a..c985c0149d2f 100644 --- a/acceptance/repository-cloudant/docker.setup.js +++ b/acceptance/repository-cloudant/docker.setup.js @@ -43,7 +43,7 @@ async function createDB() { // retry if socket hangs up too early try { dbRequest(opts); - } catch (error) { + } catch { dbRequest(opts); } return new Promise(() => { diff --git a/acceptance/repository-cloudant/package.json b/acceptance/repository-cloudant/package.json index 8a570c854573..b16b767d744b 100644 --- a/acceptance/repository-cloudant/package.json +++ b/acceptance/repository-cloudant/package.json @@ -45,6 +45,6 @@ "loopback-connector-cloudant": "2.5.0", "ms": "2.1.3", "tslib": "^2.8.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/benchmark/src/__tests__/benchmark.integration.ts b/benchmark/src/__tests__/benchmark.integration.ts index aa057bec4ec5..8915042b1c45 100644 --- a/benchmark/src/__tests__/benchmark.integration.ts +++ b/benchmark/src/__tests__/benchmark.integration.ts @@ -39,7 +39,7 @@ describe('Benchmark (SLOW)', function (this: Mocha.Suite) { urlPath: string, options?: Omit, ): Promise { - if (!options) options = {}; + options ??= {}; const requestOptions: AxiosRequestConfig = { url: this.buildUrl(urlPath), diff --git a/benchmark/src/rest-routing/routing-table.ts b/benchmark/src/rest-routing/routing-table.ts index d5f46b3edf41..631b5da0177a 100644 --- a/benchmark/src/rest-routing/routing-table.ts +++ b/benchmark/src/rest-routing/routing-table.ts @@ -99,7 +99,7 @@ function givenRouter(router: RestRouter, spec: OpenApiSpec, count: number) { try { table.find(request); found++; - } catch (e) { + } catch { missed++; } } diff --git a/bin/sync-dev-deps.js b/bin/sync-dev-deps.js index 7632ec95ce17..38821565cc8c 100755 --- a/bin/sync-dev-deps.js +++ b/bin/sync-dev-deps.js @@ -12,18 +12,20 @@ const path = require('node:path'); const fse = require('fs-extra'); -const { - loadLernaRepo, - writeJsonSync, - isDryRun, - printJson, - runMain, -} = require('./script-util'); +const pkgJson = require('@npmcli/package-json'); +const mapWorkspaces = require('@npmcli/map-workspaces'); +const {writeJsonSync, isDryRun, printJson, runMain} = require('./script-util'); async function syncDevDeps(options) { - const {project, packages} = await loadLernaRepo(); + const rootPath = process.cwd(); - const rootPath = project.rootPath; + const {content: rootPkg} = await pkgJson.load(rootPath); + const workspaces = await mapWorkspaces({cwd: rootPath, pkg: rootPkg}); + + const packages = Array.from(workspaces, ([name, location]) => ({ + name, + manifestLocation: path.join(location, 'package.json'), + })); // Load dependencies from `packages/eslint-config/package.json` const eslintDeps = require( diff --git a/bodyparsers/rest-msgpack/package.json b/bodyparsers/rest-msgpack/package.json index cf0403751e94..7de9d875e449 100644 --- a/bodyparsers/rest-msgpack/package.json +++ b/bodyparsers/rest-msgpack/package.json @@ -48,6 +48,6 @@ "@types/msgpack5": "^3.4.7", "@types/node": "^20.19.43", "@types/type-is": "^1.6.7", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/access-control-migration/package.json b/examples/access-control-migration/package.json index 5df8db41f5da..eefda068d159 100644 --- a/examples/access-control-migration/package.json +++ b/examples/access-control-migration/package.json @@ -75,6 +75,6 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/binding-resolution/package.json b/examples/binding-resolution/package.json index 04b5cc517f66..5be9d3b2b481 100644 --- a/examples/binding-resolution/package.json +++ b/examples/binding-resolution/package.json @@ -58,6 +58,6 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/context/package.json b/examples/context/package.json index 2897c225412b..1f447c95d429 100644 --- a/examples/context/package.json +++ b/examples/context/package.json @@ -55,6 +55,6 @@ "@loopback/testlab": "^8.0.15", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/context/src/custom-configuration-resolver.ts b/examples/context/src/custom-configuration-resolver.ts index 4d7d536f122c..19f15f124ccd 100644 --- a/examples/context/src/custom-configuration-resolver.ts +++ b/examples/context/src/custom-configuration-resolver.ts @@ -47,7 +47,7 @@ class EnvConfigResolver ); try { return JSON.parse(val); - } catch (err) { + } catch { return val; } } diff --git a/examples/express-composition/package.json b/examples/express-composition/package.json index 329851777c4a..ebd4ebe2fa00 100644 --- a/examples/express-composition/package.json +++ b/examples/express-composition/package.json @@ -63,6 +63,6 @@ "@types/express": "^4.17.25", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/file-transfer/package.json b/examples/file-transfer/package.json index 5167fd6e0007..697d335175f4 100644 --- a/examples/file-transfer/package.json +++ b/examples/file-transfer/package.json @@ -61,6 +61,6 @@ "@types/multer": "^2.2.0", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/file-transfer/src/services/file-upload.service.ts b/examples/file-transfer/src/services/file-upload.service.ts index 897a5c3d3586..facedb8171df 100644 --- a/examples/file-transfer/src/services/file-upload.service.ts +++ b/examples/file-transfer/src/services/file-upload.service.ts @@ -23,10 +23,8 @@ import {FileUploadHandler} from '../types'; }) export class FileUploadProvider implements Provider { constructor(@config() private options: multer.Options = {}) { - if (!this.options.storage) { - // Default to in-memory storage - this.options.storage = multer.memoryStorage(); - } + // Default to in-memory storage + this.options.storage ??= multer.memoryStorage(); } value(): FileUploadHandler { diff --git a/examples/graphql/package.json b/examples/graphql/package.json index 20fa2edb50f2..9cb1d63ce813 100644 --- a/examples/graphql/package.json +++ b/examples/graphql/package.json @@ -68,6 +68,6 @@ "graphql-ws": "^5.16.2", "rimraf": "^5.0.10", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/greeter-extension/package.json b/examples/greeter-extension/package.json index 9f97ca20362f..681708c0749e 100644 --- a/examples/greeter-extension/package.json +++ b/examples/greeter-extension/package.json @@ -59,6 +59,6 @@ "@types/debug": "4.1.13", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/greeting-app/package.json b/examples/greeting-app/package.json index 5de422b724cc..c3b1f71cc429 100644 --- a/examples/greeting-app/package.json +++ b/examples/greeting-app/package.json @@ -63,6 +63,6 @@ "@types/debug": "4.1.13", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/hello-world/package.json b/examples/hello-world/package.json index fd3c480e4b73..33072c0b350d 100644 --- a/examples/hello-world/package.json +++ b/examples/hello-world/package.json @@ -56,6 +56,6 @@ "@loopback/testlab": "^8.0.15", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/lb3-application/package.json b/examples/lb3-application/package.json index 1cc3e52cacc8..c2f7320fe80d 100644 --- a/examples/lb3-application/package.json +++ b/examples/lb3-application/package.json @@ -71,6 +71,6 @@ "eslint": "^8.57.1", "lodash": "^4.18.1", "should": "^13.2.3", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/log-extension/package.json b/examples/log-extension/package.json index 42d3495107e8..32c6841aab2f 100644 --- a/examples/log-extension/package.json +++ b/examples/log-extension/package.json @@ -58,6 +58,6 @@ "@types/debug": "^4.1.13", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/log-extension/src/decorators/log.decorator.ts b/examples/log-extension/src/decorators/log.decorator.ts index 615f4f92900e..aeb29f37bcc5 100644 --- a/examples/log-extension/src/decorators/log.decorator.ts +++ b/examples/log-extension/src/decorators/log.decorator.ts @@ -19,7 +19,7 @@ import {LevelMetadata} from '../types'; * @param level - The Log Level at or above it should log */ export function log(level?: number) { - if (level === undefined) level = LOG_LEVEL.WARN; + level ??= LOG_LEVEL.WARN; return MethodDecoratorFactory.createDecorator( EXAMPLE_LOG_METADATA_KEY, { diff --git a/examples/metrics-prometheus/package.json b/examples/metrics-prometheus/package.json index f1b7b17f8b36..513c600e7f81 100644 --- a/examples/metrics-prometheus/package.json +++ b/examples/metrics-prometheus/package.json @@ -59,6 +59,6 @@ "@loopback/testlab": "^8.0.15", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/multi-tenancy/package.json b/examples/multi-tenancy/package.json index c822b56bde0b..1007daa7af2a 100644 --- a/examples/multi-tenancy/package.json +++ b/examples/multi-tenancy/package.json @@ -73,6 +73,6 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/passport-login/package.json b/examples/passport-login/package.json index 87b4b26357d7..4f4f45da2cf8 100644 --- a/examples/passport-login/package.json +++ b/examples/passport-login/package.json @@ -97,6 +97,6 @@ "axios": "^1.16.0", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/passport-login/src/authentication-strategies/basic.ts b/examples/passport-login/src/authentication-strategies/basic.ts index bf4fd64efbae..377adcb74660 100644 --- a/examples/passport-login/src/authentication-strategies/basic.ts +++ b/examples/passport-login/src/authentication-strategies/basic.ts @@ -91,7 +91,7 @@ export class BasicStrategy implements AuthenticationStrategy { return done(null, false); } const user = users[0]; - if (!user.credentials || user.credentials.password !== password) { + if (user.credentials?.password !== password) { return done(null, false); } // Authentication passed, return user profile diff --git a/examples/passport-login/src/authentication-strategies/local.ts b/examples/passport-login/src/authentication-strategies/local.ts index 8b907e72ca7d..335a8c4cd009 100644 --- a/examples/passport-login/src/authentication-strategies/local.ts +++ b/examples/passport-login/src/authentication-strategies/local.ts @@ -87,7 +87,7 @@ export class LocalAuthStrategy implements AuthenticationStrategy { return done(null, null, {message: AUTH_FAILED_MESSAGE}); } const user = users[0]; - if (!user.credentials || user.credentials.password !== password) { + if (user.credentials?.password !== password) { return done(null, null, {message: AUTH_FAILED_MESSAGE}); } // Authentication passed, return user profile diff --git a/examples/references-many/package.json b/examples/references-many/package.json index 32b5829283f7..8f08fc32a048 100644 --- a/examples/references-many/package.json +++ b/examples/references-many/package.json @@ -71,6 +71,6 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/rest-crud/package.json b/examples/rest-crud/package.json index 126fd42c3a87..15076f7d2ca8 100644 --- a/examples/rest-crud/package.json +++ b/examples/rest-crud/package.json @@ -68,6 +68,6 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/rpc-server/package.json b/examples/rpc-server/package.json index 1adf521ce496..d4e7ebb1b07a 100644 --- a/examples/rpc-server/package.json +++ b/examples/rpc-server/package.json @@ -53,6 +53,6 @@ "@types/express": "^4.17.25", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/rpc-server/src/application.ts b/examples/rpc-server/src/application.ts index 538bf34bbfaf..70483a1ead81 100644 --- a/examples/rpc-server/src/application.ts +++ b/examples/rpc-server/src/application.ts @@ -16,7 +16,7 @@ export class MyApplication extends Application { super(options); this.controller(GreetController); this.server(RPCServer); - this.options.port = this.options.port || 3000; + this.options.port = this.options.port ?? 3000; this.bind('rpcServer.config').to(this.options); } } diff --git a/examples/rpc-server/src/rpc.router.ts b/examples/rpc-server/src/rpc.router.ts index 9a70970b32eb..62084595ff98 100644 --- a/examples/rpc-server/src/rpc.router.ts +++ b/examples/rpc-server/src/rpc.router.ts @@ -9,8 +9,8 @@ import {RPCServer} from './rpc.server'; export function rpcRouter(server: RPCServer) { const jsonParser = parser.json(); - server.expressServer.post('*', jsonParser, async (request, response) => { - await routeHandler(server, request, response); + server.expressServer.post('*', jsonParser, (request, response, next) => { + routeHandler(server, request, response).catch(next); }); } diff --git a/examples/soap-calculator/package.json b/examples/soap-calculator/package.json index 56923f3824a1..c25a1567e848 100644 --- a/examples/soap-calculator/package.json +++ b/examples/soap-calculator/package.json @@ -63,6 +63,6 @@ "eslint": "^8.57.1", "mocha": "^12.0.1", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/socketio/package.json b/examples/socketio/package.json index f2e084d1a59c..7f0e814cb478 100644 --- a/examples/socketio/package.json +++ b/examples/socketio/package.json @@ -69,6 +69,6 @@ "eslint": "^8.57.1", "socket.io-client": "^4.8.3", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/todo-jwt/package.json b/examples/todo-jwt/package.json index 3e83d63ed270..c603dab4b658 100644 --- a/examples/todo-jwt/package.json +++ b/examples/todo-jwt/package.json @@ -75,6 +75,6 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/todo-list/package.json b/examples/todo-list/package.json index 591f0bd97559..0b7eaf61e5a1 100644 --- a/examples/todo-list/package.json +++ b/examples/todo-list/package.json @@ -71,6 +71,6 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/todo-list/src/__tests__/integration/todo-list-image.repository.integration.ts b/examples/todo-list/src/__tests__/integration/todo-list-image.repository.integration.ts index 4d36bef46c0d..234a880b9847 100644 --- a/examples/todo-list/src/__tests__/integration/todo-list-image.repository.integration.ts +++ b/examples/todo-list/src/__tests__/integration/todo-list-image.repository.integration.ts @@ -31,6 +31,7 @@ describe('TodoListImageRepository', () => { testdb, async () => todoListRepo, ); + todoRepo = new TodoRepository(testdb, async () => todoListRepo); }); beforeEach(givenEmptyDatabase); diff --git a/examples/todo-list/src/__tests__/integration/todo.repository.integration.ts b/examples/todo-list/src/__tests__/integration/todo.repository.integration.ts index 716f5f78aba0..c9edcf6c356d 100644 --- a/examples/todo-list/src/__tests__/integration/todo.repository.integration.ts +++ b/examples/todo-list/src/__tests__/integration/todo.repository.integration.ts @@ -28,6 +28,10 @@ describe('TodoRepository', () => { async () => todoListImageRepo, ); todoRepo = new TodoRepository(testdb, async () => todoListRepo); + todoListImageRepo = new TodoListImageRepository( + testdb, + async () => todoListRepo, + ); }); beforeEach(givenEmptyDatabase); diff --git a/examples/todo/package.json b/examples/todo/package.json index b7382ec367bd..1f454e7165e8 100644 --- a/examples/todo/package.json +++ b/examples/todo/package.json @@ -71,6 +71,6 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/validation-app/package.json b/examples/validation-app/package.json index 7b3a2160f7de..d0e103a84d87 100644 --- a/examples/validation-app/package.json +++ b/examples/validation-app/package.json @@ -66,6 +66,6 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/examples/webpack/package.json b/examples/webpack/package.json index ad5163c7336e..aa9492a8c9e9 100644 --- a/examples/webpack/package.json +++ b/examples/webpack/package.json @@ -51,7 +51,7 @@ "events": "^3.3.0", "process": "^0.11.10", "puppeteer": "^25.10.0", - "typescript": "~5.2.2", + "typescript": "npm:@typescript/typescript6@~6.0.2", "util": "^0.12.5", "webpack": "^5.110.3", "webpack-cli": "^7.2.3" diff --git a/extensions/authentication-jwt/package.json b/extensions/authentication-jwt/package.json index fce2218d17f0..18333e910dac 100644 --- a/extensions/authentication-jwt/package.json +++ b/extensions/authentication-jwt/package.json @@ -62,6 +62,6 @@ "@types/lodash": "^4.17.25", "@types/node": "^20.19.43", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/extensions/authentication-jwt/src/services/refreshtoken.service.ts b/extensions/authentication-jwt/src/services/refreshtoken.service.ts index 9f65887db7ce..5ab5ef1643c0 100644 --- a/extensions/authentication-jwt/src/services/refreshtoken.service.ts +++ b/extensions/authentication-jwt/src/services/refreshtoken.service.ts @@ -95,7 +95,7 @@ export class RefreshtokenService { await this.refreshTokenRepository.delete( new RefreshToken({refreshToken: refreshToken}), ); - } catch (e) { + } catch { // ignore } } diff --git a/extensions/cron/src/types.ts b/extensions/cron/src/types.ts index 6af24423a758..a1720ab67002 100644 --- a/extensions/cron/src/types.ts +++ b/extensions/cron/src/types.ts @@ -52,10 +52,13 @@ export class CronJob extends BaseCronJob { } else { this.name = `job-${++CronJob.count}`; } - // Override `fireOnTick` to catch errors + // Override `fireOnTick` to catch errors. `cron` declares `fireOnTick` as a + // class field but installs it on the prototype, so it cannot be reached + // via `super`. + const fireOnTick = BaseCronJob.prototype.fireOnTick; this.fireOnTick = () => { try { - return super.fireOnTick(); + return fireOnTick.call(this); } catch (err) { this.emitter.emit('error', err); } diff --git a/extensions/graphql/src/graphql.container.ts b/extensions/graphql/src/graphql.container.ts index 3728aa21bf5b..206c3cb0c71d 100644 --- a/extensions/graphql/src/graphql.container.ts +++ b/extensions/graphql/src/graphql.container.ts @@ -84,9 +84,7 @@ export class LoopBackContainer implements ContainerType { } else { // Narrow down by key found = bindings.find(filterByKey(resolverBinding.key)); - if (!found) { - found = bindings[0]; - } + found ??= bindings[0]; } debug( diff --git a/extensions/graphql/src/graphql.server.ts b/extensions/graphql/src/graphql.server.ts index 84075b51fe08..7b402f1c005a 100644 --- a/extensions/graphql/src/graphql.server.ts +++ b/extensions/graphql/src/graphql.server.ts @@ -45,11 +45,10 @@ import { BuildSchemaOptions as TypeGrahpQLBuildSchemaOptions, buildSchema, } from 'type-graphql'; -import {Middleware} from 'type-graphql/build/typings/typings/middleware'; import {WebSocketServer} from 'ws'; import {LoopBackContainer} from './graphql.container'; import {GraphQLBindings, GraphQLTags} from './keys'; -import {GraphQLServerOptions} from './types'; +import {GraphQLMiddleware, GraphQLServerOptions} from './types'; const {json} = pkg; /** @@ -100,8 +99,8 @@ export class GraphQLServer extends Context implements Server { /** * Get a list of middleware */ - async getMiddlewareList(): Promise[]> { - const view = this.createView>( + async getMiddlewareList(): Promise[]> { + const view = this.createView>( filterByTag(GraphQLTags.MIDDLEWARE), ); return view.values(); @@ -111,8 +110,12 @@ export class GraphQLServer extends Context implements Server { * Register a GraphQL middleware * @param middleware - GraphQL middleware */ - middleware(middleware: Middleware): Binding> { - return this.bind>(BindingKey.generate(`graphql.middleware`)) + middleware( + middleware: GraphQLMiddleware, + ): Binding> { + return this.bind>( + BindingKey.generate(`graphql.middleware`), + ) .to(middleware) .tag(GraphQLTags.MIDDLEWARE); } diff --git a/extensions/graphql/src/keys.ts b/extensions/graphql/src/keys.ts index e11ade9a6123..0d1fd3c2dc65 100644 --- a/extensions/graphql/src/keys.ts +++ b/extensions/graphql/src/keys.ts @@ -37,7 +37,9 @@ export namespace GraphQLBindings { /** * Binding key for the GraphQL context resolver */ - export const GRAPHQL_CONTEXT_RESOLVER = BindingKey.create< + export const GRAPHQL_CONTEXT_RESOLVER: BindingKey< + ExpressMiddlewareOptions<{[key: string]: unknown}>['context'] + > = BindingKey.create< ExpressMiddlewareOptions<{[key: string]: unknown}>['context'] >('graphql.contextResolver'); diff --git a/extensions/graphql/src/types.ts b/extensions/graphql/src/types.ts index a15f26bae088..5ae70e491114 100644 --- a/extensions/graphql/src/types.ts +++ b/extensions/graphql/src/types.ts @@ -7,9 +7,21 @@ import {ApolloServerOptionsWithStaticSchema, BaseContext} from '@apollo/server'; import {HttpOptions} from '@loopback/http-server'; import {ExecutionArgs} from 'graphql'; import {GraphQLExecutionContextValue, SubscribeMessage} from 'graphql-ws'; +import {MiddlewareFn, MiddlewareInterface} from 'type-graphql'; export {Float, ID, Int, ResolverInterface} from 'type-graphql'; -export {Middleware as GraphQLMiddleware} from 'type-graphql/build/typings/typings/middleware'; + +/** + * A GraphQL middleware function or class. + * + * `type-graphql` declares this type as `Middleware` but does not export it from + * the package entry point, so it is declared here to match + * `type-graphql/build/typings/typings/middleware`. + */ +export type GraphQLMiddleware = + | MiddlewareFn + // eslint-disable-next-line @typescript-eslint/no-explicit-any + | (new (...args: any[]) => MiddlewareInterface); /** * Options for GraphQL component diff --git a/extensions/pooling/package.json b/extensions/pooling/package.json index 588bc9ce43cf..017daff3be74 100644 --- a/extensions/pooling/package.json +++ b/extensions/pooling/package.json @@ -49,6 +49,6 @@ "@loopback/testlab": "^8.0.15", "@types/debug": "^4.1.13", "@types/node": "^20.19.43", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/extensions/sequelize/package.json b/extensions/sequelize/package.json index 30dca35b4963..ed3498f6661f 100644 --- a/extensions/sequelize/package.json +++ b/extensions/sequelize/package.json @@ -60,6 +60,6 @@ "pg": "^8.23.0", "pg-hstore": "^2.3.4", "sqlite3": "6.0.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/extensions/sequelize/src/sequelize/sequelize.repository.base.ts b/extensions/sequelize/src/sequelize/sequelize.repository.base.ts index 839672ed6b64..fbcc585f55af 100644 --- a/extensions/sequelize/src/sequelize/sequelize.repository.base.ts +++ b/extensions/sequelize/src/sequelize/sequelize.repository.base.ts @@ -550,9 +550,7 @@ export class SequelizeCrudRepository< return []; } - if (!sourceModel) { - sourceModel = this.sequelizeModel; - } + sourceModel ??= this.sequelizeModel; if (sourceModel === this.sequelizeModel) { const invalidInclusions = inclusionFilters.filter( @@ -663,8 +661,7 @@ export class SequelizeCrudRepository< } const entityClassCol = this.entityClass.definition.properties[columnName]; - const isBooleanColumn = - entityClassCol && entityClassCol.type === 'boolean'; + const isBooleanColumn = entityClassCol?.type === 'boolean'; if (isTruelyObject(conditionValue)) { sequelizeWhere[columnName] = {}; @@ -718,7 +715,7 @@ export class SequelizeCrudRepository< * Get Sequelize Model * @returns Sequelize Model Instance based on the definitions from `entityClass` */ - public getSequelizeModel(entityClass = this.entityClass) { + public getSequelizeModel(entityClass: typeof Entity = this.entityClass) { if (!this.dataSource.sequelize) { throw Error( `The datasource "${this.dataSource.name}" doesn't have sequelize instance bound to it.`, @@ -832,7 +829,7 @@ export class SequelizeCrudRepository< * @param {Entity} entityClass - The entity class for which the table name is being retrieved. * @returns {string} - The table name associated with the entity class. Which is used when performing the query. */ - getTableName(entityClass = this.entityClass) { + getTableName(entityClass: typeof Entity = this.entityClass) { let tableName = entityClass.name; // model class name if (entityClass.definition.name !== tableName) { @@ -934,7 +931,7 @@ export class SequelizeCrudRepository< const stringTypeArray = definition[propName].itemType === String || ['String', 'string'].includes( - definition[propName].itemType?.toString() || '', + definition[propName].itemType?.toString() ?? '', ); dataType = stringTypeArray ? DataTypes.ARRAY(DataTypes.STRING) @@ -1008,7 +1005,7 @@ export class SequelizeCrudRepository< if (typeof value === 'string') { try { return JSON.parse(value); - } catch (_error) { + } catch { return null; } } diff --git a/extensions/socketio/src/socketio.server.ts b/extensions/socketio/src/socketio.server.ts index db2e4398b788..16bcc425e188 100644 --- a/extensions/socketio/src/socketio.server.ts +++ b/extensions/socketio/src/socketio.server.ts @@ -144,9 +144,7 @@ export class SocketIoServer extends Context { if (meta instanceof RegExp || typeof meta === 'string') { meta = {namespace: meta} as SocketIoMetadata; } - if (meta == null) { - meta = getSocketIoMetadata(controllerClass) as SocketIoMetadata; - } + meta ??= getSocketIoMetadata(controllerClass) as SocketIoMetadata; const nsp = meta?.namespace ? this.io.of(meta.namespace) : this.io; if (meta?.name) { this.app.bind(getNamespaceKeyForName(meta.name)).to(nsp); @@ -280,14 +278,10 @@ function resolveHttpServerConfig( ); // Can't check falsiness, 0 is a valid port. - if (result.port == null) { - result.port = 3000; - } + result.port ??= 3000; - if (result.host == null) { - // Set it to '' so that the http server will listen on all interfaces - result.host = undefined; - } + // Set it to '' so that the http server will listen on all interfaces + result.host ??= undefined; return result; } diff --git a/fixtures/mock-oauth2-provider/src/mock-oauth2-social-app.ts b/fixtures/mock-oauth2-provider/src/mock-oauth2-social-app.ts index e43ba9284a09..119a62893272 100644 --- a/fixtures/mock-oauth2-provider/src/mock-oauth2-social-app.ts +++ b/fixtures/mock-oauth2-provider/src/mock-oauth2-social-app.ts @@ -18,13 +18,25 @@ 'use strict'; import bodyParser from 'body-parser'; -import express from 'express'; +import express, {Request, RequestHandler, Response} from 'express'; import {Server} from 'http'; import jwt from 'jsonwebtoken'; import {MyUser} from './user-repository'; /* eslint-disable @typescript-eslint/naming-convention */ +/** + * Adapt an async handler so that rejections are passed to Express instead of + * becoming unhandled promise rejections. + */ +function asyncHandler( + handler: (req: Request, res: Response) => Promise, +): RequestHandler { + return (req, res, next) => { + handler(req, res).catch(next); + }; +} + const app = express(); let server: Server; @@ -246,38 +258,42 @@ app.get('/login', function (req, response) { * 3. stores token * 4. redirects to callback url with access code */ -app.post('/login_submit', urlencodedParser, async function (req, res) { - if (!req.body.username) { - res.status(400).send({error: 'missing username'}); - return; - } - const user: MyUser | undefined = findUser( - req.body.username, - req.body.password, - ); - if (user) { - // get registered app - const registeredApp = registeredApps[req.body.client_id]; - // generate access code - const authCode = Math.floor(Math.random() * Math.floor(1000)); - // create a token for the access code - const result = await createJwt( - user, - req.body.scope, - user.signingKey, - req.body.client_id, +app.post( + '/login_submit', + urlencodedParser, + asyncHandler(async function (req, res) { + if (!req.body.username) { + res.status(400).send({error: 'missing username'}); + return; + } + const user: MyUser | undefined = findUser( + req.body.username, + req.body.password, ); - // store generated token - registeredApp.tokens[authCode] = {token: result.token}; - registeredApp[result.id] = {signingKey: user.signingKey, code: authCode}; - // redirect to call back url with the access code - let params = '?client_id=' + req.body.client_id; - params = params + '&&code=' + authCode; - res.redirect(req.body.redirect_uri + params); - } else { - res.sendStatus(401); - } -}); + if (user) { + // get registered app + const registeredApp = registeredApps[req.body.client_id]; + // generate access code + const authCode = Math.floor(Math.random() * Math.floor(1000)); + // create a token for the access code + const result = await createJwt( + user, + req.body.scope, + user.signingKey, + req.body.client_id, + ); + // store generated token + registeredApp.tokens[authCode] = {token: result.token}; + registeredApp[result.id] = {signingKey: user.signingKey, code: authCode}; + // redirect to call back url with the access code + let params = '?client_id=' + req.body.client_id; + params = params + '&&code=' + authCode; + res.redirect(req.body.redirect_uri + params); + } else { + res.sendStatus(401); + } + }), +); /** * Endpoint: POST '/oauth/token' @@ -337,23 +353,26 @@ app.get('/oauth/token', function (req, res) { * * Verifies token and returns user profile */ -app.get('/verify', async function (req, res) { - try { - const token = (req.query.access_token ?? - req.header('Authorization')) as string; - if (!token) { - res.status(400).send({error: 'missing access_token'}); - return; +app.get( + '/verify', + asyncHandler(async function (req, res) { + try { + const token = (req.query.access_token ?? + req.header('Authorization')) as string; + if (!token) { + res.status(400).send({error: 'missing access_token'}); + return; + } + const result = await verifyToken(token); + const expirationTime = result.exp; + res.setHeader('Content-Type', 'application/json'); + res.send({...result, expirationTime: expirationTime}); + } catch (err) { + res.setHeader('Content-Type', 'application/json'); + res.status(401).send({error: err.message}); } - const result = await verifyToken(token); - const expirationTime = result.exp; - res.setHeader('Content-Type', 'application/json'); - res.send({...result, expirationTime: expirationTime}); - } catch (err) { - res.setHeader('Content-Type', 'application/json'); - res.status(401).send({error: err.message}); - } -}); + }), +); export function startApp(port = 9000) { server = app.listen(port); diff --git a/package-lock.json b/package-lock.json index 364537fda95b..fa5859aa7295 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,8 +24,8 @@ "@npmcli/map-workspaces": "^6.0.0", "@npmcli/package-json": "^8.0.0", "@types/mocha": "^10.0.10", - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", + "@typescript-eslint/eslint-plugin": "^8.70.0", + "@typescript-eslint/parser": "^8.70.0", "cross-env": "^10.1.0", "cz-conventional-changelog": "^3.3.0", "eslint": "^8.57.1", @@ -42,7 +42,7 @@ "mem-fs-editor": "^9.7.0", "open-cli": "^9.0.0", "prettier": "^3.8.3", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26", @@ -102,7 +102,7 @@ "loopback-connector-cloudant": "2.5.0", "ms": "2.1.3", "tslib": "^2.8.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -298,7 +298,7 @@ "@types/msgpack5": "^3.4.7", "@types/node": "^20.19.43", "@types/type-is": "^1.6.7", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -369,7 +369,7 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -411,7 +411,7 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -448,7 +448,7 @@ "@loopback/testlab": "^8.0.15", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -492,7 +492,7 @@ "@types/express": "^4.17.25", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -535,7 +535,7 @@ "@types/multer": "^2.2.0", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -582,7 +582,7 @@ "graphql-ws": "^5.16.2", "rimraf": "^5.0.10", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -622,7 +622,7 @@ "@types/debug": "4.1.13", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -665,7 +665,7 @@ "@types/debug": "4.1.13", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -703,7 +703,7 @@ "@loopback/testlab": "^8.0.15", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -755,7 +755,7 @@ "eslint": "^8.57.1", "lodash": "^4.18.1", "should": "^13.2.3", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -796,7 +796,7 @@ "@types/debug": "^4.1.13", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -836,7 +836,7 @@ "@loopback/testlab": "^8.0.15", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -883,7 +883,7 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -961,7 +961,7 @@ "axios": "^1.16.0", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -1007,7 +1007,7 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -1053,7 +1053,7 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -1092,7 +1092,7 @@ "@types/express": "^4.17.25", "@types/node": "^20.19.43", "eslint": "^8.57.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -1138,7 +1138,7 @@ "eslint": "^8.57.1", "mocha": "^12.0.1", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -1184,7 +1184,7 @@ "eslint": "^8.57.1", "socket.io-client": "^4.8.3", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -1232,7 +1232,7 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -1265,7 +1265,7 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -1311,7 +1311,7 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -1372,7 +1372,7 @@ "@types/node": "^20.19.43", "eslint": "^8.57.1", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -1414,7 +1414,7 @@ "events": "^3.3.0", "process": "^0.11.10", "puppeteer": "^25.10.0", - "typescript": "~5.2.2", + "typescript": "npm:@typescript/typescript6@~6.0.2", "util": "^0.12.5", "webpack": "^5.110.3", "webpack-cli": "^7.2.3" @@ -1505,7 +1505,7 @@ "@types/lodash": "^4.17.25", "@types/node": "^20.19.43", "lodash": "^4.18.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -1881,7 +1881,7 @@ "@loopback/testlab": "^8.0.15", "@types/debug": "^4.1.13", "@types/node": "^20.19.43", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -1928,7 +1928,7 @@ "pg": "^8.23.0", "pg-hstore": "^2.3.4", "sqlite3": "6.0.1", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -3454,23 +3454,28 @@ "license": "MIT" }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "license": "MIT", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -9927,197 +9932,605 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", - "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/type-utils": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.0.tgz", + "integrity": "sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.70.0", + "@typescript-eslint/type-utils": "8.70.0", + "@typescript-eslint/utils": "8.70.0", + "@typescript-eslint/visitor-keys": "8.70.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.70.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.9.tgz", + "integrity": "sha512-brTTsvFRt5C1gGHtPst/281UjPD5t9fBqbgoMPlVWy11ZLTPfu7HxK4ZYqO9H7o/yC9rSTCI85EaQ4OoY12qYw==", + "license": "MIT", + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", - "dependencies": { - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4" + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.70.0.tgz", + "integrity": "sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.70.0", + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/typescript-estree": "8.70.0", + "@typescript-eslint/visitor-keys": "8.70.0", + "debug": "^4.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", - "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.0.tgz", + "integrity": "sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0" + "@typescript-eslint/tsconfig-utils": "^8.70.0", + "@typescript-eslint/types": "^8.70.0", + "debug": "^4.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", - "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.0.tgz", + "integrity": "sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==", + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.18.0", - "@typescript-eslint/utils": "7.18.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/visitor-keys": "8.70.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } } }, - "node_modules/@typescript-eslint/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", - "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.0.tgz", + "integrity": "sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==", + "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", - "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.70.0.tgz", + "integrity": "sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw==", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/visitor-keys": "7.18.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/typescript-estree": "8.70.0", + "@typescript-eslint/utils": "8.70.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", + "node_modules/@typescript-eslint/types": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.0.tgz", + "integrity": "sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.0.tgz", + "integrity": "sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.2" + "@typescript-eslint/project-service": "8.70.0", + "@typescript-eslint/tsconfig-utils": "8.70.0", + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/visitor-keys": "8.70.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", - "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.0.tgz", + "integrity": "sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==", + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.18.0", - "@typescript-eslint/types": "7.18.0", - "@typescript-eslint/typescript-estree": "7.18.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.70.0", + "@typescript-eslint/types": "8.70.0", + "@typescript-eslint/typescript-estree": "8.70.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", - "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "version": "8.70.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.0.tgz", + "integrity": "sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.18.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.70.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@typescript/native": { + "name": "typescript", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/@typescript/old": { + "name": "typescript", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -21903,6 +22316,20 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/lerna/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/lerna/node_modules/validate-npm-package-name": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-6.0.2.tgz", @@ -35375,14 +35802,15 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-graphviz": { @@ -35967,15 +36395,16 @@ } }, "node_modules/typescript": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", - "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "name": "@typescript/typescript6", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript6/-/typescript6-6.0.2.tgz", + "integrity": "sha512-mbCddXd+jm7hfx7w2YU64/Av4/NqqeG3GoRZgxPcgoTxYjhrcfJRw9ULch71SS4G+Q3bOXFhRvPqjguN0Hyp5w==", + "license": "Apache-2.0", + "dependencies": { + "@typescript/old": "npm:typescript@^6" }, - "engines": { - "node": ">=14.17" + "bin": { + "tsc6": "bin/tsc6" } }, "node_modules/uid-safe": { @@ -39254,6 +39683,7 @@ "@loopback/eslint-config": "^16.0.1", "@types/mocha": "^10.0.10", "@types/node": "^20.19.43", + "@typescript/native": "npm:typescript@~7.0.2", "cross-spawn": "^7.0.6", "debug": "^4.4.3", "eslint": "^8.57.1", @@ -39265,7 +39695,7 @@ "prettier": "^3.8.3", "rimraf": "^5.0.10", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "bin": { "lb-clean": "bin/run-clean.js", @@ -39375,7 +39805,7 @@ "terminal-link": "^5.0.0", "tildify": "^2.0.0", "ts-morph": "^28.0.0", - "typescript": "~5.2.2", + "typescript": "npm:@typescript/typescript6@~6.0.2", "untildify": "^6.0.0", "update-notifier": "^7.3.1", "url-slug": "^5.1.0", @@ -40615,8 +41045,8 @@ "version": "16.0.1", "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", + "@typescript-eslint/eslint-plugin": "^8.70.0", + "@typescript-eslint/parser": "^8.70.0", "eslint-config-prettier": "^9.1.1", "eslint-plugin-eslint-plugin": "^5.5.1", "eslint-plugin-mocha": "^10.5.0" @@ -40655,7 +41085,7 @@ "@types/on-finished": "^2.3.5", "@types/toposort": "^2.0.7", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" @@ -40692,7 +41122,7 @@ "@loopback/build": "^12.0.15", "@loopback/testlab": "^8.0.15", "@types/node": "^20.19.43", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engines": { "node": "22 || 24 || 26" diff --git a/package.json b/package.json index 2231d7536514..6a1d053e43b8 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ "@npmcli/map-workspaces": "^6.0.0", "@npmcli/package-json": "^8.0.0", "@types/mocha": "^10.0.10", - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", + "@typescript-eslint/eslint-plugin": "^8.70.0", + "@typescript-eslint/parser": "^8.70.0", "cross-env": "^10.1.0", "cz-conventional-changelog": "^3.3.0", "eslint": "^8.57.1", @@ -71,7 +71,7 @@ "mem-fs-editor": "^9.7.0", "open-cli": "^9.0.0", "prettier": "^3.8.3", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" }, "engineStrict": true, "engines": { diff --git a/packages/authentication/src/__tests__/fixtures/helper.ts b/packages/authentication/src/__tests__/fixtures/helper.ts index 6099d5eda748..43078d2d612a 100644 --- a/packages/authentication/src/__tests__/fixtures/helper.ts +++ b/packages/authentication/src/__tests__/fixtures/helper.ts @@ -96,7 +96,7 @@ export function createBearerAuthorizationHeaderValue( alternativePrefix?: string, ): string { // default type is 'Bearer ', unless another is specified - const prefix = alternativePrefix ? alternativePrefix : 'Bearer '; + const prefix = alternativePrefix ?? 'Bearer '; return prefix + token; } diff --git a/packages/authorization/src/__tests__/acceptance/authorization.options.acceptance.ts b/packages/authorization/src/__tests__/acceptance/authorization.options.acceptance.ts index 62c3a8abf0d2..5a9d2078877b 100644 --- a/packages/authorization/src/__tests__/acceptance/authorization.options.acceptance.ts +++ b/packages/authorization/src/__tests__/acceptance/authorization.options.acceptance.ts @@ -163,7 +163,7 @@ describe('Authorization', () => { try { await invokeMethod(controller, 'cancelOrder', reqCtx, ['order-01']); finalDecision = Allow; - } catch (err) { + } catch { finalDecision = Deny; } return finalDecision; @@ -174,7 +174,7 @@ describe('Authorization', () => { try { await invokeMethod(controller, 'placeOrder', reqCtx, ['prod-01', 10]); finalDecision = Allow; - } catch (err) { + } catch { finalDecision = Deny; } return finalDecision; diff --git a/packages/boot/src/booters/base-artifact.booter.ts b/packages/boot/src/booters/base-artifact.booter.ts index 50150a50bbd0..634c4eb489ea 100644 --- a/packages/boot/src/booters/base-artifact.booter.ts +++ b/packages/boot/src/booters/base-artifact.booter.ts @@ -96,9 +96,9 @@ export class BaseArtifactBooter implements Booter { const joinedExts = `@(${this.extensions.join('|')})`; - this.glob = this.options.glob - ? this.options.glob - : `/${joinedDirs}/${this.options.nested ? '**/*' : '*'}${joinedExts}`; + this.glob = + this.options.glob ?? + `/${joinedDirs}/${this.options.nested ? '**/*' : '*'}${joinedExts}`; } /** diff --git a/packages/boot/src/bootstrapper.ts b/packages/boot/src/bootstrapper.ts index 40c473b05c15..21ed0ce8399e 100644 --- a/packages/boot/src/bootstrapper.ts +++ b/packages/boot/src/bootstrapper.ts @@ -96,9 +96,7 @@ export class Bootstrapper { // names of booters that should be run), that is the value, otherwise it // is all the registered booters by default. const names = execOptions - ? execOptions.filter?.booters - ? execOptions.filter.booters - : defaultBooterNames + ? (execOptions.filter?.booters ?? defaultBooterNames) : defaultBooterNames; // Filter bindings by names diff --git a/packages/booter-lb3app/src/lb3app.booter.ts b/packages/booter-lb3app/src/lb3app.booter.ts index da1fef2295e5..50111db8156b 100644 --- a/packages/booter-lb3app/src/lb3app.booter.ts +++ b/packages/booter-lb3app/src/lb3app.booter.ts @@ -131,7 +131,7 @@ export class Lb3AppBooter implements Booter { } private mountFullApp(lb3App: Lb3Application, spec: OpenApiSpec) { - const restApiRoot = lb3App.get('restApiRoot') || '/'; + const restApiRoot = lb3App.get('restApiRoot') ?? '/'; debug('Mounting the entire LB3 app at %s', restApiRoot); const specInRoot = rebaseOpenApiSpec(spec, restApiRoot); this.app.mountExpressRouter('/', lb3App, specInRoot); diff --git a/packages/build/README.md b/packages/build/README.md index 55479348de04..08e69e43cb55 100644 --- a/packages/build/README.md +++ b/packages/build/README.md @@ -93,6 +93,35 @@ Now you run the scripts, such as: | ------------------ | ------------------------------------------------------------------------------------------------- | | `--copy-resources` | Copy all non-typescript files from `src` and `test` to `outDir`, preserving their relative paths. | + - TypeScript compiler + + `lb-tsc` compiles with the TypeScript 7 native compiler (installed as + `@typescript/native`), preferring the copy owned by the project being built. + If TypeScript 7 is not installed, it falls back to the `typescript` package. + + TypeScript 7 no longer ships the JavaScript compiler API, so `typescript` is + aliased to `@typescript/typescript6`. That keeps `require('typescript')` + working for tools that consume the API, such as `@typescript-eslint` and + editor language services. + + Note that TypeScript 7 removed `"moduleResolution": "node"` (node10). Remove + the option from your `tsconfig.json` - `"module": "commonjs"` resolves + modules the same way Node.js does without it. + + TypeScript 6 also stopped including every installed `@types/*` package + automatically, so `tsconfig.common.json` lists `"types": ["node", "mocha"]`. + A project that relies on other packages of global type declarations has to + list them explicitly: + + ```json + { + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "types": ["node", "mocha", "my-global-types"] + } + } + ``` + - Using [`ttypescript`](https://github.com/cevek/ttypescript) ### Stability: ⚠️Experimental⚠️ diff --git a/packages/build/bin/compile-package.js b/packages/build/bin/compile-package.js index 808d47227d85..85c4d08a6f1b 100755 --- a/packages/build/bin/compile-package.js +++ b/packages/build/bin/compile-package.js @@ -23,8 +23,29 @@ const path = require('path'); const fs = require('fs'); const {globSync} = require('glob'); const fse = require('fs-extra'); +// The `typescript` package is aliased to the TypeScript 6 JS compiler, which +// still ships the compiler API. TypeScript 7 (the native compiler) does not. const {buildOpts: buildOptions} = require('typescript'); +/** + * Resolve the TypeScript 7 native compiler (`@typescript/native`), preferring + * the copy owned by the package being built. Its `exports` map does not expose + * `lib/tsc.js`, so the CLI is located relative to the package manifest. + * + * Returns `undefined` when TypeScript 7 is not installed, in which case the + * `typescript` package (TypeScript 6) is used to compile. + */ +function resolveNativeTsc() { + try { + const manifest = require.resolve('@typescript/native/package.json', { + paths: [utils.getPackageDir(), __dirname], + }); + return path.join(path.dirname(manifest), 'bin', 'tsc'); + } catch (e) { + debug('TypeScript 7 (@typescript/native) is not installed: %s', e.message); + } +} + function run(argv, options) { if (options === true) { options = {dryRun: true}; @@ -48,12 +69,12 @@ function run(argv, options) { '--copy-resources', ); - let TSC_CLI = 'typescript/lib/tsc'; + let TSC_CLI = resolveNativeTsc() ?? 'typescript/lib/tsc'; if (useTtsc) { try { require.resolve('ttypescript'); TSC_CLI = 'ttypescript/lib/tsc'; - } catch (e) { + } catch { if (isUseTtscSet) { console.error( 'Error using the --use-ttypescript option - ttypescript is not installed', @@ -174,6 +195,11 @@ function run(argv, options) { * @param {string[]} args An array of arguments */ function validArgsForBuild(args) { + if (!Array.isArray(buildOptions)) { + debug('Cannot validate args for tsc -b - the compiler API is unavailable'); + return args; + } + const validBooleanOptions = []; const validValueOptions = []; @@ -194,6 +220,8 @@ function validArgsForBuild(args) { let validArgs = args; if (args.includes('-b') || args.includes('--build')) { validArgs = filterArgs(args, (arg, next) => { + // `-b` is re-added below as the first argument + if (arg === '-b' || arg === '--build') return 0; if (validBooleanOptions.includes(arg)) { return next === 'false' || next === 'true' ? 2 : 1; } diff --git a/packages/build/bin/utils.js b/packages/build/bin/utils.js index 135d7bef7f6c..2daeb243a573 100644 --- a/packages/build/bin/utils.js +++ b/packages/build/bin/utils.js @@ -70,7 +70,7 @@ function resolveCLI(cli, options = {resolveFromProjectFirst: true}) { const pkgDir = getPackageDir(); const resolved = resolveCLIFromProject(cli, pkgDir); if (resolved != null) return resolved; - } catch (e) { + } catch { // Ignore errors } return require.resolve(cli); diff --git a/packages/build/config/tsconfig.common.json b/packages/build/config/tsconfig.common.json index 8d69c2baae2a..79825d36329e 100644 --- a/packages/build/config/tsconfig.common.json +++ b/packages/build/config/tsconfig.common.json @@ -15,10 +15,13 @@ "incremental": true, + // TypeScript 6 no longer includes every `@types/*` package automatically, + // ambient type packages have to be listed explicitly. + "types": ["node", "mocha"], + "lib": ["es2020"], "module": "commonjs", "esModuleInterop": true, - "moduleResolution": "node", "target": "es2018", "sourceMap": true, "declaration": true, diff --git a/packages/build/package.json b/packages/build/package.json index 3c7b8491f093..f6e1ceeae98d 100644 --- a/packages/build/package.json +++ b/packages/build/package.json @@ -34,6 +34,7 @@ "@loopback/eslint-config": "^16.0.1", "@types/mocha": "^10.0.10", "@types/node": "^20.19.43", + "@typescript/native": "npm:typescript@~7.0.2", "cross-spawn": "^7.0.6", "debug": "^4.4.3", "eslint": "^8.57.1", @@ -45,6 +46,6 @@ "prettier": "^3.8.3", "rimraf": "^5.0.10", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/packages/build/test/integration/scripts.integration.js b/packages/build/test/integration/scripts.integration.js index edbaf5067e4e..bd3f3252cd90 100644 --- a/packages/build/test/integration/scripts.integration.js +++ b/packages/build/test/integration/scripts.integration.js @@ -185,7 +185,8 @@ describe('build', /** @this {Mocha.Suite} */ function () { '--locale en_US.UTF-8 should be honored', ); assert( - command.indexOf('tsc.js -b') !== -1, + // `tsc` for the TypeScript 7 native compiler, `tsc.js` for TypeScript 6 + /[\\/]tsc(\.js)? -b /.test(command), '-b should be the first argument', ); }); diff --git a/packages/cli/.yo-rc.json b/packages/cli/.yo-rc.json index 6973e6a7ffd9..d8b7ae537f0d 100644 --- a/packages/cli/.yo-rc.json +++ b/packages/cli/.yo-rc.json @@ -178,14 +178,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "required": false, - "description": "Project name for the application", - "name": "name" - } - ], + "arguments": [], "name": "app" }, "extension": { @@ -309,14 +302,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "required": false, - "description": "Project name for the extension", - "name": "name" - } - ], + "arguments": [], "name": "extension" }, "controller": { @@ -387,14 +373,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "required": false, - "description": "Name for the controller", - "name": "name" - } - ], + "arguments": [], "name": "controller" }, "datasource": { @@ -458,14 +437,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "required": false, - "description": "Name for the datasource", - "name": "name" - } - ], + "arguments": [], "name": "datasource" }, "import-lb3-models": { @@ -536,14 +508,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "required": true, - "description": "Path to your LoopBack 3.x application. This can be a project directory (e.g. \"my-lb3-app\") or the server file (e.g. \"my-lb3-app/server/server.js\").", - "name": "lb3app" - } - ], + "arguments": [], "name": "import-lb3-models" }, "model": { @@ -635,14 +600,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "required": false, - "description": "Name for the model", - "name": "name" - } - ], + "arguments": [], "name": "model" }, "repository": { @@ -734,14 +692,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "required": false, - "description": "Name for the repository ", - "name": "name" - } - ], + "arguments": [], "name": "repository" }, "service": { @@ -819,14 +770,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "required": false, - "description": "Name for the service", - "name": "name" - } - ], + "arguments": [], "name": "service" }, "example": { @@ -890,14 +834,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "description": "Name of the example to clone", - "required": false, - "name": "example-name" - } - ], + "arguments": [], "name": "example" }, "openapi": { @@ -1015,14 +952,7 @@ "hide": false } }, - "arguments": [ - { - "description": "URL or file path of the OpenAPI spec", - "required": false, - "type": "String", - "name": "url" - } - ], + "arguments": [], "name": "openapi" }, "observer": { @@ -1093,14 +1023,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "required": false, - "description": "Name for the observer", - "name": "name" - } - ], + "arguments": [], "name": "observer" }, "interceptor": { @@ -1178,14 +1101,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "required": false, - "description": "Name for the interceptor", - "name": "name" - } - ], + "arguments": [], "name": "interceptor" }, "discover": { @@ -1309,14 +1225,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "required": false, - "description": "Name for the discover", - "name": "name" - } - ], + "arguments": [], "name": "discover" }, "relation": { @@ -1650,14 +1559,7 @@ "hide": false } }, - "arguments": [ - { - "type": "String", - "required": false, - "description": "Name for the rest-config", - "name": "name" - } - ], + "arguments": [], "name": "rest-crud" }, "copyright": { diff --git a/packages/cli/bin/cli-main.js b/packages/cli/bin/cli-main.js index eb521bf7fba0..8c6cef47fb2c 100755 --- a/packages/cli/bin/cli-main.js +++ b/packages/cli/bin/cli-main.js @@ -46,21 +46,21 @@ const originalCommand = args[0]; if (tabCompletionCommands.includes(originalCommand)) { const yoJsonFile = path.join(__dirname, '../.yo-rc.json'); const config = fs.readJsonSync(yoJsonFile); - return runTabCompletionCommand(config.commands, originalCommand, console.log); -} - -const main = require('../lib/cli'); + runTabCompletionCommand(config.commands, originalCommand, console.log); +} else { + const main = require('../lib/cli'); -// Force version check with `lb4 --version` -const interval = opts.version ? 0 : undefined; + // Force version check with `lb4 --version` + const interval = opts.version ? 0 : undefined; -import('update-notifier') - .then(({default: updateNotifier}) => { - updateNotifier({ - pkg: pkg, - updateCheckInterval: interval, - }).notify({isGlobal: true}); - }) - .catch(() => {}); + import('update-notifier') + .then(({default: updateNotifier}) => { + updateNotifier({ + pkg: pkg, + updateCheckInterval: interval, + }).notify({isGlobal: true}); + }) + .catch(() => {}); -main(opts); + main(opts); +} diff --git a/packages/cli/generators/app/templates/src/index.ts.ejs b/packages/cli/generators/app/templates/src/index.ts.ejs index 24768091c944..eebbdbaae396 100644 --- a/packages/cli/generators/app/templates/src/index.ts.ejs +++ b/packages/cli/generators/app/templates/src/index.ts.ejs @@ -19,7 +19,7 @@ if (require.main === module) { const config = { rest: { port: +(process.env.PORT ?? 3000), - host: process.env.HOST || '127.0.0.1', + host: process.env.HOST ?? '127.0.0.1', // The `gracePeriodForClose` provides a graceful close for http/https // servers with keep-alive clients. The default value is `Infinity` // (don't force-close). If you want to immediately destroy all sockets diff --git a/packages/cli/generators/project/templates/tsconfig.json.ejs b/packages/cli/generators/project/templates/tsconfig.json.ejs index 782307ad4522..ea98a4a5c806 100644 --- a/packages/cli/generators/project/templates/tsconfig.json.ejs +++ b/packages/cli/generators/project/templates/tsconfig.json.ejs @@ -24,7 +24,6 @@ "lib": ["es2018", "esnext.asynciterable"], "module": "commonjs", "esModuleInterop": true, - "moduleResolution": "node", "target": "es2017", "sourceMap": true, "declaration": true diff --git a/packages/cli/lib/utils.js b/packages/cli/lib/utils.js index a4a3ee26b4a9..e2ecc645509b 100644 --- a/packages/cli/lib/utils.js +++ b/packages/cli/lib/utils.js @@ -454,7 +454,7 @@ exports.validateStringObject = function (type) { if (type === 'array' && !Array.isArray(result)) { return err; } - } catch (e) { + } catch { return err; } diff --git a/packages/cli/package.json b/packages/cli/package.json index 3b34b7af5d98..8debd66fbe0d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -73,7 +73,7 @@ "terminal-link": "^5.0.0", "tildify": "^2.0.0", "ts-morph": "^28.0.0", - "typescript": "~5.2.2", + "typescript": "npm:@typescript/typescript6@~6.0.2", "untildify": "^6.0.0", "update-notifier": "^7.3.1", "url-slug": "^5.1.0", @@ -110,12 +110,12 @@ "cross-spawn": "^7.0.6", "debug": "^4.4.3", "fs-extra": "^11.4.0", - "mocha": "^11.8.0", + "mocha": "^12.0.1", "nyc": "^18.0.0", "prettier": "^3.8.3", "rimraf": "^5.0.10", "source-map-support": "^0.5.21", - "typescript": "~5.2.2", + "typescript": "npm:@typescript/typescript6@~6.0.2", "@loopback/authentication": "^12.0.17", "@loopback/boot": "^8.0.16", "@loopback/build": "^12.0.15", @@ -152,8 +152,8 @@ "eslint-plugin-mocha": "^10.5.0", "@loopback/example-greeting-app": "^8.0.16", "@loopback/example-context": "^8.0.15", - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", + "@typescript-eslint/eslint-plugin": "^8.70.0", + "@typescript-eslint/parser": "^8.70.0", "eslint-plugin-eslint-plugin": "^5.5.1", "eslint-config-prettier": "^9.1.1", "@loopback/repository-tests": "^0.25.16", @@ -195,7 +195,8 @@ "@loopback/monorepo": "^0.6.0", "@loopback/tsdocs": "^7.0.15", "@loopback/example-references-many": "^9.0.16", - "@loopback/sequelize": "^0.8.12" + "@loopback/sequelize": "^0.8.12", + "@typescript/native": "npm:typescript@~7.0.2" } } } diff --git a/packages/context/src/__tests__/unit/try-catch-finally.unit.ts b/packages/context/src/__tests__/unit/try-catch-finally.unit.ts index 697d04162252..b772e13f3eda 100644 --- a/packages/context/src/__tests__/unit/try-catch-finally.unit.ts +++ b/packages/context/src/__tests__/unit/try-catch-finally.unit.ts @@ -19,7 +19,7 @@ describe('tryWithFinally', () => { let finalActionInvoked = false; const action = () => 1; const finalAction = () => (finalActionInvoked = true); - // eslint-disable-next-line @typescript-eslint/no-floating-promises + tryWithFinally(action, finalAction); expect(finalActionInvoked).to.be.true(); }); @@ -56,7 +56,7 @@ describe('tryCatchFinally', () => { let finalActionInvoked = false; const action = () => 1; const finalAction = () => (finalActionInvoked = true); - // eslint-disable-next-line @typescript-eslint/no-floating-promises + tryCatchFinally(action, undefined, finalAction); expect(finalActionInvoked).to.be.true(); }); @@ -79,7 +79,7 @@ describe('tryCatchFinally', () => { errorActionInvoked = true; throw err; }; - // eslint-disable-next-line @typescript-eslint/no-floating-promises + tryCatchFinally(action, errorAction); expect(errorActionInvoked).to.be.false(); }); diff --git a/packages/context/src/binding-inspector.ts b/packages/context/src/binding-inspector.ts index 85a777b49a2c..17db19645eef 100644 --- a/packages/context/src/binding-inspector.ts +++ b/packages/context/src/binding-inspector.ts @@ -356,16 +356,14 @@ function buildBindingKey( ); // Derive the key from type + name let type = options.type ?? bindingTemplate.tagMap[ContextTags.TYPE]; - if (!type) { - type = - bindingTemplate.tagNames.find(t => namespaces[t] != null) ?? - ContextTags.CLASS; - } + type ??= + bindingTemplate.tagNames.find(t => namespaces[t] != null) ?? + ContextTags.CLASS; namespace = getNamespace(type, namespaces); } const name = - options.name ?? (bindingTemplate.tagMap[ContextTags.NAME] || cls.name); + options.name ?? bindingTemplate.tagMap[ContextTags.NAME] ?? cls.name; key = `${namespace}.${name}`; return key; diff --git a/packages/context/src/context-view.ts b/packages/context/src/context-view.ts index 22be275203dd..1f013cc29945 100644 --- a/packages/context/src/context-view.ts +++ b/packages/context/src/context-view.ts @@ -127,9 +127,7 @@ export class ContextView */ get bindings(): Readonly>[] { debug('Reading bindings'); - if (this._cachedBindings == null) { - this._cachedBindings = this.findBindings(); - } + this._cachedBindings ??= this.findBindings(); return this._cachedBindings; } diff --git a/packages/context/src/inject-config.ts b/packages/context/src/inject-config.ts index 04d48fda6a84..1dc692a9c688 100644 --- a/packages/context/src/inject-config.ts +++ b/packages/context/src/inject-config.ts @@ -138,7 +138,7 @@ function getCurrentBindingKey(session: ResolutionSession) { * @param session - Resolution session */ function getTargetBindingKey(injection: Injection, session: ResolutionSession) { - return injection.metadata.fromBinding || getCurrentBindingKey(session); + return injection.metadata.fromBinding ?? getCurrentBindingKey(session); } /** diff --git a/packages/context/src/resolution-session.ts b/packages/context/src/resolution-session.ts index 0ed8af400b22..a264840d05d3 100644 --- a/packages/context/src/resolution-session.ts +++ b/packages/context/src/resolution-session.ts @@ -55,7 +55,7 @@ export type ResolutionElement = BindingElement | InjectionElement; function isBinding( element: ResolutionElement | undefined, ): element is BindingElement { - return element != null && element.type === 'binding'; + return element?.type === 'binding'; } /** @@ -65,7 +65,7 @@ function isBinding( function isInjection( element: ResolutionElement | undefined, ): element is InjectionElement { - return element != null && element.type === 'injection'; + return element?.type === 'injection'; } /** diff --git a/packages/context/src/value-promise.ts b/packages/context/src/value-promise.ts index 4e4aa85c944a..d026c3bc3331 100644 --- a/packages/context/src/value-promise.ts +++ b/packages/context/src/value-promise.ts @@ -110,7 +110,7 @@ export function resolveMap( for (const key in map) { const valueOrPromise = resolver(map[key], key, map); if (isPromiseLike(valueOrPromise)) { - if (!asyncResolvers) asyncResolvers = []; + asyncResolvers ??= []; asyncResolvers.push(valueOrPromise.then(setter(key))); } else { if (valueOrPromise !== undefined) { @@ -168,7 +168,7 @@ export function resolveList( for (let ix = 0; ix < list.length; ix++) { const valueOrPromise = resolver(list[ix], ix, list); if (isPromiseLike(valueOrPromise)) { - if (!asyncResolvers) asyncResolvers = []; + asyncResolvers ??= []; asyncResolvers.push(valueOrPromise.then(setter(ix))); } else { result[ix] = valueOrPromise; diff --git a/packages/core/src/extension-point.ts b/packages/core/src/extension-point.ts index 3fed237d544c..192425e129d3 100644 --- a/packages/core/src/extension-point.ts +++ b/packages/core/src/extension-point.ts @@ -229,7 +229,7 @@ function inferExtensionPointName( ): string { if (currentBinding) { const name = - currentBinding.tagMap[CoreTags.EXTENSION_POINT] || + currentBinding.tagMap[CoreTags.EXTENSION_POINT] ?? currentBinding.tagMap[ContextTags.NAME]; if (name) return name; diff --git a/packages/core/src/lifecycle-registry.ts b/packages/core/src/lifecycle-registry.ts index 3178bd0dd555..a6c1c22fa7c2 100644 --- a/packages/core/src/lifecycle-registry.ts +++ b/packages/core/src/lifecycle-registry.ts @@ -99,11 +99,9 @@ export class LifeCycleObserverRegistry implements LifeCycleObserver { ): string { // First check if there is an explicit group name in the tag let group = binding.tagMap[CoreTags.LIFE_CYCLE_OBSERVER_GROUP]; - if (!group) { - // Fall back to a tag that matches one of the groups - group = this.options.orderedGroups.find(g => binding.tagMap[g] === g); - } - group = group || ''; + // Fall back to a tag that matches one of the groups + group ??= this.options.orderedGroups.find(g => binding.tagMap[g] === g); + group = group ?? ''; debug( 'Binding %s is configured with observer group %s', binding.key, diff --git a/packages/eslint-config/eslintrc.js b/packages/eslint-config/eslintrc.js index 6a9385df6153..ebd4b5acbfc7 100644 --- a/packages/eslint-config/eslintrc.js +++ b/packages/eslint-config/eslintrc.js @@ -69,6 +69,8 @@ module.exports = { '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-member-accessibility': 'off', '@typescript-eslint/no-var-requires': 'off', + // `no-require-imports` replaces `no-var-requires` in typescript-eslint v8 + '@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/no-object-literal-type-assertion': 'off', '@typescript-eslint/no-parameter-properties': 'off', @@ -76,6 +78,10 @@ module.exports = { '@typescript-eslint/prefer-interface': 'off', '@typescript-eslint/no-namespace': 'off', '@typescript-eslint/ban-types': 'off', + // `ban-types` was split into the following rules in typescript-eslint v8 + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/no-unsafe-function-type': 'off', + '@typescript-eslint/no-wrapper-object-types': 'off', '@typescript-eslint/no-triple-slash-reference': 'off', '@typescript-eslint/no-empty-interface': 'off', '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 9ddee37a0f6a..1319f5af0470 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -21,8 +21,8 @@ "eslint": "^8.57.1" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "^7.18.0", - "@typescript-eslint/parser": "^7.18.0", + "@typescript-eslint/eslint-plugin": "^8.70.0", + "@typescript-eslint/parser": "^8.70.0", "eslint-config-prettier": "^9.1.1", "eslint-plugin-eslint-plugin": "^5.5.1", "eslint-plugin-mocha": "^10.5.0" diff --git a/packages/express/package.json b/packages/express/package.json index 58ba6e77b2c6..6320694acada 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -62,6 +62,6 @@ "@types/on-finished": "^2.3.5", "@types/toposort": "^2.0.7", "source-map-support": "^0.5.21", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/packages/filter/package.json b/packages/filter/package.json index c51bcc8b184f..01f3ad613da3 100644 --- a/packages/filter/package.json +++ b/packages/filter/package.json @@ -38,6 +38,6 @@ "@loopback/build": "^12.0.15", "@loopback/testlab": "^8.0.15", "@types/node": "^20.19.43", - "typescript": "~5.2.2" + "typescript": "npm:@typescript/typescript6@~6.0.2" } } diff --git a/packages/filter/src/query.ts b/packages/filter/src/query.ts index bdd4afe5c972..5d036bb888e5 100644 --- a/packages/filter/src/query.ts +++ b/packages/filter/src/query.ts @@ -610,9 +610,7 @@ export class FilterBuilder { * or an Order object for the field/direction pairs */ order(...o: (string | string[] | Order)[]): this { - if (!this.filter.order) { - this.filter.order = []; - } + this.filter.order ??= []; o.forEach(order => { if (typeof order === 'string') { this.validateOrder(order); @@ -646,9 +644,7 @@ export class FilterBuilder { * object for the relation/scope definitions */ include(...i: (string | string[] | Inclusion)[]): this { - if (this.filter.include == null) { - this.filter.include = []; - } + this.filter.include ??= []; for (const include of i) { if (typeof include === 'string') { this.filter.include.push({relation: include}); @@ -745,7 +741,7 @@ export function filterTemplate(strings: TemplateStringsArray, ...keys: any[]) { const result = tokens.join(''); try { return JSON.parse(result); - } catch (e) { + } catch { throw new Error('Invalid JSON: ' + result); } }; diff --git a/packages/http-caching-proxy/src/http-caching-proxy.ts b/packages/http-caching-proxy/src/http-caching-proxy.ts index 89c761aed051..e5af8267bc0b 100644 --- a/packages/http-caching-proxy/src/http-caching-proxy.ts +++ b/packages/http-caching-proxy/src/http-caching-proxy.ts @@ -146,7 +146,7 @@ export class HttpCachingProxy { const onerror = (error: Error) => { this.logError(request, error); // eslint-disable-next-line @typescript-eslint/no-explicit-any - response.statusCode = (error as any).statusCode || 502; + response.statusCode = (error as any).statusCode ?? 502; response.end(`${error.name}: ${error.message}`); }; diff --git a/packages/metadata/src/decorator-factory.ts b/packages/metadata/src/decorator-factory.ts index 0ba155e43d1e..e641d4a55d9d 100644 --- a/packages/metadata/src/decorator-factory.ts +++ b/packages/metadata/src/decorator-factory.ts @@ -579,7 +579,7 @@ export class ParameterDecoratorFactory extends DecoratorFactory< target: Object, methodName?: string, ) { - const method = methodName ? methodName : ''; + const method = methodName ?? ''; let methodMeta = meta[method]; if (methodMeta == null) { // Initialize the method metadata @@ -700,7 +700,7 @@ export class MethodParameterDecoratorFactory extends DecoratorFactory< methodName, ); // Default to the last parameter - if (index == null) index = numOfParams - 1; + index ??= numOfParams - 1; if (index < 0) { // Excessive decorations than the number of parameters detected const method = DecoratorFactory.getTargetName( diff --git a/packages/openapi-spec-builder/src/openapi-spec-builder.ts b/packages/openapi-spec-builder/src/openapi-spec-builder.ts index cb798873a3b9..6a6b752fc502 100644 --- a/packages/openapi-spec-builder/src/openapi-spec-builder.ts +++ b/packages/openapi-spec-builder/src/openapi-spec-builder.ts @@ -114,7 +114,7 @@ export class OpenApiSpecBuilder extends BuilderBase { spec: OperationObject | OperationSpecBuilder, ): this { if (spec instanceof OperationSpecBuilder) spec = spec.build(); - if (!this._spec.paths[path]) this._spec.paths[path] = {}; + this._spec.paths[path] ??= {}; this._spec.paths[path][verb] = spec; return this; } @@ -145,7 +145,7 @@ export class OpenApiSpecBuilder extends BuilderBase { */ withComponents(spec: ComponentsObject | ComponentsSpecBuilder): this { if (spec instanceof ComponentsSpecBuilder) spec = spec.build(); - if (!this._spec.components) this._spec.components = spec; + this._spec.components ??= spec; return this; } } @@ -190,7 +190,7 @@ export class OperationSpecBuilder extends BuilderBase { * @param parameterSpecs */ withParameter(...parameterSpecs: ParameterObject[]): this { - if (!this._spec.parameters) this._spec.parameters = []; + this._spec.parameters ??= []; this._spec.parameters.push(...parameterSpecs); return this; } @@ -251,7 +251,7 @@ export class OperationSpecBuilder extends BuilderBase { * @param tags */ withTags(tags: string | string[]): this { - if (!this._spec.tags) this._spec.tags = []; + this._spec.tags ??= []; if (typeof tags === 'string') tags = [tags]; this._spec.tags.push(...tags); return this; @@ -274,7 +274,7 @@ export class ComponentsSpecBuilder extends BuilderBase { * */ withSchema(name: string, schema: SchemaObject | ReferenceObject): this { - if (!this._spec.schemas) this._spec.schemas = {}; + this._spec.schemas ??= {}; this._spec.schemas[name] = schema; return this; } @@ -287,7 +287,7 @@ export class ComponentsSpecBuilder extends BuilderBase { * */ withResponse(name: string, response: ResponseObject | ReferenceObject): this { - if (!this._spec.responses) this._spec.responses = {}; + this._spec.responses ??= {}; this._spec.responses[name] = response; return this; } @@ -303,7 +303,7 @@ export class ComponentsSpecBuilder extends BuilderBase { name: string, parameter: ParameterObject | ReferenceObject, ): this { - if (!this._spec.parameters) this._spec.parameters = {}; + this._spec.parameters ??= {}; this._spec.parameters[name] = parameter; return this; } @@ -316,7 +316,7 @@ export class ComponentsSpecBuilder extends BuilderBase { * */ withExample(name: string, example: ExampleObject | ReferenceObject): this { - if (!this._spec.examples) this._spec.examples = {}; + this._spec.examples ??= {}; this._spec.examples[name] = example; return this; } @@ -332,7 +332,7 @@ export class ComponentsSpecBuilder extends BuilderBase { name: string, requestBody: RequestBodyObject | ReferenceObject, ): this { - if (!this._spec.requestBodies) this._spec.requestBodies = {}; + this._spec.requestBodies ??= {}; this._spec.requestBodies[name] = requestBody; return this; } @@ -345,7 +345,7 @@ export class ComponentsSpecBuilder extends BuilderBase { * */ withHeader(name: string, header: HeaderObject | ReferenceObject): this { - if (!this._spec.headers) this._spec.headers = {}; + this._spec.headers ??= {}; this._spec.headers[name] = header; return this; } @@ -361,7 +361,7 @@ export class ComponentsSpecBuilder extends BuilderBase { name: string, securityScheme: SecuritySchemeObject | ReferenceObject, ): this { - if (!this._spec.securitySchemes) this._spec.securitySchemes = {}; + this._spec.securitySchemes ??= {}; this._spec.securitySchemes[name] = securityScheme; return this; } @@ -374,7 +374,7 @@ export class ComponentsSpecBuilder extends BuilderBase { * */ withLink(name: string, link: LinkObject | ReferenceObject): this { - if (!this._spec.links) this._spec.links = {}; + this._spec.links ??= {}; this._spec.links[name] = link; return this; } @@ -387,7 +387,7 @@ export class ComponentsSpecBuilder extends BuilderBase { * */ withCallback(name: string, callback: CallbackObject | ReferenceObject): this { - if (!this._spec.callbacks) this._spec.callbacks = {}; + this._spec.callbacks ??= {}; this._spec.callbacks[name] = callback; return this; } diff --git a/packages/openapi-v3/src/controller-spec.ts b/packages/openapi-v3/src/controller-spec.ts index 424c8be32056..806871524631 100644 --- a/packages/openapi-v3/src/controller-spec.ts +++ b/packages/openapi-v3/src/controller-spec.ts @@ -355,21 +355,17 @@ function resolveControllerSpec(constructor: Function): ControllerSpec { operationSpec['x-operation-name'] = op; operationSpec['x-controller-name'] = - operationSpec['x-controller-name'] || constructor.name; - - if (operationSpec.operationId == null) { - // Build the operationId as `.` - // Please note API explorer (https://github.com/swagger-api/swagger-js/) - // will normalize it as `_` - operationSpec.operationId = - operationSpec['x-controller-name'] + - '.' + - operationSpec['x-operation-name']; - } + operationSpec['x-controller-name'] ?? constructor.name; - if (!spec.paths[path]) { - spec.paths[path] = {}; - } + // Build the operationId as `.` + // Please note API explorer (https://github.com/swagger-api/swagger-js/) + // will normalize it as `_` + operationSpec.operationId ??= + operationSpec['x-controller-name'] + + '.' + + operationSpec['x-operation-name']; + + spec.paths[path] ??= {}; if (spec.paths[path][verb]) { // Operations from subclasses override those from the base diff --git a/packages/openapi-v3/src/decorators/parameter.decorator.ts b/packages/openapi-v3/src/decorators/parameter.decorator.ts index fe9c7505a6f0..8f63ba91c128 100644 --- a/packages/openapi-v3/src/decorators/parameter.decorator.ts +++ b/packages/openapi-v3/src/decorators/parameter.decorator.ts @@ -41,7 +41,7 @@ export function param(paramSpec: ParameterObject) { paramSpec = {...paramSpec}; // Get the design time method parameter metadata const methodSig = MetadataInspector.getDesignTypeForMethod(target, member); - const paramTypes = methodSig?.parameterTypes || []; + const paramTypes = methodSig?.parameterTypes ?? []; // Map design-time parameter type to the OpenAPI param type diff --git a/packages/openapi-v3/src/decorators/request-body.decorator.ts b/packages/openapi-v3/src/decorators/request-body.decorator.ts index 8a4345df6872..76972391b636 100644 --- a/packages/openapi-v3/src/decorators/request-body.decorator.ts +++ b/packages/openapi-v3/src/decorators/request-body.decorator.ts @@ -93,7 +93,7 @@ export function requestBody(requestBodySpec?: Partial) { // Get the design time method parameter metadata const methodSig = MetadataInspector.getDesignTypeForMethod(target, member); - const paramTypes = methodSig?.parameterTypes || []; + const paramTypes = methodSig?.parameterTypes ?? []; const paramType = paramTypes[index]; const schema = resolveSchema(paramType); @@ -101,9 +101,7 @@ export function requestBody(requestBodySpec?: Partial) { if (debug.enabled) debug(' inferred schema: %s', inspect(schema, {depth: null})); requestBodySpec.content = _.mapValues(requestBodySpec.content, c => { - if (!c.schema) { - c.schema = schema; - } + c.schema ??= schema; return c; }); diff --git a/packages/repository-json-schema/src/build-schema.ts b/packages/repository-json-schema/src/build-schema.ts index 74f143369ea2..e0f97a1298f2 100644 --- a/packages/repository-json-schema/src/build-schema.ts +++ b/packages/repository-json-schema/src/build-schema.ts @@ -344,7 +344,7 @@ function buildSchemaTitle( options: JsonSchemaOptions, ) { if (options.title) return options.title; - const title = meta.title || ctor.name; + const title = meta.title ?? ctor.name; return title + getTitleSuffix(options); } @@ -489,7 +489,7 @@ export function modelToJsonSchema( indexInfo = {[p]: index}; } if (indexInfo && Object.keys(indexInfo).length) { - if (result.description === undefined) result.description = ''; + result.description ??= ''; if (result.description.includes('indexInfo')) { const indexInfoMatched = result.description.match(/\{"indexInfo".*$/s); if (indexInfoMatched) { diff --git a/packages/repository-tests/src/crud/transactions.suite.ts b/packages/repository-tests/src/crud/transactions.suite.ts index e82187c90128..cbe273e4b366 100644 --- a/packages/repository-tests/src/crud/transactions.suite.ts +++ b/packages/repository-tests/src/crud/transactions.suite.ts @@ -89,7 +89,7 @@ export function transactionSuite( afterEach(async () => { if (ds2) { - ds2.disconnect(); + await ds2.disconnect(); (ds2 as unknown) = undefined; } }); diff --git a/packages/repository/src/__tests__/unit/repositories/belongs-to-repository-factory.unit.ts b/packages/repository/src/__tests__/unit/repositories/belongs-to-repository-factory.unit.ts index 3846d899cc77..8c220545ec1f 100644 --- a/packages/repository/src/__tests__/unit/repositories/belongs-to-repository-factory.unit.ts +++ b/packages/repository/src/__tests__/unit/repositories/belongs-to-repository-factory.unit.ts @@ -144,7 +144,7 @@ describe('createBelongsToAccessor', () => { } function givenStubbedCompanyRepo() { - customerRepo = createStubInstance(CompanyRepository); + companyRepo = createStubInstance(CompanyRepository); } function givenBelongsToDefinition( diff --git a/packages/repository/src/mixins/repository.mixin.ts b/packages/repository/src/mixins/repository.mixin.ts index a2411ab002b2..22780cef0f69 100644 --- a/packages/repository/src/mixins/repository.mixin.ts +++ b/packages/repository/src/mixins/repository.mixin.ts @@ -131,12 +131,12 @@ export function RepositoryMixin>( const options = toOptions(nameOrOptions); // We have an instance of if (dataSource instanceof juggler.DataSource) { - const name = options.name || dataSource.name; + const name = options.name ?? dataSource.name; const namespace = options.namespace ?? RepositoryBindings.DATASOURCES; const key = `${namespace}.${name}`; return this.bind(key).to(dataSource).tag(RepositoryTags.DATASOURCE); } else if (typeof dataSource === 'function') { - options.name = options.name || dataSource.dataSourceName; + options.name = options.name ?? dataSource.dataSourceName; const binding = createBindingFromClass(dataSource, { namespace: RepositoryBindings.DATASOURCES, type: RepositoryTags.DATASOURCE, diff --git a/packages/repository/src/model.ts b/packages/repository/src/model.ts index 5424d2d2a6f1..529be2041aac 100644 --- a/packages/repository/src/model.ts +++ b/packages/repository/src/model.ts @@ -352,7 +352,7 @@ export class Model { }; const json: AnyObject = {}; - const hiddenProperties: string[] = def.settings.hiddenProperties || []; + const hiddenProperties: string[] = def.settings.hiddenProperties ?? []; for (const p in def.properties) { if (p in this && !hiddenProperties.includes(p)) { copyPropertyAsJson(p); @@ -383,7 +383,7 @@ export class Model { const obj: AnyObject = {}; if (options?.ignoreUnknownProperties === false) { - const hiddenProperties: string[] = def?.settings.hiddenProperties || []; + const hiddenProperties: string[] = def?.settings.hiddenProperties ?? []; for (const p in this) { if (!hiddenProperties.includes(p)) { const val = (this as AnyObject)[p]; diff --git a/packages/repository/src/types/buffer.ts b/packages/repository/src/types/buffer.ts index 3ebe4e74d8a7..72bba2ee60fb 100644 --- a/packages/repository/src/types/buffer.ts +++ b/packages/repository/src/types/buffer.ts @@ -36,7 +36,7 @@ export class BufferType implements Type { if (Buffer.isBuffer(value)) return value as Buffer; if (typeof value === 'string') { options = options ?? {}; - const encoding = options.encoding || 'utf-8'; + const encoding = options.encoding ?? 'utf-8'; return Buffer.from(value, encoding); } else if (Array.isArray(value)) { return Buffer.from(value); @@ -47,7 +47,7 @@ export class BufferType implements Type { serialize(value: Buffer | null | undefined, options?: Options) { if (value == null) return value; - const encoding = options?.encoding || 'base64'; + const encoding = options?.encoding ?? 'base64'; return value.toString(encoding); } } diff --git a/packages/rest/src/__tests__/acceptance/caching-interceptor/caching-interceptor.ts b/packages/rest/src/__tests__/acceptance/caching-interceptor/caching-interceptor.ts index 8b83ae646d11..b4100718522d 100644 --- a/packages/rest/src/__tests__/acceptance/caching-interceptor/caching-interceptor.ts +++ b/packages/rest/src/__tests__/acceptance/caching-interceptor/caching-interceptor.ts @@ -70,7 +70,7 @@ export async function cache( const req = await invocationCtx.get(RestBindings.Http.REQUEST, { optional: true, }); - if (!req || req.method.toLowerCase() !== 'get') { + if (req?.method.toLowerCase() !== 'get') { // The method is not invoked by an http request, no caching return next(); } diff --git a/packages/rest/src/rest.application.ts b/packages/rest/src/rest.application.ts index 7041addd9c1e..970d203f3e13 100644 --- a/packages/rest/src/rest.application.ts +++ b/packages/rest/src/rest.application.ts @@ -285,24 +285,6 @@ export class RestApplication extends Application implements HttpServerLike { */ route(route: RouteEntry): Binding; - /** - * Register a new route. - * - * @example - * ```ts - * function greet(name: string) { - * return `hello ${name}`; - * } - * app.route('get', '/', operationSpec, greet); - * ``` - */ - route( - verb: string, - path: string, - spec: OperationObject, - handler: Function, - ): Binding; - route( routeOrVerb: RouteEntry | string, path?: string, diff --git a/packages/rest/src/rest.server.ts b/packages/rest/src/rest.server.ts index 0d398e2fdaa5..38d5ef235cff 100644 --- a/packages/rest/src/rest.server.ts +++ b/packages/rest/src/rest.server.ts @@ -343,9 +343,9 @@ export class RestServer this.addOpenApiSpecEndpoint(p, mapping[p], router); } const explorerPaths = ['/swagger-ui', '/explorer']; - router.get(explorerPaths, (req, res, next) => - this._redirectToSwaggerUI(req, res, next), - ); + router.get(explorerPaths, (req, res, next) => { + this._redirectToSwaggerUI(req, res, next).catch(next); + }); this.expressMiddleware('middleware.apiSpec.defaults', router, { group: RestMiddlewareGroups.API_SPEC, upstreamGroups: RestMiddlewareGroups.CORS, @@ -376,7 +376,9 @@ export class RestServer ); } const newRouter = express.Router(); - newRouter.get(path, (req, res) => this._serveOpenApiSpec(req, res, form)); + newRouter.get(path, (req, res, next) => { + this._serveOpenApiSpec(req, res, form).catch(next); + }); this.expressMiddleware( () => newRouter, {}, @@ -387,7 +389,9 @@ export class RestServer }, ); } else { - router.get(path, (req, res) => this._serveOpenApiSpec(req, res, form)); + router.get(path, (req, res, next) => { + this._serveOpenApiSpec(req, res, form).catch(next); + }); } } @@ -1227,20 +1231,14 @@ function resolveRestServerConfig( ); // Can't check falsiness, 0 is a valid port. - if (result.port == null) { - result.port = 3000; - } + result.port ??= 3000; - if (result.host == null) { - // Set it to '' so that the http server will listen on all interfaces - result.host = undefined; - } + // Set it to '' so that the http server will listen on all interfaces + result.host ??= undefined; - if (!result.openApiSpec.endpointMapping) { - // mapping may be mutated by addOpenApiSpecEndpoint, be sure that doesn't - // pollute the default mapping configuration - result.openApiSpec.endpointMapping = cloneDeep(OPENAPI_SPEC_MAPPING); - } + // mapping may be mutated by addOpenApiSpecEndpoint, be sure that doesn't + // pollute the default mapping configuration + result.openApiSpec.endpointMapping ??= cloneDeep(OPENAPI_SPEC_MAPPING); result.apiExplorer = normalizeApiExplorerConfig(config.apiExplorer); diff --git a/packages/rest/src/router/controller-route.ts b/packages/rest/src/router/controller-route.ts index 33e204f9fffd..3b7fd02b6dad 100644 --- a/packages/rest/src/router/controller-route.ts +++ b/packages/rest/src/router/controller-route.ts @@ -67,7 +67,7 @@ export class ControllerRoute extends BaseRoute { controllerFactory?: ControllerFactory, methodName?: string, ) { - const controllerName = spec['x-controller-name'] || controllerCtor.name; + const controllerName = spec['x-controller-name'] ?? controllerCtor.name; methodName = methodName ?? spec['x-operation-name']; if (!methodName) { @@ -96,7 +96,7 @@ export class ControllerRoute extends BaseRoute { this._controllerFactory = controllerFactory ?? createControllerFactoryForClass(controllerCtor); this._controllerCtor = controllerCtor; - this._controllerName = controllerName || controllerCtor.name; + this._controllerName = controllerName ?? controllerCtor.name; this._methodName = methodName; } @@ -170,9 +170,7 @@ export function createControllerFactoryForClass( let inst = await ctx.get(`controllers.${controllerCtor.name}`, { optional: true, }); - if (inst === undefined) { - inst = await instantiateClass(controllerCtor, ctx); - } + inst ??= await instantiateClass(controllerCtor, ctx); return inst; }; } diff --git a/packages/rest/src/router/router-spec.ts b/packages/rest/src/router/router-spec.ts index 1de5b781ca73..3621d01371af 100644 --- a/packages/rest/src/router/router-spec.ts +++ b/packages/rest/src/router/router-spec.ts @@ -9,9 +9,9 @@ export type RouterSpec = Pick; export function assignRouterSpec(target: RouterSpec, additions: RouterSpec) { if (additions.components) { - if (!target.components) target.components = {}; + target.components ??= {}; for (const key in additions.components) { - if (!target.components[key]) target.components[key] = {}; + target.components[key] ??= {}; Object.assign(target.components[key], additions.components[key]); } } @@ -26,7 +26,7 @@ export function assignRouterSpec(target: RouterSpec, additions: RouterSpec) { } if (additions.tags && additions.tags.length > 0) { - if (!target.tags) target.tags = []; + target.tags ??= []; for (const tag of additions.tags) { // tags defined earlier take precedence if (target.tags.some(t => t.name === tag.name)) continue; diff --git a/packages/rest/src/router/routing-table.ts b/packages/rest/src/router/routing-table.ts index bc0677559a9e..3dc2313eab54 100644 --- a/packages/rest/src/router/routing-table.ts +++ b/packages/rest/src/router/routing-table.ts @@ -83,9 +83,7 @@ export class RoutingTable { for (const route of this._router.list()) { if (route.spec['x-visibility'] === 'undocumented') continue; - if (!paths[route.path]) { - paths[route.path] = {}; - } + paths[route.path] ??= {}; paths[route.path][route.verb] = route.spec; } diff --git a/packages/testlab/package.json b/packages/testlab/package.json index faf1d2d6d44b..a6ce44bd31a8 100644 --- a/packages/testlab/package.json +++ b/packages/testlab/package.json @@ -29,7 +29,6 @@ "README.md", "dist", "fixtures", - "should-as-function.d.ts", "src", "!*/__tests__" ], diff --git a/packages/testlab/src/expect.ts b/packages/testlab/src/expect.ts index be7f3f175573..a74adb9f893c 100644 --- a/packages/testlab/src/expect.ts +++ b/packages/testlab/src/expect.ts @@ -3,8 +3,7 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -// eslint-disable-next-line @typescript-eslint/triple-slash-reference -/// +import {Internal} from './should-as-function'; const shouldAsFunction: Internal = require('should/as-function'); diff --git a/packages/testlab/src/http-server-config.ts b/packages/testlab/src/http-server-config.ts index 45f5f02c456a..793be72d718a 100644 --- a/packages/testlab/src/http-server-config.ts +++ b/packages/testlab/src/http-server-config.ts @@ -56,8 +56,8 @@ export function givenHttpServerConfig( if (isHttpsConfig(customConfig)) { const config: T = {...customConfig}; - if (config.host == null) config.host = defaults.host; - if (config.port == null) config.port = defaults.port; + config.host ??= defaults.host; + config.port ??= defaults.port; setupTlsConfig(config as HttpsOptions); assertHostPort(config); return config; @@ -65,8 +65,8 @@ export function givenHttpServerConfig( assertHttpConfig(customConfig); const config: T = {...customConfig}; - if (config.host == null) config.host = defaults.host; - if (config.port == null) config.port = defaults.port; + config.host ??= defaults.host; + config.port ??= defaults.port; assertHostPort(config); return config; } diff --git a/packages/testlab/should-as-function.d.ts b/packages/testlab/src/should-as-function.ts similarity index 90% rename from packages/testlab/should-as-function.d.ts rename to packages/testlab/src/should-as-function.ts index 6fe6886c2fc7..059e2d4cdf1e 100644 --- a/packages/testlab/should-as-function.d.ts +++ b/packages/testlab/src/should-as-function.ts @@ -26,8 +26,10 @@ interface Object { /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/unified-signatures */ +/* These are should.js API names, they are not ours to rename. */ +/* eslint-disable @typescript-eslint/naming-convention */ -interface ShouldAssertion { +export interface ShouldAssertion { // basic grammar a: ShouldAssertion; an: ShouldAssertion; @@ -141,9 +143,9 @@ interface ShouldAssertion { //stubs alwaysCalledOn(thisTarget: any): ShouldAssertion; - alwaysCalledWith(...arguments: any[]): ShouldAssertion; - alwaysCalledWithExactly(...arguments: any[]): ShouldAssertion; - alwaysCalledWithMatch(...arguments: any[]): ShouldAssertion; + alwaysCalledWith(...args: any[]): ShouldAssertion; + alwaysCalledWithExactly(...args: any[]): ShouldAssertion; + alwaysCalledWithMatch(...args: any[]): ShouldAssertion; alwaysCalledWithNew(): ShouldAssertion; alwaysThrew(exception?: any): ShouldAssertion; callCount(count: number): ShouldAssertion; @@ -152,12 +154,12 @@ interface ShouldAssertion { calledOnce(): ShouldAssertion; calledTwice(): ShouldAssertion; calledThrice(): ShouldAssertion; - calledWith(...arguments: any[]): ShouldAssertion; - calledWithExactly(...arguments: any[]): ShouldAssertion; - calledWithMatch(...arguments: any[]): ShouldAssertion; + calledWith(...args: any[]): ShouldAssertion; + calledWithExactly(...args: any[]): ShouldAssertion; + calledWithMatch(...args: any[]): ShouldAssertion; calledWithNew(): ShouldAssertion; - neverCalledWith(...arguments: any[]): ShouldAssertion; - neverCalledWithMatch(...arguments: any[]): ShouldAssertion; + neverCalledWith(...args: any[]): ShouldAssertion; + neverCalledWithMatch(...args: any[]): ShouldAssertion; threw(exception?: any): ShouldAssertion; // aliases @@ -178,20 +180,20 @@ interface ShouldAssertion { undefined(): ShouldAssertion; } -interface ShouldInternal { +export interface ShouldInternal { // should.js's extras exist(actual: any, msg?: string): void; exists(actual: any, msg?: string): void; not: ShouldInternal; } -interface Assertion { +export interface Assertion { add(name: string, func: Function): void; addChain(name: string, onCall?: Function): void; alias(from: string, to: string): void; } -interface Internal extends ShouldInternal { +export interface Internal extends ShouldInternal { (obj: any): ShouldAssertion; // Simple utility function for a bit more easier should assertion extension @@ -213,11 +215,6 @@ interface Internal extends ShouldInternal { inspect(value: any, obj: any): any; } -declare var should: Internal; -interface Window { - Should: Internal; -} - -declare module 'should' { - export = should; -} +/* The global `should` variable and the `Window.Should` property are not +declared here - this module wraps `should/as-function`, which deliberately +leaves the global scope and `Object.prototype` untouched. */ From 8915bc4222a94f2b6693175990b1ff7bf03b6300 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Tue, 15 Sep 2026 19:10:42 -0700 Subject: [PATCH 2/2] fix(mock-oauth2-provider): resolve CodeQL prototype pollution and open redirect Registered apps and issued tokens were held in plain objects keyed by values taken from the request, so a `__proto__` key reached `Object.prototype`. They are `Map`s now, which also drops the `[key: string]: any` index signature from the `App` interface (CodeQL js/prototype-polluting-assignment). `redirect_uri` is validated before redirecting and the callback url is built from the parsed `URL` rather than by concatenating the request value. A real authorization server matches `redirect_uri` against the callback urls registered for the client; this provider only ever serves test applications running on the same machine, so it accepts loopback hosts (CodeQL js/server-side-unvalidated-url-redirection). Both findings predate the TypeScript 7 upgrade - they were reported on this pull request only because wrapping the handlers re-indented the lines. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Raymond Feng --- .../src/mock-oauth2-social-app.ts | 125 ++++++++++++------ 1 file changed, 86 insertions(+), 39 deletions(-) diff --git a/fixtures/mock-oauth2-provider/src/mock-oauth2-social-app.ts b/fixtures/mock-oauth2-provider/src/mock-oauth2-social-app.ts index 119a62893272..82872eaa3fca 100644 --- a/fixtures/mock-oauth2-provider/src/mock-oauth2-social-app.ts +++ b/fixtures/mock-oauth2-provider/src/mock-oauth2-social-app.ts @@ -37,6 +37,33 @@ function asyncHandler( }; } +/** + * Hosts this mock provider is willing to redirect back to. + * + * A real authorization server matches `redirect_uri` against the callback urls + * registered for the client. This provider only ever serves test applications + * running on the same machine, so it accepts loopback hosts instead of + * redirecting wherever the request asks. + */ +const ALLOWED_REDIRECT_HOSTS = ['localhost', '127.0.0.1', '::1']; + +/** + * Parse `redirect_uri` and return it only when it points at a local test app. + * @param redirectUri - The `redirect_uri` taken from the request + */ +function parseRedirectUri(redirectUri: string) { + let url: URL; + try { + url = new URL(redirectUri); + } catch { + return undefined; + } + const isHttp = url.protocol === 'http:' || url.protocol === 'https:'; + // `URL` keeps IPv6 hosts in brackets + const host = url.hostname.replace(/^\[|]$/g, ''); + return isHttp && ALLOWED_REDIRECT_HOSTS.includes(host) ? url : undefined; +} + const app = express(); let server: Server; @@ -49,33 +76,37 @@ const urlencodedParser = bodyParser.urlencoded({extended: false}); /** * data structure for an app registration, also holds issued tokens for an app + * + * The maps are keyed by values taken from the request, so they are `Map`s + * rather than plain objects - a `__proto__` key would otherwise reach + * `Object.prototype`. */ interface App { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; client_secret: string; - tokens: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - }; + /** + * access tokens issued for this app, keyed by the access code handed to the + * client + */ + tokens: Map; + /** + * signing key of each issued token, keyed by the token's `jti` claim + */ + issuedTokens: Map; } /** - * list of registered apps for this oauth2 provider identified by their client ids + * apps registered with this provider, keyed by their client ids */ -interface AppRegistry { - [clientId: string]: App; -} - -/** - * apps registered with this provider - * format: - * { clientId: {client_secret, list_of_tokens} } - */ -const registeredApps: AppRegistry = { - '1111': {client_secret: 'app1_secret', tokens: {}}, - '2222': {client_secret: 'app2_secret', tokens: {}}, -}; +const registeredApps = new Map([ + [ + '1111', + {client_secret: 'app1_secret', tokens: new Map(), issuedTokens: new Map()}, + ], + [ + '2222', + {client_secret: 'app2_secret', tokens: new Map(), issuedTokens: new Map()}, + ], +]); /** * user registry @@ -169,10 +200,13 @@ async function verifyToken(token: string) { if (unwrappedJwt == null) throw new Error('invalid token'); const tokenId = (unwrappedJwt.payload as jwt.JwtPayload).jti; if (!tokenId) throw new Error('invalid token'); - const registeredApp: App = - registeredApps[(unwrappedJwt.payload as jwt.JwtPayload).client_id]; + const registeredApp = registeredApps.get( + (unwrappedJwt.payload as jwt.JwtPayload).client_id, + ); if (registeredApp) { - const result = jwt.verify(token, registeredApp[tokenId].signingKey); + const issuedToken = registeredApp.issuedTokens.get(String(tokenId)); + if (!issuedToken) throw new Error('invalid token'); + const result = jwt.verify(token, issuedToken.signingKey); if (result) { return result as Record; } else { @@ -203,7 +237,7 @@ app.get('/oauth/dialog', function (req, res) { res.status(400).send({error: 'missing client_id'}); return; } - if (registeredApps[req.query.client_id as string]) { + if (registeredApps.has(req.query.client_id as string)) { let params = '?client_id=' + req.query.client_id + @@ -272,7 +306,11 @@ app.post( ); if (user) { // get registered app - const registeredApp = registeredApps[req.body.client_id]; + const registeredApp = registeredApps.get(req.body.client_id); + if (!registeredApp) { + res.status(401).send({error: 'invalid client_id'}); + return; + } // generate access code const authCode = Math.floor(Math.random() * Math.floor(1000)); // create a token for the access code @@ -283,12 +321,20 @@ app.post( req.body.client_id, ); // store generated token - registeredApp.tokens[authCode] = {token: result.token}; - registeredApp[result.id] = {signingKey: user.signingKey, code: authCode}; + registeredApp.tokens.set(String(authCode), {token: result.token}); + registeredApp.issuedTokens.set(String(result.id), { + signingKey: user.signingKey, + code: authCode, + }); // redirect to call back url with the access code - let params = '?client_id=' + req.body.client_id; - params = params + '&&code=' + authCode; - res.redirect(req.body.redirect_uri + params); + const callbackUrl = parseRedirectUri(req.body.redirect_uri); + if (!callbackUrl) { + res.status(400).send({error: 'invalid redirect_uri'}); + return; + } + callbackUrl.searchParams.set('client_id', req.body.client_id); + callbackUrl.searchParams.set('code', String(authCode)); + res.redirect(callbackUrl.href); } else { res.sendStatus(401); } @@ -306,12 +352,13 @@ app.post('/oauth/token', urlencodedParser, function (req, res) { res.status(400).send({error: 'missing client_id'}); return; } - if (registeredApps[req.body.client_id]) { + const registeredApp = registeredApps.get(req.body.client_id); + if (registeredApp) { //&& apps[req.query.client_id].client_secret === req.query.client_secret - const oauthStates = registeredApps[req.body.client_id].tokens; - if (oauthStates[req.body.code]) { + const oauthState = registeredApp.tokens.get(String(req.body.code)); + if (oauthState) { res.setHeader('Content-Type', 'application/json'); - res.send({access_token: oauthStates[req.body.code].token}); + res.send({access_token: oauthState.token}); } else { res.status(401).send({error: 'invalid code'}); } @@ -332,13 +379,13 @@ app.get('/oauth/token', function (req, res) { res.status(400).send({error: 'missing client_id'}); return; } - if (registeredApps[clientId]) { + const registeredApp = registeredApps.get(clientId); + if (registeredApp) { //&& apps[req.query.client_id].client_secret === req.query.client_secret - const oauthStates = registeredApps[clientId].tokens; - const code = req.query.code as string; - if (oauthStates[code]) { + const oauthState = registeredApp.tokens.get(req.query.code as string); + if (oauthState) { res.setHeader('Content-Type', 'application/json'); - res.send({access_token: oauthStates[code].token}); + res.send({access_token: oauthState.token}); } else { res.status(401).send({error: 'invalid code'}); }