From c7df471c0e636a989417e22dead36dbb9f800265 Mon Sep 17 00:00:00 2001 From: "MK (fengmk2)" Date: Mon, 6 Oct 2025 14:48:45 +0800 Subject: [PATCH] refactor: use tegg and egg v4 beta (#836) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🦀 for https://github.com/eggjs/egg/issues/3644 --- .oxlintrc.json | 5 ++- .prettierrc | 1 + DEVELOPER.md | 2 +- INTEGRATE.md | 2 +- app.ts | 1 + app/common/typing.ts | 2 -- app/port/UserRoleManager.ts | 16 +++++---- .../controller/ChangesStreamController.ts | 2 +- app/port/controller/ProxyCacheController.ts | 2 +- app/port/controller/RegistryController.ts | 2 +- app/port/controller/ScopeController.ts | 2 +- app/port/controller/TokenController.ts | 2 +- app/port/controller/UserController.ts | 2 +- .../package/SavePackageVersionController.ts | 2 +- .../package/SearchPackageController.ts | 2 +- .../package/UpdatePackageController.ts | 2 +- app/port/typebox.ts | 4 +-- app/port/webauth/WebauthController.ts | 3 +- config/config.default.ts | 7 ++-- config/config.unittest.ts | 2 +- config/plugin.ts | 2 +- package.json | 28 ++++++++------- .../package/UpdatePackageController.test.ts | 36 +++++++++++++++++++ tsconfig.json | 6 ++-- 24 files changed, 91 insertions(+), 44 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 19549122..9562e9e9 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -13,7 +13,10 @@ "no-console": "warn", "import/no-anonymous-default-export": "error", "no-unassigned-import": "allow", - "new-cap": "allow" + "new-cap": "allow", + "class-methods-use-this": "allow", + "import/no-named-export": "allow", + "unicorn/no-array-sort": "allow" }, "ignorePatterns": ["index.d.ts"] } diff --git a/.prettierrc b/.prettierrc index 43aee1a4..54775e6e 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,5 +2,6 @@ "singleQuote": true, "trailingComma": "es5", "tabWidth": 2, + "printWidth": 120, "arrowParens": "avoid" } diff --git a/DEVELOPER.md b/DEVELOPER.md index 4bd2e9b4..6aa6432c 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -236,7 +236,7 @@ private async getPackageEntity(scope: string, name: string) { #### 1、请求参数校验 -使用 [egg-typebox-validate](https://github.com/xiekw2010/egg-typebox-validate) 来做请求参数校验,只需要定义一次参数类型和规则,就能同时拥有参数校验和类型定义。 +使用 [@eggjs/typebox-validate](https://github.com/eggjs/egg/tree/next/plugins/typebox-validate) 来做请求参数校验,只需要定义一次参数类型和规则,就能同时拥有参数校验和类型定义。 详细使用方式可以参考 [PR#12](https://github.com/cnpm/cnpmcore/pull/12)。 使用方式请直接参考 `app/port/typebox.ts` 代码。 diff --git a/INTEGRATE.md b/INTEGRATE.md index 49933dae..dd40ec3f 100644 --- a/INTEGRATE.md +++ b/INTEGRATE.md @@ -73,7 +73,7 @@ }, typeboxValidate: { enable: true, - package: 'egg-typebox-validate', + package: '@eggjs/typebox-validate', }, } ``` diff --git a/app.ts b/app.ts index b3129e93..3929d4b4 100644 --- a/app.ts +++ b/app.ts @@ -1,6 +1,7 @@ import path from 'node:path'; import { readFile } from 'node:fs/promises'; import type { Application, ILifecycleBoot } from 'egg'; + import { ChangesStreamService } from './app/core/service/ChangesStreamService.js'; declare module 'egg' { diff --git a/app/common/typing.ts b/app/common/typing.ts index 031b5b4f..746afdd2 100644 --- a/app/common/typing.ts +++ b/app/common/typing.ts @@ -77,8 +77,6 @@ export interface AuthClient { } declare module 'egg' { - // oxlint-disable-next-line prefer-ts-expect-error ban-ts-comment - // @ts-ignore interface EggAppConfig { cnpmcore: CnpmcoreConfig; } diff --git a/app/port/UserRoleManager.ts b/app/port/UserRoleManager.ts index 2dc772c3..b5f8622e 100644 --- a/app/port/UserRoleManager.ts +++ b/app/port/UserRoleManager.ts @@ -140,16 +140,20 @@ export class UserRoleManager { `Read-only Token "${token.tokenMark}" can't publish` ); } + const userAgent: string = ctx.get('user-agent'); // only support npm >= 7.0.0 allow publish action // user-agent: "npm/6.14.12 node/v10.24.1 darwin x64" - const m = /\bnpm\/(\d{1,5})\./.exec(ctx.get('user-agent')); - if (!m) { + // pnpm: "pnpm/10.17.0 npm/? node/v20.19.5 darwin arm64" + const isPnpm = userAgent.startsWith('pnpm/'); + const m = /\bnpm\/(\d{1,5})\./.exec(userAgent); + if (m) { + const major = Number.parseInt(m[1]); + if (major < 7) { + throw new ForbiddenError('Only allow npm@>=7.0.0 client to access'); + } + } else if (!isPnpm) { throw new ForbiddenError('Only allow npm client to access'); } - const major = Number.parseInt(m[1]); - if (major < 7) { - throw new ForbiddenError('Only allow npm@>=7.0.0 client to access'); - } } if (role === 'setting') { if (token.isReadonly) { diff --git a/app/port/controller/ChangesStreamController.ts b/app/port/controller/ChangesStreamController.ts index 22c14511..ce0c1671 100644 --- a/app/port/controller/ChangesStreamController.ts +++ b/app/port/controller/ChangesStreamController.ts @@ -7,7 +7,7 @@ import { HTTPQuery, Inject, } from '@eggjs/tegg'; -import { Type } from 'egg-typebox-validate/typebox'; +import { Type } from '@eggjs/typebox-validate/typebox'; import { AbstractController } from './AbstractController.js'; import type { ChangeRepository } from '../../repository/ChangeRepository.js'; diff --git a/app/port/controller/ProxyCacheController.ts b/app/port/controller/ProxyCacheController.ts index bf874839..b23f0b13 100644 --- a/app/port/controller/ProxyCacheController.ts +++ b/app/port/controller/ProxyCacheController.ts @@ -17,7 +17,7 @@ import { import { AbstractController } from './AbstractController.js'; import type { ProxyCacheRepository } from '../../repository/ProxyCacheRepository.js'; -import type { Static } from 'egg-typebox-validate/typebox'; +import type { Static } from '@eggjs/typebox-validate/typebox'; import type { QueryPageOptions } from '../typebox.js'; import { FULLNAME_REG_STRING } from '../../common/PackageUtil.js'; import type { ProxyCacheService } from '../../core/service/ProxyCacheService.js'; diff --git a/app/port/controller/RegistryController.ts b/app/port/controller/RegistryController.ts index 18533732..7f03370c 100644 --- a/app/port/controller/RegistryController.ts +++ b/app/port/controller/RegistryController.ts @@ -11,7 +11,7 @@ import { Middleware, } from '@eggjs/tegg'; import { NotFoundError } from 'egg-errors'; -import type { Static } from 'egg-typebox-validate/typebox'; +import type { Static } from '@eggjs/typebox-validate/typebox'; import { AbstractController } from './AbstractController.js'; import type { diff --git a/app/port/controller/ScopeController.ts b/app/port/controller/ScopeController.ts index 16bc5f14..4c6aafa2 100644 --- a/app/port/controller/ScopeController.ts +++ b/app/port/controller/ScopeController.ts @@ -10,7 +10,7 @@ import { Middleware, } from '@eggjs/tegg'; import { E400 } from 'egg-errors'; -import type { Static } from 'egg-typebox-validate/typebox'; +import type { Static } from '@eggjs/typebox-validate/typebox'; import { AbstractController } from './AbstractController.js'; import { AdminAccess } from '../middleware/AdminAccess.js'; diff --git a/app/port/controller/TokenController.ts b/app/port/controller/TokenController.ts index 3c261d64..15e2d23c 100644 --- a/app/port/controller/TokenController.ts +++ b/app/port/controller/TokenController.ts @@ -9,7 +9,7 @@ import { HTTPParam, Inject, } from '@eggjs/tegg'; -import { Type, type Static } from 'egg-typebox-validate/typebox'; +import { Type, type Static } from '@eggjs/typebox-validate/typebox'; import type { AuthAdapter } from '../../infra/AuthAdapter.js'; import { AbstractController } from './AbstractController.js'; diff --git a/app/port/controller/UserController.ts b/app/port/controller/UserController.ts index 568e67b5..69421fa2 100644 --- a/app/port/controller/UserController.ts +++ b/app/port/controller/UserController.ts @@ -13,7 +13,7 @@ import { UnauthorizedError, UnprocessableEntityError, } from 'egg-errors'; -import { Type, type Static } from 'egg-typebox-validate/typebox'; +import { Type, type Static } from '@eggjs/typebox-validate/typebox'; import { AbstractController } from './AbstractController.js'; import { LoginResultCode } from '../../common/enum/User.js'; diff --git a/app/port/controller/package/SavePackageVersionController.ts b/app/port/controller/package/SavePackageVersionController.ts index 5e625cfa..e215cbbc 100644 --- a/app/port/controller/package/SavePackageVersionController.ts +++ b/app/port/controller/package/SavePackageVersionController.ts @@ -17,7 +17,7 @@ import { } from '@eggjs/tegg'; import { checkData, fromData } from 'ssri'; import validateNpmPackageName from 'validate-npm-package-name'; -import { Type, type Static } from 'egg-typebox-validate/typebox'; +import { Type, type Static } from '@eggjs/typebox-validate/typebox'; import { AbstractController } from '../AbstractController.js'; import { diff --git a/app/port/controller/package/SearchPackageController.ts b/app/port/controller/package/SearchPackageController.ts index d60baec7..4734a750 100644 --- a/app/port/controller/package/SearchPackageController.ts +++ b/app/port/controller/package/SearchPackageController.ts @@ -9,7 +9,7 @@ import { Inject, Middleware, } from '@eggjs/tegg'; -import type { Static } from 'egg-typebox-validate/typebox'; +import type { Static } from '@eggjs/typebox-validate/typebox'; import { E451 } from 'egg-errors'; import { AbstractController } from '../AbstractController.js'; diff --git a/app/port/controller/package/UpdatePackageController.ts b/app/port/controller/package/UpdatePackageController.ts index 440a4cca..9bb4dc90 100644 --- a/app/port/controller/package/UpdatePackageController.ts +++ b/app/port/controller/package/UpdatePackageController.ts @@ -9,7 +9,7 @@ import { HTTPParam, Inject, } from '@eggjs/tegg'; -import { Type, type Static } from 'egg-typebox-validate/typebox'; +import { Type, type Static } from '@eggjs/typebox-validate/typebox'; import { AbstractController } from '../AbstractController.js'; import { FULLNAME_REG_STRING } from '../../../common/PackageUtil.js'; diff --git a/app/port/typebox.ts b/app/port/typebox.ts index a6a3820e..a0bc07ce 100644 --- a/app/port/typebox.ts +++ b/app/port/typebox.ts @@ -1,8 +1,8 @@ -import { Type, type Static } from 'egg-typebox-validate/typebox'; +import { Type, type Static } from '@eggjs/typebox-validate/typebox'; import semver from 'semver'; import npa from 'npm-package-arg'; import { uniq } from 'lodash-es'; -import type { Ajv } from 'egg-typebox-validate'; +import type { Ajv } from '@eggjs/typebox-validate'; import { RegistryType } from '../common/enum/Registry.js'; import { HookType } from '../common/enum/Hook.js'; diff --git a/app/port/webauth/WebauthController.ts b/app/port/webauth/WebauthController.ts index a4fe4387..2a36e932 100644 --- a/app/port/webauth/WebauthController.ts +++ b/app/port/webauth/WebauthController.ts @@ -10,7 +10,8 @@ import { Inject, } from '@eggjs/tegg'; import type { EggAppConfig, EggLogger } from 'egg'; -import { Type, type Static } from 'egg-typebox-validate/typebox'; +import '@eggjs/typebox-validate'; +import { Type, type Static } from '@eggjs/typebox-validate/typebox'; import { ForbiddenError, NotFoundError } from 'egg-errors'; import { createHash } from 'node:crypto'; import base64url from 'base64url'; diff --git a/config/config.default.ts b/config/config.default.ts index 4260bc4e..a67bcdaa 100644 --- a/config/config.default.ts +++ b/config/config.default.ts @@ -1,7 +1,8 @@ import assert from 'node:assert/strict'; import { randomUUID } from 'node:crypto'; import { join } from 'node:path'; -import type { Context, EggAppConfig, PowerPartial } from 'egg'; + +import type { Context, EggAppConfig, PartialEggConfig } from 'egg'; import OSSClient from 'oss-cnpm'; import S3Client from 's3-cnpmcore'; import { env } from 'read-env-value'; @@ -84,9 +85,9 @@ export interface NFSConfig { removeBeforeUpload: boolean; } -export type Config = PowerPartial & { nfs: NFSConfig }; +export type Config = PartialEggConfig & { nfs: NFSConfig }; -export default function startConfig(appInfo: EggAppConfig) { +export default function startConfig(appInfo: EggAppConfig): Config { const config = {} as Config; config.keys = env('CNPMCORE_EGG_KEYS', 'string', randomUUID()); diff --git a/config/config.unittest.ts b/config/config.unittest.ts index 24836fc9..1950bd5f 100644 --- a/config/config.unittest.ts +++ b/config/config.unittest.ts @@ -7,7 +7,7 @@ import { database } from './database.js'; // @ts-expect-error has no construct signatures export const mockES = new Mock(); -export default function startConfig(appInfo: EggAppConfig) { +export default function startConfig(appInfo: EggAppConfig): PowerPartial { const config = {} as PowerPartial; config.dataDir = join(appInfo.root, '.cnpmcore_unittest'); diff --git a/config/plugin.ts b/config/plugin.ts index c23a5c22..1d77b845 100644 --- a/config/plugin.ts +++ b/config/plugin.ts @@ -42,7 +42,7 @@ const plugin: EggPlugin = { }, typeboxValidate: { enable: true, - package: 'egg-typebox-validate', + package: '@eggjs/typebox-validate', }, redis: { enable: true, diff --git a/package.json b/package.json index 54ad9868..c405d255 100644 --- a/package.json +++ b/package.json @@ -41,24 +41,25 @@ "dev:postgresql": "CNPMCORE_DATABASE_TYPE=PostgreSQL egg-bin dev", "lint": "oxlint", "lint:fix": "npm run lint -- --fix", - "test:postgresql": "npm run lint:fix && npm run test:local:postgresql", + "typecheck": "tsc --noEmit", + "test:postgresql": "npm run lint && npm run test:local:postgresql", "pretest:local:postgresql": "bash prepare-database-postgresql.sh", "test:local:postgresql": "CNPMCORE_DATABASE_TYPE=PostgreSQL egg-bin test", "pretest": "npm run clean", - "test": "npm run lint:fix && npm run test:local", + "test": "npm run lint && npm run typecheck && npm run test:local", "pretest:local": "bash prepare-database-mysql.sh", "test:local": "egg-bin test", "pret": "bash prepare-database-mysql.sh", - "t": "npm run lint:fix && egg-bin test --changed", + "t": "npm run lint && egg-bin test --changed", "precov": "bash prepare-database-mysql.sh", "cov": "egg-bin cov", "precov:postgresql": "bash prepare-database-postgresql.sh", "cov:postgresql": "CNPMCORE_DATABASE_TYPE=PostgreSQL egg-bin cov", - "preci": "npm run clean && npm run lint", + "preci": "npm run clean && npm run lint && npm run typecheck", "ci": "npm run cov ", "postci": "npm run tsc:prod && npm run clean", "ci:postgresql": "npm run lint && npm run cov:postgresql && npm run tsc:prod && npm run clean", - "clean": "tsc -b --clean && rm -rf dist", + "clean": "tsc -b --clean && rm -rf dist *.tsbuildinfo", "tsc": "npm run clean && tsc -p ./tsconfig.json", "tsc:prod": "npm run clean && tsc -p ./tsconfig.prod.json", "prepublishOnly": "npm run tsc:prod", @@ -88,7 +89,7 @@ "mocha": "11.6.0" }, "dependencies": { - "@eggjs/redis": "^3.0.0", + "@eggjs/redis": "beta", "@eggjs/scripts": "^4.0.0", "@eggjs/tegg": "beta", "@eggjs/tegg-aop-plugin": "beta", @@ -99,7 +100,8 @@ "@eggjs/tegg-orm-plugin": "beta", "@eggjs/tegg-plugin": "beta", "@eggjs/tegg-schedule-plugin": "beta", - "@eggjs/tracer": "^3.0.0", + "@eggjs/tracer": "beta", + "@eggjs/typebox-validate": "beta", "@elastic/elasticsearch": "^8.8.1", "@fengmk2/tar": "^6.2.0", "@node-rs/crc32": "^1.2.2", @@ -108,11 +110,10 @@ "base64url": "^3.0.1", "bson-objectid": "^2.0.4", "dayjs": "^1.10.7", - "egg": "^4.0.8", + "egg": "beta", "egg-cors": "^3.0.0", "egg-errors": "^2.3.0", "egg-status": "^1.0.0", - "egg-typebox-validate": "^3.0.0", "egg-view-nunjucks": "^2.3.0", "eggjs-elasticsearch": "^0.0.6", "fast-xml-parser": "^5.0.9", @@ -138,10 +139,10 @@ "s3-cnpmcore": "^1.1.2" }, "devDependencies": { - "@eggjs/bin": "^7.1.0", - "@eggjs/mock": "^6.0.7", + "@eggjs/bin": "beta", + "@eggjs/mock": "beta", "@eggjs/oxlint-config": "^1.0.0", - "@eggjs/tsconfig": "^2.0.0", + "@eggjs/tsconfig": "beta", "@elastic/elasticsearch-mock": "^2.0.0", "@simplewebauthn/typescript-types": "^7.0.0", "@types/lodash-es": "^4.17.12", @@ -159,6 +160,7 @@ "husky": "^9.1.7", "lint-staged": "^15.5.0", "oxlint": "^1.11.0", + "oxlint-tsgolint": "^0.2.0", "prettier": "^3.5.3", "typescript": "5" }, @@ -169,7 +171,7 @@ }, "homepage": "https://github.com/cnpm/cnpmcore#readme", "engines": { - "node": ">= 20.18.0" + "node": "^20.18.0 || >=22.18.0" }, "lint-staged": { "*": "prettier --write --ignore-unknown --cache", diff --git a/test/port/controller/package/UpdatePackageController.test.ts b/test/port/controller/package/UpdatePackageController.test.ts index bbd3f968..3bf8d67d 100644 --- a/test/port/controller/package/UpdatePackageController.test.ts +++ b/test/port/controller/package/UpdatePackageController.test.ts @@ -263,6 +263,24 @@ describe('test/port/controller/package/UpdatePackageController.test.ts', () => { assert.deepEqual(res.body, { ok: true }); }); + it('should support pnpm client', async () => { + const user = await TestUtil.createUser(); + mock(app.config.cnpmcore, 'admins', { [user.name]: user.email }); + const res = await app + .httpRequest() + .put(`/${scopedName}/-rev/${rev}`) + .set('authorization', user.authorization) + .set('user-agent', 'pnpm/7.3.1 npm/?') + .set('npm-command', 'owner') + .send({ + _id: rev, + _rev: rev, + maintainers: [{ name: user.name, email: user.email }], + }); + assert.deepEqual(res.body, { ok: true }); + assert.equal(res.statusCode, 200); + }); + it('should 403 when npm client invalid', async () => { const user = await TestUtil.createUser(); let res = await app @@ -474,6 +492,24 @@ describe('test/port/controller/package/UpdatePackageController.test.ts', () => { }) .expect(200); assert.equal(res.body.ok, true); + + // should valid with pnpm10 and npm=? + res = await app + .httpRequest() + .put(`/${scopedName}/-rev/${rev}`) + .set('authorization', publisher.authorization) + .set('user-agent', 'pnpm/10.0.0 npm/?') + .set('npm-command', 'owner') + .send({ + _id: rev, + _rev: rev, + maintainers: [ + { name: user.name, email: user.email }, + { name: publisher.name, email: publisher.email }, + ], + }) + .expect(200); + assert.equal(res.body.ok, true); }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 9147a169..d020db91 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "target": "ES2021", "resolveJsonModule": true, "useUnknownInCatchVariables": false, - "declaration": false - }, - "exclude": ["test"] + "declaration": false, + "erasableSyntaxOnly": false + } }