refactor: use tegg and egg v4 beta (#836)

🦀 for https://github.com/eggjs/egg/issues/3644
This commit is contained in:
MK (fengmk2)
2025-10-06 14:48:45 +08:00
committed by GitHub
parent c16ec7bad0
commit c7df471c0e
24 changed files with 91 additions and 44 deletions

View File

@@ -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"]
}

View File

@@ -2,5 +2,6 @@
"singleQuote": true,
"trailingComma": "es5",
"tabWidth": 2,
"printWidth": 120,
"arrowParens": "avoid"
}

View File

@@ -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` 代码。

View File

@@ -73,7 +73,7 @@
},
typeboxValidate: {
enable: true,
package: 'egg-typebox-validate',
package: '@eggjs/typebox-validate',
},
}
```

1
app.ts
View File

@@ -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' {

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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<EggAppConfig> & { 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());

View File

@@ -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<EggAppConfig> {
const config = {} as PowerPartial<EggAppConfig>;
config.dataDir = join(appInfo.root, '.cnpmcore_unittest');

View File

@@ -42,7 +42,7 @@ const plugin: EggPlugin = {
},
typeboxValidate: {
enable: true,
package: 'egg-typebox-validate',
package: '@eggjs/typebox-validate',
},
redis: {
enable: true,

View File

@@ -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",

View File

@@ -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);
});
});
});

View File

@@ -4,7 +4,7 @@
"target": "ES2021",
"resolveJsonModule": true,
"useUnknownInCatchVariables": false,
"declaration": false
},
"exclude": ["test"]
"declaration": false,
"erasableSyntaxOnly": false
}
}