From 1932bb9713187bcd4d7e0b0dde410cb0118ab607 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Tue, 8 Aug 2023 20:42:57 +0800 Subject: [PATCH 1/4] fix: noImplicitAny ts (#568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > Attempted to apply the `noImplicitAny`, parameter types should be specified unless any is manually declared. * 🐞 Fixed an issue in bugVersionAdvice where AbbreviatedManifest was being set abnormally. * 🤖 Added index.d.ts to store declarations for dependencies without types. * 🤔 skipLibCheck has no effect on leoric for now, so it cannot be enabled temporarily. -------- > 尝试应用 `noImplicitAny` 配置,除非手动声明 any,否则需要指定参数类型 * 🐞 修复 bugVersionAdvice 中,AbbreviatedManifest 设置异常 * 🤖 添加 index.d.ts 存放无类型依赖声明 * 🤔 skipLibCheck 对 leoric 失效,暂时无法开启 ![image](https://github.com/cnpm/cnpmcore/assets/5574625/7ed9d22e-cac8-4202-ba3c-d4c26eb7dc00) --- app/common/FileUtil.ts | 4 +- app/common/UserUtil.ts | 2 +- app/common/adapter/NPMRegistry.ts | 3 +- app/common/adapter/binary/AbstractBinary.ts | 8 +- app/common/adapter/binary/PlaywrightBinary.ts | 4 +- .../changesStream/CnpmjsorgChangesStream.ts | 14 ++- app/core/entity/BugVersion.ts | 2 +- app/core/entity/Hook.ts | 2 +- app/core/entity/SqlRange.ts | 2 +- app/core/service/BugVersionService.ts | 2 +- app/core/service/PackageManagerService.ts | 11 +- app/core/service/PackageSyncerService.ts | 6 +- app/core/service/PackageVersionService.ts | 2 +- app/core/service/UserService.ts | 8 +- app/port/controller/DownloadController.ts | 15 ++- app/port/controller/PackageTagController.ts | 2 +- .../package/SavePackageVersionController.ts | 14 ++- app/port/schedule/UpdateTotalData.ts | 4 +- app/port/typebox.ts | 6 +- app/port/webauth/WebauthController.ts | 25 +++- app/repository/BinaryRepository.ts | 4 +- app/repository/PackageRepository.ts | 7 +- .../PackageVersionDownloadRepository.ts | 2 +- app/repository/PackageVersionRepository.ts | 6 +- app/repository/UserRepository.ts | 2 +- app/repository/model/PackageVersion.ts | 2 +- app/repository/package.json | 3 + app/repository/util/ModelConvertor.ts | 27 +++-- index.d.ts | 113 ++++++++++++++++++ module.d.ts | 2 +- package.json | 7 +- tsconfig.json | 4 +- tsconfig.prod.json | 3 +- 33 files changed, 239 insertions(+), 79 deletions(-) create mode 100644 index.d.ts diff --git a/app/common/FileUtil.ts b/app/common/FileUtil.ts index fc7a3ffc..ab4f4b7f 100644 --- a/app/common/FileUtil.ts +++ b/app/common/FileUtil.ts @@ -105,13 +105,13 @@ const WHITE_FILENAME_CONTENT_TYPES = { '.eslintignore': PLAIN_TEXT, '.jshintrc': 'application/json', '.eslintrc': 'application/json', -}; +} as const; export function mimeLookup(filepath: string) { const filename = path.basename(filepath).toLowerCase(); if (filename.endsWith('.ts')) return PLAIN_TEXT; if (filename.endsWith('.lock')) return PLAIN_TEXT; return mime.lookup(filename) || - WHITE_FILENAME_CONTENT_TYPES[filename] || + WHITE_FILENAME_CONTENT_TYPES[filename as keyof typeof WHITE_FILENAME_CONTENT_TYPES] || DEFAULT_CONTENT_TYPE; } diff --git a/app/common/UserUtil.ts b/app/common/UserUtil.ts index d3ed5e3e..7d94f36b 100644 --- a/app/common/UserUtil.ts +++ b/app/common/UserUtil.ts @@ -34,7 +34,7 @@ export function integrity(plain: string): string { } export function checkIntegrity(plain: string, expectedIntegrity: string): boolean { - return ssri.checkData(plain, expectedIntegrity); + return !!ssri.checkData(plain, expectedIntegrity); } export function sha512(plain: string): string { diff --git a/app/common/adapter/NPMRegistry.ts b/app/common/adapter/NPMRegistry.ts index 1745b172..0a5537b3 100644 --- a/app/common/adapter/NPMRegistry.ts +++ b/app/common/adapter/NPMRegistry.ts @@ -11,6 +11,7 @@ import { HttpClientRequestOptions, HttpClientResponse, } from 'egg'; +import { PackageManifestType } from '../../repository/PackageRepository'; type HttpMethod = HttpClientRequestOptions['method']; @@ -40,7 +41,7 @@ export class NPMRegistry { this.registryHost = registryHost; } - public async getFullManifests(fullname: string, optionalConfig?: {retries?:number, remoteAuthToken?:string}): Promise { + public async getFullManifests(fullname: string, optionalConfig?: { retries?: number, remoteAuthToken?: string }): Promise<{ method: HttpMethod } & HttpClientResponse> { let retries = optionalConfig?.retries || 3; // set query t=timestamp, make sure CDN cache disable // cache=0 is sync worker request flag diff --git a/app/common/adapter/binary/AbstractBinary.ts b/app/common/adapter/binary/AbstractBinary.ts index 2f25e0f3..f811f80f 100644 --- a/app/common/adapter/binary/AbstractBinary.ts +++ b/app/common/adapter/binary/AbstractBinary.ts @@ -17,6 +17,8 @@ export type FetchResult = { nextParams?: any; }; +const platforms = [ 'darwin', 'linux', 'win32' ] as const; + export const BINARY_ADAPTER_ATTRIBUTE = Symbol('BINARY_ADAPTER_ATTRIBUTE'); export abstract class AbstractBinary { @@ -74,7 +76,7 @@ export abstract class AbstractBinary { protected listNodePlatforms() { // https://nodejs.org/api/os.html#osplatform - return [ 'darwin', 'linux', 'win32' ]; + return platforms; } protected listNodeArchs(binaryConfig?: BinaryTaskConfig) { @@ -87,11 +89,11 @@ export abstract class AbstractBinary { }; } - protected listNodeLibcs() { + protected listNodeLibcs(): Record { // https://github.com/lovell/detect-libc/blob/master/lib/detect-libc.js#L42 return { - linux: [ 'glibc', 'musl' ], darwin: [ 'unknown' ], + linux: [ 'glibc', 'musl' ], win32: [ 'unknown' ], }; } diff --git a/app/common/adapter/binary/PlaywrightBinary.ts b/app/common/adapter/binary/PlaywrightBinary.ts index 24680e39..848e248b 100644 --- a/app/common/adapter/binary/PlaywrightBinary.ts +++ b/app/common/adapter/binary/PlaywrightBinary.ts @@ -189,7 +189,7 @@ const DOWNLOAD_PATHS = { 'android': { '': 'builds/android/%s/android.zip', }, -}; +} as const; @SingletonProto() @BinaryAdapter(BinaryType.Playwright) @@ -215,7 +215,7 @@ export class PlaywrightBinary extends AbstractBinary { .filter(version => version.match(/^(?:\d+\.\d+\.\d+)(?:-beta-\d+)?$/)) // select recently update 20 items .slice(-20); - const browsers: { name: string; revision: string; browserVersion: string; revisionOverrides?: Record }[] = []; + const browsers: { name: keyof typeof DOWNLOAD_PATHS; revision: string; browserVersion: string; revisionOverrides?: Record }[] = []; await Promise.all( packageVersions.map(version => this.requestJSON( diff --git a/app/common/adapter/changesStream/CnpmjsorgChangesStream.ts b/app/common/adapter/changesStream/CnpmjsorgChangesStream.ts index ac253353..82460de3 100644 --- a/app/common/adapter/changesStream/CnpmjsorgChangesStream.ts +++ b/app/common/adapter/changesStream/CnpmjsorgChangesStream.ts @@ -6,6 +6,16 @@ import { AbstractChangeStream, RegistryChangesStream } from './AbstractChangesSt const MAX_LIMIT = 10000; +type FetchResults = { + results: { + seq: number; + type: string; + id: string; + changes: Record[]; + gmt_modified: Date, + }[]; +}; + @SingletonProto() @RegistryChangesStream(RegistryType.Cnpmjsorg) export class CnpmjsorgChangesStream extends AbstractChangeStream { @@ -18,13 +28,13 @@ export class CnpmjsorgChangesStream extends AbstractChangeStream { return since; } - private async tryFetch(registry: Registry, since: string, limit = 1000) { + private async tryFetch(registry: Registry, since: string, limit = 1000): Promise<{ data: FetchResults }> { if (limit > MAX_LIMIT) { throw new E500(`limit too large, current since: ${since}, limit: ${limit}`); } const db = this.getChangesStreamUrl(registry, since, limit); // json mode - const res = await this.httpclient.request(db, { + const res = await this.httpclient.request(db, { followRedirect: true, timeout: 30000, dataType: 'json', diff --git a/app/core/entity/BugVersion.ts b/app/core/entity/BugVersion.ts index ecd177d7..7ad27f3d 100644 --- a/app/core/entity/BugVersion.ts +++ b/app/core/entity/BugVersion.ts @@ -8,7 +8,7 @@ export type BugVersionPackages = Record; export class BugVersion { private readonly data: BugVersionPackages; - constructor(data) { + constructor(data: BugVersionPackages) { this.data = data; } diff --git a/app/core/entity/Hook.ts b/app/core/entity/Hook.ts index 345be061..443c2650 100644 --- a/app/core/entity/Hook.ts +++ b/app/core/entity/Hook.ts @@ -48,7 +48,7 @@ export class Hook extends Entity { } // payload 可能会特别大,如果做多次 stringify 浪费太多 cpu - signPayload(payload: object): { digest, payloadStr } { + signPayload(payload: object) { const payloadStr = JSON.stringify(payload); const digest = crypto.createHmac('sha256', this.secret) .update(JSON.stringify(payload)) diff --git a/app/core/entity/SqlRange.ts b/app/core/entity/SqlRange.ts index 78213703..ca47a4df 100644 --- a/app/core/entity/SqlRange.ts +++ b/app/core/entity/SqlRange.ts @@ -38,7 +38,7 @@ export class SqlRange { }; } const paddingSemver = new PaddingSemVer(comparator.semver); - const operator = OPERATOR_MAP[comparator.operator]; + const operator = OPERATOR_MAP[comparator.operator as keyof typeof OPERATOR_MAP]; if (!operator) { throw new Error(`unknown operator ${comparator.operator}`); } diff --git a/app/core/service/BugVersionService.ts b/app/core/service/BugVersionService.ts index 9f7a2ce3..4e6b1016 100644 --- a/app/core/service/BugVersionService.ts +++ b/app/core/service/BugVersionService.ts @@ -40,7 +40,7 @@ export class BugVersionService { const packageVersionJson = (await this.distRepository.findPackageVersionManifest(pkg!.packageId, tag!.version)) as PackageJSONType; if (!packageVersionJson) return; const data = packageVersionJson.config?.['bug-versions']; - bugVersion = new BugVersion(data); + bugVersion = new BugVersion(data || {}); this.bugVersionStore.setBugVersion(bugVersion, tag!.version); } return bugVersion; diff --git a/app/core/service/PackageManagerService.ts b/app/core/service/PackageManagerService.ts index e5e2606c..1da7594c 100644 --- a/app/core/service/PackageManagerService.ts +++ b/app/core/service/PackageManagerService.ts @@ -425,7 +425,7 @@ export class PackageManagerService extends AbstractService { public plusPackageVersionCounter(fullname: string, version: string) { // set counter + 1, schedule will store them into database - const counters = PackageManagerService.downloadCounters; + const counters: Record> = PackageManagerService.downloadCounters; if (!counters[fullname]) counters[fullname] = {}; counters[fullname][version] = (counters[fullname][version] || 0) + 1; // Total @@ -444,7 +444,7 @@ export class PackageManagerService extends AbstractService { // will be call by schedule/SavePackageVersionDownloadCounter.ts async savePackageVersionCounters() { // { [fullname]: { [version]: number } } - const counters = PackageManagerService.downloadCounters; + const counters: Record> = PackageManagerService.downloadCounters; const fullnames = Object.keys(counters); if (fullnames.length === 0) return; @@ -724,13 +724,16 @@ export class PackageManagerService extends AbstractService { const fieldsFromLatestManifest = [ 'author', 'bugs', 'contributors', 'description', 'homepage', 'keywords', 'license', 'readmeFilename', 'repository', - ]; + ] as const; // the latest version metas for (const field of fieldsFromLatestManifest) { - fullManifests[field] = latestManifest[field]; + if (latestManifest[field]) { + (fullManifests as Record)[field] = latestManifest[field]; + } } } + private async _setPackageDistTagsAndLatestInfos(pkg: Package, fullManifests: PackageManifestType, abbreviatedManifests: AbbreviatedPackageManifestType) { const distTags = await this._listPackageDistTags(pkg); if (distTags.latest) { diff --git a/app/core/service/PackageSyncerService.ts b/app/core/service/PackageSyncerService.ts index be396d61..1affb746 100644 --- a/app/core/service/PackageSyncerService.ts +++ b/app/core/service/PackageSyncerService.ts @@ -18,7 +18,7 @@ import { downloadToTempfile } from '../../common/FileUtil'; import { TaskState, TaskType } from '../../common/enum/Task'; import { AbstractService } from '../../common/AbstractService'; import { TaskRepository } from '../../repository/TaskRepository'; -import { PackageJSONType, PackageRepository } from '../../repository/PackageRepository'; +import { PackageJSONType, PackageManifestType, PackageRepository } from '../../repository/PackageRepository'; import { PackageVersionDownloadRepository } from '../../repository/PackageVersionDownloadRepository'; import { UserRepository } from '../../repository/UserRepository'; import { Task, SyncPackageTaskOptions, CreateSyncPackageTask } from '../entity/Task'; @@ -484,7 +484,7 @@ export class PackageSyncerService extends AbstractService { // { name: 'jasonlaster11', email: 'jason.laster.11@gmail.com' } // ], let maintainers = data.maintainers; - const maintainersMap = {}; + const maintainersMap: Record = {}; const users: User[] = []; let changedUserCount = 0; if (!Array.isArray(maintainers) || maintainers.length === 0) { @@ -619,7 +619,7 @@ export class PackageSyncerService extends AbstractService { } if (!isEqual(remoteItemValue, existsItem[key])) { diffMeta[key] = remoteItemValue; - } else if (!ignoreInAbbreviated.includes(key) && existsAbbreviatedItem && !isEqual(remoteItemValue, existsAbbreviatedItem[key])) { + } else if (!ignoreInAbbreviated.includes(key) && existsAbbreviatedItem && !isEqual(remoteItemValue, (existsAbbreviatedItem as Record)[key])) { // should diff exists abbreviated item too diffMeta[key] = remoteItemValue; } diff --git a/app/core/service/PackageVersionService.ts b/app/core/service/PackageVersionService.ts index 63e5964b..8852ca48 100644 --- a/app/core/service/PackageVersionService.ts +++ b/app/core/service/PackageVersionService.ts @@ -49,7 +49,7 @@ export class PackageVersionService { if (isFullManifests) { manifest = await this.distRepository.findPackageVersionManifest(pkgId, version); } else { - manifest = this.distRepository.findPackageAbbreviatedManifest(pkgId, version); + manifest = await this.distRepository.findPackageAbbreviatedManifest(pkgId, version); } if (manifest && bugVersionAdvice) { manifest.deprecated = `[WARNING] Use ${bugVersionAdvice.advice.version} instead of ${bugVersionAdvice.version}, reason: ${bugVersionAdvice.advice.reason}`; diff --git a/app/core/service/UserService.ts b/app/core/service/UserService.ts index 1d5df151..f634deb0 100644 --- a/app/core/service/UserService.ts +++ b/app/core/service/UserService.ts @@ -199,14 +199,14 @@ export class UserService extends AbstractService { await this.userRepository.removeToken(token.tokenId); } - async findWebauthnCredential(userId: string, browserType?: string) { + async findWebauthnCredential(userId: string, browserType: string | undefined | null) { const credential = await this.userRepository.findCredentialByUserIdAndBrowserType(userId, browserType || null); return credential; } - async createWebauthnCredential(userId: string, options: CreateWebauthnCredentialOptions) { + async createWebauthnCredential(userId: string | undefined, options: CreateWebauthnCredentialOptions) { const credentialEntity = WebauthnCredentialEntity.create({ - userId, + userId: userId as string, credentialId: options.credentialId, publicKey: options.publicKey, browserType: options.browserType, @@ -215,7 +215,7 @@ export class UserService extends AbstractService { return credentialEntity; } - async removeWebauthnCredential(userId: string, browserType?: string) { + async removeWebauthnCredential(userId?: string, browserType?: string) { const credential = await this.userRepository.findCredentialByUserIdAndBrowserType(userId, browserType || null); if (credential) { await this.userRepository.removeCredential(credential.wancId); diff --git a/app/port/controller/DownloadController.ts b/app/port/controller/DownloadController.ts index 7bcc2ac5..33a615a8 100644 --- a/app/port/controller/DownloadController.ts +++ b/app/port/controller/DownloadController.ts @@ -28,15 +28,15 @@ export class DownloadController extends AbstractController { const pkg = await this.packageRepository.findPackage(scope, name); if (!pkg) throw new NotFoundError(`${fullname} not found`); const entities = await this.packageVersionDownloadRepository.query(pkg.packageId, startDate.toDate(), endDate.toDate()); - const days = {}; - const versions = {}; + const days: Record = {}; + const versions: Record = {}; for (const entity of entities) { const yearMonth = String(entity.yearMonth); const prefix = yearMonth.substring(0, 4) + '-' + yearMonth.substring(4, 6); for (let i = 1; i <= 31; i++) { const day = String(i).padStart(2, '0'); - const field = `d${day}`; - const counter = entity[field]; + const field = `d${day}` as keyof typeof entity; + const counter = entity[field] as number; if (!counter) continue; const date = `${prefix}-${day}`; days[date] = (days[date] || 0) + counter; @@ -66,14 +66,14 @@ export class DownloadController extends AbstractController { async showTotalDownloads(@HTTPParam() scope: string, @HTTPParam() range: string) { const [ startDate, endDate ] = this.checkAndGetRange(range); const entities = await this.packageVersionDownloadRepository.query(scope, startDate.toDate(), endDate.toDate()); - const days = {}; + const days: Record = {}; for (const entity of entities) { const yearMonth = String(entity.yearMonth); const prefix = yearMonth.substring(0, 4) + '-' + yearMonth.substring(4, 6); for (let i = 1; i <= 31; i++) { const day = String(i).padStart(2, '0'); - const field = `d${day}`; - const counter = entity[field]; + const field = `d${day}` as keyof typeof entity; + const counter = entity[field] as number; if (!counter) continue; const date = `${prefix}-${day}`; days[date] = (days[date] || 0) + counter; @@ -115,4 +115,3 @@ export class DownloadController extends AbstractController { return [ startDate, endDate ]; } } - diff --git a/app/port/controller/PackageTagController.ts b/app/port/controller/PackageTagController.ts index 5618e4fc..a099310f 100644 --- a/app/port/controller/PackageTagController.ts +++ b/app/port/controller/PackageTagController.ts @@ -29,7 +29,7 @@ export class PackageTagController extends AbstractController { async showTags(@HTTPParam() fullname: string) { const packageEntity = await this.getPackageEntityByFullname(fullname); const tagEntities = await this.packageRepository.listPackageTags(packageEntity.packageId); - const tags = {}; + const tags: Record = {}; for (const entity of tagEntities) { tags[entity.tag] = entity.version; } diff --git a/app/port/controller/package/SavePackageVersionController.ts b/app/port/controller/package/SavePackageVersionController.ts index 369a4381..3386a9c7 100644 --- a/app/port/controller/package/SavePackageVersionController.ts +++ b/app/port/controller/package/SavePackageVersionController.ts @@ -21,6 +21,7 @@ import { Static, Type } from '@sinclair/typebox'; import { AbstractController } from '../AbstractController'; import { getScopeAndName, FULLNAME_REG_STRING, extractPackageJSON } from '../../../common/PackageUtil'; import { PackageManagerService } from '../../../core/service/PackageManagerService'; +import { PackageVersion as PackageVersionEntity } from '../../../core/entity/PackageVersion'; import { VersionRule, TagWithVersionRule, @@ -103,7 +104,7 @@ export class SavePackageVersionController extends AbstractController { const [ scope, name ] = getScopeAndName(fullname); const pkg = await this.packageRepository.findPackage(scope, name); if (!pkg) { - const errors = (validateResult.errors || validateResult.warnings).join(', '); + const errors = (validateResult.errors || validateResult.warnings || []).join(', '); throw new UnprocessableEntityError(`package.name invalid, errors: ${errors}`); } } @@ -185,7 +186,8 @@ export class SavePackageVersionController extends AbstractController { const tarballPkg = await extractPackageJSON(tarballBytes); const versionManifest = pkg.versions[tarballPkg.version]; const diffKeys = STRICT_CHECK_TARBALL_FIELDS.filter(key => { - return !isEqual(tarballPkg[key], versionManifest[key]); + const targetKey = key as unknown as keyof typeof versionManifest; + return !isEqual(tarballPkg[key], versionManifest[targetKey]); }); if (diffKeys.length > 0) { throw new UnprocessableEntityError(`${diffKeys} mismatch between tarball and manifest`); @@ -205,7 +207,7 @@ export class SavePackageVersionController extends AbstractController { const registry = await this.registryManagerService.ensureSelfRegistry(); - let packageVersionEntity; + let packageVersionEntity: PackageVersionEntity | undefined; const lockRes = await this.cacheAdapter.usingLock(`${pkg.name}:publish`, 60, async () => { packageVersionEntity = await this.packageManagerService.publish({ scope, @@ -230,12 +232,12 @@ export class SavePackageVersionController extends AbstractController { } this.logger.info('[package:version:add] %s@%s, packageVersionId: %s, tag: %s, userId: %s', - packageVersion.name, packageVersion.version, packageVersionEntity.packageVersionId, - tagWithVersion.tag, user.userId); + packageVersion.name, packageVersion.version, packageVersionEntity?.packageVersionId, + tagWithVersion.tag, user?.userId); ctx.status = 201; return { ok: true, - rev: `${packageVersionEntity.id}-${packageVersionEntity.packageVersionId}`, + rev: `${packageVersionEntity?.id}-${packageVersionEntity?.packageVersionId}`, }; } diff --git a/app/port/schedule/UpdateTotalData.ts b/app/port/schedule/UpdateTotalData.ts index 895e8cc8..6485cd78 100644 --- a/app/port/schedule/UpdateTotalData.ts +++ b/app/port/schedule/UpdateTotalData.ts @@ -79,8 +79,8 @@ export class UpdateTotalData { for (const row of rows) { for (let i = 1; i <= 31; i++) { const day = String(i).padStart(2, '0'); - const field = `d${day}`; - const counter = row[field]; + const field = `d${day}` as keyof typeof row; + const counter = row[field] as number; if (!counter) continue; const dayInt = row.yearMonth * 100 + i; if (dayInt === todayInt) download.today += counter; diff --git a/app/port/typebox.ts b/app/port/typebox.ts index 013538a9..330db94b 100644 --- a/app/port/typebox.ts +++ b/app/port/typebox.ts @@ -3,7 +3,7 @@ import { RegistryType } from '../common/enum/Registry'; import semver from 'semver'; import npa from 'npm-package-arg'; import { HookType } from '../common/enum/Hook'; -import binaryConfig from '../../config/binaries'; +import binaryConfig, { BinaryName } from '../../config/binaries'; export const Name = Type.String({ transform: [ 'trim' ], @@ -151,8 +151,8 @@ export function patchAjv(ajv: any) { }); ajv.addFormat('binary-name', { type: 'string', - validate: (binaryName: string) => { - return !!binaryConfig[binaryName]; + validate: (binaryName: BinaryName) => { + return binaryConfig[binaryName]; }, }); ajv.addFormat('semver-version-array', { diff --git a/app/port/webauth/WebauthController.ts b/app/port/webauth/WebauthController.ts index 2e486a25..d4939c7d 100644 --- a/app/port/webauth/WebauthController.ts +++ b/app/port/webauth/WebauthController.ts @@ -22,6 +22,8 @@ import { verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse, + VerifyRegistrationResponseOpts, + VerifyAuthenticationResponseOpts, } from '@simplewebauthn/server'; import type { PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/typescript-types'; import { LoginResultCode, WanStatusCode } from '../../common/enum/User'; @@ -44,6 +46,17 @@ type LoginPrepareResult = { wanCredentialAuthOption?: PublicKeyCredentialRequestOptionsJSON; }; +type LoginImplementRequest = { + accData: { + username: string; + password: string; + }; + wanCredentialRegiData: unknown; + wanCredentialAuthData: unknown; + needUnbindWan: boolean; + +}; + const UserRule = Type.Object({ name: Type.String({ minLength: 1, maxLength: 100 }), password: Type.String({ minLength: 8, maxLength: 100 }), @@ -102,7 +115,7 @@ export class WebauthController extends MiddlewareController { path: '/-/v1/login/request/session/:sessionId', method: HTTPMethodEnum.POST, }) - async loginImplement(@Context() ctx: EggContext, @HTTPParam() sessionId: string, @HTTPBody() loginImplementRequest) { + async loginImplement(@Context() ctx: EggContext, @HTTPParam() sessionId: string, @HTTPBody() loginImplementRequest: LoginImplementRequest) { ctx.tValidate(SessionRule, { sessionId }); const sessionToken = await this.cacheAdapter.get(sessionId); if (typeof sessionToken !== 'string') { @@ -123,7 +136,7 @@ export class WebauthController extends MiddlewareController { } } - const browserType = getBrowserTypeForWebauthn(ctx.headers['user-agent']); + const browserType = getBrowserTypeForWebauthn(ctx.headers['user-agent']) || undefined; const expectedChallenge = (await this.cacheAdapter.get(`${sessionId}_challenge`)) || ''; const expectedOrigin = this.config.cnpmcore.registry; const expectedRPID = new URL(expectedOrigin).hostname; @@ -139,7 +152,7 @@ export class WebauthController extends MiddlewareController { } try { const verification = await verifyAuthenticationResponse({ - response: wanCredentialAuthData, + response: wanCredentialAuthData as VerifyAuthenticationResponseOpts['response'], expectedChallenge, expectedOrigin, expectedRPID, @@ -193,7 +206,7 @@ export class WebauthController extends MiddlewareController { user = result.user; // need unbind webauthn credential if (needUnbindWan) { - await this.userService.removeWebauthnCredential(user.userId, browserType); + await this.userService.removeWebauthnCredential(user?.userId, browserType); } } else { // others: LoginResultCode.UserNotFound @@ -215,7 +228,7 @@ export class WebauthController extends MiddlewareController { if (enableWebAuthn && isSupportWebAuthn && wanCredentialRegiData) { try { const verification = await verifyRegistrationResponse({ - response: wanCredentialRegiData, + response: wanCredentialRegiData as VerifyRegistrationResponseOpts['response'], expectedChallenge, expectedOrigin, expectedRPID, @@ -225,7 +238,7 @@ export class WebauthController extends MiddlewareController { const { credentialPublicKey, credentialID } = registrationInfo; const base64CredentialPublicKey = base64url.encode(Buffer.from(new Uint8Array(credentialPublicKey))); const base64CredentialID = base64url.encode(Buffer.from(new Uint8Array(credentialID))); - this.userService.createWebauthnCredential(user.userId, { + this.userService.createWebauthnCredential(user?.userId, { credentialId: base64CredentialID, publicKey: base64CredentialPublicKey, browserType, diff --git a/app/repository/BinaryRepository.ts b/app/repository/BinaryRepository.ts index 5a539ab2..3f6752de 100644 --- a/app/repository/BinaryRepository.ts +++ b/app/repository/BinaryRepository.ts @@ -15,9 +15,9 @@ export class BinaryRepository extends AbstractRepository { if (binary.id) { const model = await this.Binary.findOne({ id: binary.id }); if (!model) return; - await ModelConvertor.saveEntityToModel(binary, model); + await ModelConvertor.saveEntityToModel(binary as unknown as Record, model); } else { - const model = await ModelConvertor.convertEntityToModel(binary, this.Binary); + const model = await ModelConvertor.convertEntityToModel(binary as unknown as Record, this.Binary); this.logger.info('[BinaryRepository:saveBinary:new] id: %s, binaryId: %s', model.id, model.binaryId); } } diff --git a/app/repository/PackageRepository.ts b/app/repository/PackageRepository.ts index 5da0b350..38e291d8 100644 --- a/app/repository/PackageRepository.ts +++ b/app/repository/PackageRepository.ts @@ -17,6 +17,7 @@ import type { Maintainer as MaintainerModel } from './model/Maintainer'; import type { User as UserModel } from './model/User'; import { User as UserEntity } from '../core/entity/User'; import { AbstractRepository } from './AbstractRepository'; +import { BugVersionPackages } from '../core/entity/BugVersion'; export type PackageManifestType = Pick & { _id: string; @@ -63,7 +64,9 @@ export type PackageJSONType = CnpmcorePatchInfo & { directories?: DirectoriesType; repository?: RepositoryType; scripts?: Record; - config?: Record; + config?: { + 'bug-versions'?: BugVersionPackages; + }; dependencies?: DepInfo; devDependencies?: DepInfo; peerDependencies?: DepInfo; @@ -102,7 +105,7 @@ export type PackageJSONType = CnpmcorePatchInfo & { [key: string]: unknown; }; -type PackageJSONPickKey = 'name' | 'author' | 'bugs' | 'description' | 'homepage' | 'keywords' | 'license' | 'readme' | 'readmeFilename' | 'repository' | 'versions'; +type PackageJSONPickKey = 'name' | 'author' | 'bugs' | 'description' | 'homepage' | 'keywords' | 'license' | 'readme' | 'readmeFilename' | 'repository' | 'versions' | 'contributors'; type CnpmcorePatchInfo = { _cnpmcore_publish_time?: Date; diff --git a/app/repository/PackageVersionDownloadRepository.ts b/app/repository/PackageVersionDownloadRepository.ts index c6553679..cf13ab6d 100644 --- a/app/repository/PackageVersionDownloadRepository.ts +++ b/app/repository/PackageVersionDownloadRepository.ts @@ -65,7 +65,7 @@ export class PackageVersionDownloadRepository extends AbstractRepository { } for (const [ date, counter ] of counters) { const field = `d${date}`; - model[field] = counter; + (model as unknown as Record)[field] = counter; } await model.save(); } diff --git a/app/repository/PackageVersionRepository.ts b/app/repository/PackageVersionRepository.ts index a5afbcc7..54b2e4fa 100644 --- a/app/repository/PackageVersionRepository.ts +++ b/app/repository/PackageVersionRepository.ts @@ -47,7 +47,7 @@ export class PackageVersionRepository { scope, name, tag, - } as object); + } as object) as { version: string }[]; const tagModel = tags && tags[0]; return tagModel?.version; } @@ -64,7 +64,7 @@ export class PackageVersionRepository { 'packages.name': name, ...sqlRange.condition, } as object) - .order('packageVersions.paddingVersion', 'desc'); + .order('packageVersions.paddingVersion', 'desc') as { version: string }[]; return versions?.[0]?.version; } @@ -78,6 +78,6 @@ export class PackageVersionRepository { ...sqlRange.condition, } as object); return (versions as any).toObject() - .map(t => t.version); + .map((t: { version: string }) => t.version); } } diff --git a/app/repository/UserRepository.ts b/app/repository/UserRepository.ts index 4bab40be..98cfe445 100644 --- a/app/repository/UserRepository.ts +++ b/app/repository/UserRepository.ts @@ -146,7 +146,7 @@ export class UserRepository extends AbstractRepository { } } - async findCredentialByUserIdAndBrowserType(userId: string, browserType: string | null) { + async findCredentialByUserIdAndBrowserType(userId: string | undefined, browserType: string | null) { const model = await this.WebauthnCredential.findOne({ userId, browserType, diff --git a/app/repository/model/PackageVersion.ts b/app/repository/model/PackageVersion.ts index f2115af5..1231ab4d 100644 --- a/app/repository/model/PackageVersion.ts +++ b/app/repository/model/PackageVersion.ts @@ -55,7 +55,7 @@ export class PackageVersion extends Bone { @Attribute(DataTypes.BOOLEAN) isPreRelease: boolean; - static beforeCreate(instance) { + static beforeCreate(instance: { version: string; paddingVersion: string; isPreRelease: boolean }) { if (!instance.paddingVersion) { const paddingSemVer = new PaddingSemVer(instance.version); instance.paddingVersion = paddingSemVer.paddingVersion; diff --git a/app/repository/package.json b/app/repository/package.json index db903524..8ffe8a5d 100644 --- a/app/repository/package.json +++ b/app/repository/package.json @@ -2,5 +2,8 @@ "name": "cnpmcore-repository", "eggModule": { "name": "cnpmcoreRepository" + }, + "devDependencies": { + "@types/lodash": "^4.14.196" } } diff --git a/app/repository/util/ModelConvertor.ts b/app/repository/util/ModelConvertor.ts index fd14c85c..393d59c6 100644 --- a/app/repository/util/ModelConvertor.ts +++ b/app/repository/util/ModelConvertor.ts @@ -8,13 +8,16 @@ const CREATED_AT = 'createdAt'; const UPDATED_AT = 'updatedAt'; const ID = 'id'; +type BonePatchInfo = { id?: bigint, updatedAt?: Date, createdAt?: Date }; +type PatchedBone = Bone & BonePatchInfo; + export class ModelConvertor { - static async convertEntityToModel(entity: object, ModelClazz: EggProtoImplClass, options?): Promise { + static async convertEntityToModel(entity: object, ModelClazz: EggProtoImplClass, options?: object): Promise { const metadata = ModelMetadataUtil.getModelMetadata(ModelClazz); if (!metadata) { throw new Error(`Model ${ModelClazz.name} has no metadata`); } - const attributes = {}; + const attributes: Record = {}; for (const attributeMeta of metadata.attributes) { const modelPropertyName = attributeMeta.propertyName; const entityPropertyName = ModelConvertorUtil.getEntityPropertyName(ModelClazz, modelPropertyName); @@ -22,17 +25,17 @@ export class ModelConvertor { const attributeValue = _.get(entity, entityPropertyName); attributes[modelPropertyName] = attributeValue; } - const model = await (ModelClazz as unknown as typeof Bone).create(attributes, options); + const model = await (ModelClazz as unknown as typeof Bone).create(attributes, options) as PatchedBone; // auto set entity id to model id - entity[ID] = model[ID]; + (entity as Record)[ID] = model[ID]; // use model dates - entity[UPDATED_AT] = model[UPDATED_AT]; - entity[CREATED_AT] = model[CREATED_AT]; + (entity as Record)[UPDATED_AT] = model[UPDATED_AT]; + (entity as Record)[CREATED_AT] = model[CREATED_AT]; return model as T; } static convertEntityToChanges(entity: object, ModelClazz: EggProtoImplClass) { - const changes = {}; + const changes: Record = {}; const metadata = ModelMetadataUtil.getModelMetadata(ModelClazz); if (!metadata) { throw new Error(`Model ${ModelClazz.name} has no metadata`); @@ -45,13 +48,13 @@ export class ModelConvertor { changes[modelPropertyName] = attributeValue; } changes[UPDATED_AT] = new Date(); - entity[UPDATED_AT] = changes[UPDATED_AT]; + (entity as Record)[UPDATED_AT] = changes[UPDATED_AT]; return changes; } // TODO: options is QueryOptions, should let leoric export it to use // Find out which attributes changed and set `updatedAt` to now - static async saveEntityToModel(entity: object, model: T, options?): Promise { + static async saveEntityToModel(entity: object, model: T & PatchedBone, options?: object): Promise { const ModelClazz = model.constructor as EggProtoImplClass; const metadata = ModelMetadataUtil.getModelMetadata(ModelClazz); if (!metadata) { @@ -64,14 +67,14 @@ export class ModelConvertor { // Restricted updates to the primary key if (entityPropertyName === ID && model[ID]) continue; const attributeValue = _.get(entity, entityPropertyName); - model[modelPropertyName] = attributeValue; + (model as unknown as Record)[modelPropertyName] = attributeValue; } // Restricted updates to the UPDATED_AT // Leoric will set by default model[UPDATED_AT] = undefined; await model.save(options); - entity[UPDATED_AT] = model[UPDATED_AT]; + (entity as Record)[UPDATED_AT] = model[UPDATED_AT]; return true; } @@ -85,7 +88,7 @@ export class ModelConvertor { for (const attributeMeta of metadata.attributes) { const modelPropertyName = attributeMeta.propertyName; const entityPropertyName = ModelConvertorUtil.getEntityPropertyName(ModelClazz as EggProtoImplClass, modelPropertyName); - const attributeValue = bone[attributeMeta.propertyName]; + const attributeValue = bone[attributeMeta.propertyName as keyof Bone]; _.set(data, entityPropertyName, attributeValue); } const model = Reflect.construct(entityClazz, [ data ]); diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 00000000..8f725bfa --- /dev/null +++ b/index.d.ts @@ -0,0 +1,113 @@ +declare module 'fs-cnpm' { + export default class FSClient extends NFSClient { + constructor(options: { + dir: string; + }); + } +} + +declare module 'ssri' { + export interface Integrity { + algorithm: string; + digest: string; + options?: string[]; + } + + export interface HashLike { + digest: string; + algorithm: string; + options?: string[]; + sha1: { + hexDigest(): string; + }[]; + sha512: { toString(): string }[]; + } + + export interface HashOptions { + algorithms?: string[]; + options?: string[]; + } + + export interface IntegrityOptions { + algorithms?: string[]; + options?: string[]; + single?: boolean; + } + + export interface CreateRes { + update(v: string): { digest: () => { toString() }; }; + } + + export function fromHex(hexDigest: string, algorithm: string, options?: string[]): Integrity; + + export function fromData(data: Buffer | string | Uint8Array, options?: HashOptions): HashLike; + + export function fromStream(stream: NodeJS.ReadableStream, options?: HashOptions): Promise; + + export function checkData(data: Buffer | string, sri: string | Integrity, options?: IntegrityOptions): boolean; + + export function checkStream(stream: NodeJS.ReadableStream, sri: string | Integrity, options?: IntegrityOptions): Promise; + + export function parse(sri: string): Integrity; + + export function create(): CreateRes; + + export function stringify(integrity: Integrity, options?: { strict?: boolean }): string; +} + +declare module 'oss-cnpm' { + import { Readable } from 'stream'; + + export interface AppendResult { + name: string; + url: string; + etag: string; + size: number; + } + + export interface UploadOptions { + key: string; + content: Readable; + size: number; + } + + export interface UploadResult { + name: string; + url: string; + etag: string; + size: number; + } + + export interface DownloadOptions { + key: string; + } + + export default class OSSClient { + constructor(options: { + cdnBaseUrl?: string; + accessKeyId: string; + accessKeySecret: string; + bucket: string; + internal?: boolean; + secure?: boolean; + timeout?: number; + cname?: boolean; + endpoint?: string; + defaultHeaders?: Record; + }); + + append(options: UploadOptions): Promise; + + upload(options: UploadOptions): Promise; + + download(options: DownloadOptions): Promise; + + delete(key: string): Promise; + + exists(key: string): Promise; + + stat(key: string): Promise<{ size: number }>; + + url(key: string): string; + } +} diff --git a/module.d.ts b/module.d.ts index d41dbda3..f1060c40 100644 --- a/module.d.ts +++ b/module.d.ts @@ -4,4 +4,4 @@ declare module "egg" { export interface EggContextModule { cnpmcoreCore: ContextCnpmcore; } -} +}; diff --git a/package.json b/package.json index 789b0e04..932e1689 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "devDependencies": { "@cnpmjs/npm-cli-login": "^1.1.0", "@simplewebauthn/typescript-types": "^7.0.0", + "@types/mime-types": "^2.1.1", "@types/mocha": "^10.0.1", "@types/mysql": "^2.15.21", "@types/semver": "^7.3.12", @@ -127,7 +128,11 @@ "eslint": "^8.29.0", "eslint-config-egg": "^12.1.0", "git-contributor": "2", - "typescript": "^5.0.4" + "typescript": "^5.0.4", + "@types/ua-parser-js": "^0.7.36", + "@types/lodash": "^4.14.196", + "@types/npm-package-arg": "^6.1.1", + "@types/validate-npm-package-name": "^4.0.0" }, "author": "killagu", "license": "MIT", diff --git a/tsconfig.json b/tsconfig.json index 6b24d140..c5a51662 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,14 +1,16 @@ { "extends": "@eggjs/tsconfig", "compilerOptions": { + "strict": true, "target": "ES2021", "module": "Node16", "moduleResolution": "Node", "declaration": false, "resolveJsonModule": true, - "useUnknownInCatchVariables": false + "useUnknownInCatchVariables": false, }, "exclude": [ + "test", "node_modules" ], } diff --git a/tsconfig.prod.json b/tsconfig.prod.json index 0d27de5c..5a4fc1d6 100644 --- a/tsconfig.prod.json +++ b/tsconfig.prod.json @@ -14,6 +14,7 @@ "config/**/*.ts", "typings/**/*.ts", "app.ts", - "module.d.ts" + "module.d.ts", + "index.d.ts" ] } From b102711adfab33f6a6e59bfe1e0e11c98e6b09c3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 8 Aug 2023 12:44:23 +0000 Subject: [PATCH 2/4] Release 3.39.5 [skip ci] ## [3.39.5](https://github.com/cnpm/cnpmcore/compare/v3.39.4...v3.39.5) (2023-08-08) ### Bug Fixes * noImplicitAny ts ([#568](https://github.com/cnpm/cnpmcore/issues/568)) ([1932bb9](https://github.com/cnpm/cnpmcore/commit/1932bb9713187bcd4d7e0b0dde410cb0118ab607)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ada5df6d..c011d5e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [3.39.5](https://github.com/cnpm/cnpmcore/compare/v3.39.4...v3.39.5) (2023-08-08) + + +### Bug Fixes + +* noImplicitAny ts ([#568](https://github.com/cnpm/cnpmcore/issues/568)) ([1932bb9](https://github.com/cnpm/cnpmcore/commit/1932bb9713187bcd4d7e0b0dde410cb0118ab607)) + ## [3.39.4](https://github.com/cnpm/cnpmcore/compare/v3.39.3...v3.39.4) (2023-08-04) diff --git a/package.json b/package.json index 932e1689..9d401600 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cnpmcore", - "version": "3.39.4", + "version": "3.39.5", "description": "npm core", "files": [ "dist/**/*" From c7106008d9e5da1760919010899f7a0e24acd051 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Tue, 8 Aug 2023 20:45:46 +0800 Subject: [PATCH 3/4] feat: signup on auth (#567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit > Auto init the account when auth * 🧶 Added `findOrCreateUser` method. Initialize account on both login and authorization, as per the submitted GitHub." ------ > 授权时,默认进行账户初始化 * 🧶 新增 `findOrCreateUser` 方法,登录和授权时均初始化账户 --- app/core/service/UserService.ts | 10 +++++++--- app/port/controller/TokenController.ts | 9 +++------ .../controller/TokenController/createToken.test.ts | 7 +++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/core/service/UserService.ts b/app/core/service/UserService.ts index f634deb0..e4987844 100644 --- a/app/core/service/UserService.ts +++ b/app/core/service/UserService.ts @@ -106,19 +106,23 @@ export class UserService extends AbstractService { return { code: LoginResultCode.Success, user, token }; } - async ensureTokenByUser({ name, email, password = crypto.randomUUID(), ip }: Optional) { + async findOrCreateUser({ name, email, ip, password = crypto.randomUUID() }: Optional) { let user = await this.userRepository.findUserByName(name); if (!user) { const createRes = await this.create({ name, email, - // Authentication via sso - // should use token instead of password password, ip, }); user = createRes.user; } + + return user; + } + + async ensureTokenByUser(opts: Optional) { + const user = await this.findOrCreateUser(opts); const token = await this.createToken(user.userId); return { user, token }; } diff --git a/app/port/controller/TokenController.ts b/app/port/controller/TokenController.ts index 46c65fab..fbbd1b70 100644 --- a/app/port/controller/TokenController.ts +++ b/app/port/controller/TokenController.ts @@ -131,15 +131,12 @@ export class TokenController extends AbstractController { return { objects, total: objects.length, urls: {} }; } - private async ensureWebUser() { + private async ensureWebUser(ip = '') { const userRes = await this.authAdapter.ensureCurrentUser(); if (!userRes?.name || !userRes?.email) { throw new ForbiddenError('need login first'); } - const user = await this.userService.findUserByName(userRes.name); - if (!user?.userId) { - throw new ForbiddenError('invalid user info'); - } + const user = await this.userService.findOrCreateUser({ name: userRes.name, email: userRes.email, ip }); return user; } @@ -155,7 +152,7 @@ export class TokenController extends AbstractController { // 3. Need to implement ensureCurrentUser method in AuthAdapter, or pass in this.user async createGranularToken(@Context() ctx: EggContext, @HTTPBody() tokenOptions: GranularTokenOptions) { ctx.tValidate(GranularTokenOptionsRule, tokenOptions); - const user = await this.ensureWebUser(); + const user = await this.ensureWebUser(ctx.ip); // 生成 Token const { name, description, allowedPackages, allowedScopes, cidr_whitelist, automation, readonly, expires } = tokenOptions; diff --git a/test/port/controller/TokenController/createToken.test.ts b/test/port/controller/TokenController/createToken.test.ts index 44adabc5..f05bcafa 100644 --- a/test/port/controller/TokenController/createToken.test.ts +++ b/test/port/controller/TokenController/createToken.test.ts @@ -135,21 +135,20 @@ describe('test/port/controller/TokenController/createToken.test.ts', () => { assert.match(res.body.error, /\[FORBIDDEN\] need login first/); }); - it('should 403 when no user info', async () => { + it('should auto create when no user info', async () => { mock(AuthAdapter.prototype, 'ensureCurrentUser', async () => { return { name: 'banana', email: 'banana@fruits.com', }; }); - const res = await app.httpRequest() + await app.httpRequest() .post('/-/npm/v1/tokens/gat') .send({ name: 'banana', expires: 30, }) - .expect(403); - assert.match(res.body.error, /\[FORBIDDEN\] invalid user info/); + .expect(200); }); describe('should 200', () => { From 3f9c91c430f142ab8d5696e01378628bf0d53378 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 8 Aug 2023 12:47:01 +0000 Subject: [PATCH 4/4] Release 3.40.0 [skip ci] ## [3.40.0](https://github.com/cnpm/cnpmcore/compare/v3.39.5...v3.40.0) (2023-08-08) ### Features * signup on auth ([#567](https://github.com/cnpm/cnpmcore/issues/567)) ([c710600](https://github.com/cnpm/cnpmcore/commit/c7106008d9e5da1760919010899f7a0e24acd051)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c011d5e2..0daf872a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [3.40.0](https://github.com/cnpm/cnpmcore/compare/v3.39.5...v3.40.0) (2023-08-08) + + +### Features + +* signup on auth ([#567](https://github.com/cnpm/cnpmcore/issues/567)) ([c710600](https://github.com/cnpm/cnpmcore/commit/c7106008d9e5da1760919010899f7a0e24acd051)) + ## [3.39.5](https://github.com/cnpm/cnpmcore/compare/v3.39.4...v3.39.5) (2023-08-08) diff --git a/package.json b/package.json index 9d401600..7ea7b007 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cnpmcore", - "version": "3.39.5", + "version": "3.40.0", "description": "npm core", "files": [ "dist/**/*"