From 793e78699083f25ddcb6e4f64a8f0dd427a6aa2d Mon Sep 17 00:00:00 2001 From: kunlongli Date: Thu, 12 Dec 2024 14:50:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0ohpm=E4=BB=93?= =?UTF-8?q?=E5=BA=93=20#2850?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/artifact/pojo/RepositoryType.kt | 2 +- .../common/artifact/util/PackageKeys.kt | 19 ++ .../bkrepo/common/service/proxy/ProxyEnv.kt | 2 +- .../tencent/bkrepo/npm/constants/Constants.kt | 19 +- .../tencent/bkrepo/npm/pojo/OhpmResponse.kt | 50 ++++ .../npm/pojo/user/OhpmDistTagRequest.kt | 32 ++ .../npm/pojo/user/OhpmUnpublishRequest.kt | 32 ++ src/backend/npm/biz-npm/build.gradle.kts | 1 + .../npm/artifact/NpmArtifactConfigurer.kt | 10 +- .../artifact/repository/NpmLocalRepository.kt | 65 +++- .../npm/controller/NpmClientController.kt | 80 ++++- .../UserModuleDependentsController.kt | 4 +- .../npm/controller/UserNpmController.kt | 8 +- .../npm/exception/NpmExceptionHandler.kt | 59 ++-- .../bkrepo/npm/handler/NpmDependentHandler.kt | 27 +- .../bkrepo/npm/handler/NpmPackageHandler.kt | 18 +- .../bkrepo/npm/service/NpmClientService.kt | 29 +- .../npm/service/impl/AbstractNpmService.kt | 14 +- .../npm/service/impl/NpmClientServiceImpl.kt | 282 +++++++++++++++--- .../npm/service/impl/NpmFixToolServiceImpl.kt | 8 +- .../npm/service/impl/NpmWebServiceImpl.kt | 17 +- .../com/tencent/bkrepo/npm/utils/NpmUtils.kt | 133 ++++++++- .../internal/type/NpmPackageNodeMapper.kt | 6 +- .../repository/pojo/packages/PackageType.kt | 3 +- 24 files changed, 778 insertions(+), 142 deletions(-) create mode 100644 src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/pojo/OhpmResponse.kt create mode 100644 src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/pojo/user/OhpmDistTagRequest.kt create mode 100644 src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/pojo/user/OhpmUnpublishRequest.kt diff --git a/src/backend/common/common-artifact/artifact-api/src/main/kotlin/com/tencent/bkrepo/common/artifact/pojo/RepositoryType.kt b/src/backend/common/common-artifact/artifact-api/src/main/kotlin/com/tencent/bkrepo/common/artifact/pojo/RepositoryType.kt index 85eef37ae6..1653dbb8a2 100644 --- a/src/backend/common/common-artifact/artifact-api/src/main/kotlin/com/tencent/bkrepo/common/artifact/pojo/RepositoryType.kt +++ b/src/backend/common/common-artifact/artifact-api/src/main/kotlin/com/tencent/bkrepo/common/artifact/pojo/RepositoryType.kt @@ -54,7 +54,7 @@ enum class RepositoryType(val supportPackage: Boolean) { SVN(false), S3(false), MEDIA(false), - + OHPM(true), ; companion object { diff --git a/src/backend/common/common-artifact/artifact-service/src/main/kotlin/com/tencent/bkrepo/common/artifact/util/PackageKeys.kt b/src/backend/common/common-artifact/artifact-service/src/main/kotlin/com/tencent/bkrepo/common/artifact/util/PackageKeys.kt index 901d1e49d6..e77bd18526 100644 --- a/src/backend/common/common-artifact/artifact-service/src/main/kotlin/com/tencent/bkrepo/common/artifact/util/PackageKeys.kt +++ b/src/backend/common/common-artifact/artifact-service/src/main/kotlin/com/tencent/bkrepo/common/artifact/util/PackageKeys.kt @@ -41,6 +41,7 @@ object PackageKeys { private const val DOCKER = "docker" private const val NPM = "npm" + private const val OHPM = "ohpm" private const val HELM = "helm" private const val RPM = "rpm" private const val PYPI = "pypi" @@ -100,6 +101,15 @@ object PackageKeys { return ofName(NPM, name) } + /** + * 生成ohpm格式key + * + * 例子: ohpm://test + */ + fun ofOhpm(name: String): String { + return ofName(OHPM, name) + } + /** * 生成helm格式key * @@ -166,6 +176,15 @@ object PackageKeys { return resolveName(NPM, npmKey) } + /** + * 解析ohpm格式的key + * + * 例子: ohpm://test -> test + */ + fun resolveOhpm(ohpmKey: String): String { + return resolveName(OHPM, ohpmKey) + } + /** * 解析helm格式的key * diff --git a/src/backend/common/common-service/src/main/kotlin/com/tencent/bkrepo/common/service/proxy/ProxyEnv.kt b/src/backend/common/common-service/src/main/kotlin/com/tencent/bkrepo/common/service/proxy/ProxyEnv.kt index 6ae9a921ac..ad5dad3f95 100644 --- a/src/backend/common/common-service/src/main/kotlin/com/tencent/bkrepo/common/service/proxy/ProxyEnv.kt +++ b/src/backend/common/common-service/src/main/kotlin/com/tencent/bkrepo/common/service/proxy/ProxyEnv.kt @@ -96,7 +96,7 @@ object ProxyEnv { private fun getProperty(key: String): String { if (properties.isEmpty) { if (!propertyFileResource.exists()) { - throw RuntimeException() + throw RuntimeException("properties is empty and property file resource not exist") } properties.load(propertyFileResource.inputStream) diff --git a/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/constants/Constants.kt b/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/constants/Constants.kt index 592b61a499..332c082f86 100644 --- a/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/constants/Constants.kt +++ b/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/constants/Constants.kt @@ -56,8 +56,8 @@ const val NPM_PKG_TGZ_FILE_FULL_PATH = NPM_PACKAGE_TGZ_FILE + "_full_path" const val NPM_PKG_VERSION_JSON_FILE_FULL_PATH = NPM_PACKAGE_VERSION_JSON_FILE + "_full_path" const val NPM_PKG_JSON_FILE_FULL_PATH = NPM_PACKAGE_JSON_FILE + "_full_path" // full path value -const val NPM_PKG_TGZ_FULL_PATH = "/%s/-/%s-%s.tgz" -const val NPM_PKG_TGZ_WITH_DOWNLOAD_FULL_PATH = "/%s/download/%s-%s.tgz" +const val NPM_PKG_TGZ_FULL_PATH = "/%s/-/%s-%s%s" +const val NPM_PKG_TGZ_WITH_DOWNLOAD_FULL_PATH = "/%s/download/%s-%s%s" const val NPM_PKG_VERSION_METADATA_FULL_PATH = "/.npm/%s/%s-%s.json" const val NPM_PKG_METADATA_FULL_PATH = "/.npm/%s/package.json" @@ -71,3 +71,18 @@ const val PKG_NAME = "pkg_name" val ERROR_MAP = mapOf("error" to "not_found", "reason" to "document not found") const val NPM_TGZ_TARBALL_PREFIX = "X-BKREPO-NPM-PREFIX" + +// OHPM +const val INTEGRITY_HSP = "integrity_hsp" +const val RESOLVED_HSP = "resolved_hsp" +const val HSP_FILE_EXT = ".hsp" +const val HAR_FILE_EXT = ".har" +const val HSP_TYPE = "hspType" +const val HSP_TYPE_BUNDLE_APP = "bundle_app" +const val OHPM_PACKAGE_TYPE = "packageType" +const val OHPM_PACKAGE_TYPE_HSP = "InterfaceHar" +const val OHPM_ARTIFACT_TYPE = "artifactType" +const val OHPM_DEFAULT_ARTIFACT_TYPE = "original" +const val OHPM_DEPRECATE = "deprecate" +const val OHPM_CHANGELOG_FILE_NAME = "changelog.md" +const val OHPM_README_FILE_NAME = "readme.md" diff --git a/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/pojo/OhpmResponse.kt b/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/pojo/OhpmResponse.kt new file mode 100644 index 0000000000..c23c91acb8 --- /dev/null +++ b/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/pojo/OhpmResponse.kt @@ -0,0 +1,50 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.bkrepo.npm.pojo + +import com.fasterxml.jackson.annotation.JsonInclude +import com.tencent.bkrepo.common.api.constant.HttpStatus + +@JsonInclude(JsonInclude.Include.NON_NULL) +data class OhpmResponse( + val code: Int = HttpStatus.OK.value, + val message: String? = null, + /** + * 出错时ohpm客户端会取响应体中error字段的值进行打印提示 + */ + val error: String? = null, + /** + * ohpm客户端会在发布包成功后打印该字段的值 + */ + val additionalMsg: String? = null, +) { + companion object { + fun success(message: String = "success") = OhpmResponse(HttpStatus.OK.value, message) + fun error(code: Int, error: String) = OhpmResponse(code = code, error = error) + } +} diff --git a/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/pojo/user/OhpmDistTagRequest.kt b/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/pojo/user/OhpmDistTagRequest.kt new file mode 100644 index 0000000000..9d60371840 --- /dev/null +++ b/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/pojo/user/OhpmDistTagRequest.kt @@ -0,0 +1,32 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.bkrepo.npm.pojo.user + +data class OhpmDistTagRequest( + val version: String +) diff --git a/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/pojo/user/OhpmUnpublishRequest.kt b/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/pojo/user/OhpmUnpublishRequest.kt new file mode 100644 index 0000000000..5de1560313 --- /dev/null +++ b/src/backend/npm/api-npm/src/main/kotlin/com/tencent/bkrepo/npm/pojo/user/OhpmUnpublishRequest.kt @@ -0,0 +1,32 @@ +/* + * Tencent is pleased to support the open source community by making BK-CI 蓝鲸持续集成平台 available. + * + * Copyright (C) 2024 THL A29 Limited, a Tencent company. All rights reserved. + * + * BK-CI 蓝鲸持续集成平台 is licensed under the MIT license. + * + * A copy of the MIT License is included in this file. + * + * + * Terms of the MIT License: + * --------------------------------------------------- + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package com.tencent.bkrepo.npm.pojo.user + +data class OhpmUnpublishRequest( + val version: String +) diff --git a/src/backend/npm/biz-npm/build.gradle.kts b/src/backend/npm/biz-npm/build.gradle.kts index 4fc66c4c63..309d254b4c 100644 --- a/src/backend/npm/biz-npm/build.gradle.kts +++ b/src/backend/npm/biz-npm/build.gradle.kts @@ -35,4 +35,5 @@ dependencies { api(project(":common:common-artifact:artifact-service")) implementation("org.springframework.retry:spring-retry") implementation("com.google.code.gson:gson") + implementation("com.github.zafarkhaja:java-semver") } diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/artifact/NpmArtifactConfigurer.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/artifact/NpmArtifactConfigurer.kt index 975922ef18..40a3e3083a 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/artifact/NpmArtifactConfigurer.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/artifact/NpmArtifactConfigurer.kt @@ -36,12 +36,15 @@ import com.tencent.bkrepo.common.api.pojo.Response import com.tencent.bkrepo.common.artifact.config.ArtifactConfigurerSupport import com.tencent.bkrepo.common.artifact.exception.ExceptionResponseTranslator import com.tencent.bkrepo.common.artifact.pojo.RepositoryType +import com.tencent.bkrepo.common.artifact.repository.context.ArtifactContextHolder import com.tencent.bkrepo.common.security.http.core.HttpAuthSecurityCustomizer +import com.tencent.bkrepo.common.service.util.HttpContextHolder import com.tencent.bkrepo.common.service.util.SpringContextUtils import com.tencent.bkrepo.npm.artifact.repository.NpmLocalRepository import com.tencent.bkrepo.npm.artifact.repository.NpmRemoteRepository import com.tencent.bkrepo.npm.artifact.repository.NpmVirtualRepository import com.tencent.bkrepo.npm.pojo.NpmErrorResponse +import com.tencent.bkrepo.npm.pojo.OhpmResponse import org.springframework.http.server.ServerHttpRequest import org.springframework.http.server.ServerHttpResponse import org.springframework.stereotype.Component @@ -49,6 +52,7 @@ import org.springframework.stereotype.Component @Component class NpmArtifactConfigurer : ArtifactConfigurerSupport() { + override fun getRepositoryTypes() = listOf(RepositoryType.OHPM) override fun getRepositoryType() = RepositoryType.NPM override fun getLocalRepository() = SpringContextUtils.getBean() override fun getRemoteRepository() = SpringContextUtils.getBean() @@ -62,7 +66,11 @@ class NpmArtifactConfigurer : ArtifactConfigurerSupport() { override fun getExceptionResponseTranslator() = object : ExceptionResponseTranslator { override fun translate(payload: Response<*>, request: ServerHttpRequest, response: ServerHttpResponse): Any { - return NpmErrorResponse(payload.message.orEmpty(), StringPool.EMPTY) + return if (ArtifactContextHolder.getRepoDetailOrNull()?.type == RepositoryType.OHPM) { + OhpmResponse.error(HttpContextHolder.getResponse().status, payload.message.orEmpty()) + } else { + NpmErrorResponse(payload.message.orEmpty(), StringPool.EMPTY) + } } } } diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/artifact/repository/NpmLocalRepository.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/artifact/repository/NpmLocalRepository.kt index 1485ab9b7d..956f97095e 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/artifact/repository/NpmLocalRepository.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/artifact/repository/NpmLocalRepository.kt @@ -34,6 +34,7 @@ import com.tencent.bkrepo.common.api.util.UrlFormatter import com.tencent.bkrepo.common.artifact.api.ArtifactFile import com.tencent.bkrepo.common.artifact.hash.sha1 import com.tencent.bkrepo.common.artifact.message.ArtifactMessageCode +import com.tencent.bkrepo.common.artifact.pojo.RepositoryType import com.tencent.bkrepo.common.artifact.repository.context.ArtifactDownloadContext import com.tencent.bkrepo.common.artifact.repository.context.ArtifactMigrateContext import com.tencent.bkrepo.common.artifact.repository.context.ArtifactQueryContext @@ -45,12 +46,16 @@ import com.tencent.bkrepo.common.artifact.repository.migration.MigrateDetail import com.tencent.bkrepo.common.artifact.repository.migration.PackageMigrateDetail import com.tencent.bkrepo.common.artifact.resolve.file.ArtifactFileFactory import com.tencent.bkrepo.common.artifact.resolve.response.ArtifactResource -import com.tencent.bkrepo.common.artifact.util.PackageKeys import com.tencent.bkrepo.common.query.enums.OperationType import com.tencent.bkrepo.npm.constants.ATTRIBUTE_OCTET_STREAM_SHA1 +import com.tencent.bkrepo.npm.constants.HAR_FILE_EXT +import com.tencent.bkrepo.npm.constants.HSP_FILE_EXT import com.tencent.bkrepo.npm.constants.METADATA import com.tencent.bkrepo.npm.constants.NPM_FILE_FULL_PATH import com.tencent.bkrepo.npm.constants.NPM_PACKAGE_TGZ_FILE +import com.tencent.bkrepo.npm.constants.OHPM_CHANGELOG_FILE_NAME +import com.tencent.bkrepo.npm.constants.OHPM_README_FILE_NAME +import com.tencent.bkrepo.npm.constants.RESOLVED_HSP import com.tencent.bkrepo.npm.constants.SEARCH_REQUEST import com.tencent.bkrepo.npm.constants.SIZE import com.tencent.bkrepo.npm.handler.NpmDependentHandler @@ -147,9 +152,14 @@ class NpmLocalRepository( artifactResource: ArtifactResource ): PackageDownloadRecord? { with(context) { + if (artifactInfo.getArtifactFullPath().endsWith(HSP_FILE_EXT)) { + // 拉取package一次会同时下载har与hsp包,此处仅统计har,避免重复 + return null + } val packageInfo = NpmUtils.parseNameAndVersionFromFullPath(artifactInfo.getArtifactFullPath()) with(packageInfo) { - return PackageDownloadRecord(projectId, repoName, PackageKeys.ofNpm(first), second) + val packageKey = NpmUtils.packageKeyByRepoType(first, repositoryDetail.type) + return PackageDownloadRecord(projectId, repoName, packageKey, second) } } } @@ -216,7 +226,7 @@ class NpmLocalRepository( with(context) { val (packageName, packageVersion) = NpmUtils.parseNameAndVersionFromFullPath(artifactInfo.getArtifactFullPath()) - val packageKey = PackageKeys.ofNpm(packageName) + val packageKey = NpmUtils.packageKeyByRepoType(packageName, context.repositoryDetail.type) return packageClient.findVersionByName(projectId, repoName, packageKey, packageVersion).data } } @@ -279,6 +289,7 @@ class NpmLocalRepository( var count = 0 val totalSize = packageMetaData.versions.map.size val iterator = packageMetaData.versions.map.entries.iterator() + val ohpm = context.repositoryDetail.type == RepositoryType.OHPM while (iterator.hasNext()) { val entry = iterator.next() val version = entry.key @@ -287,7 +298,11 @@ class NpmLocalRepository( measureTimeMillis { val tarball = versionMetadata.dist?.tarball!! storeVersionMetadata(context, versionMetadata) - val size = storeTgzArtifact(context, tarball, name, version) + var size = storeTgzArtifact(context, tarball, name, version, NpmUtils.getContentFileExt(ohpm)) + versionMetadata.dist?.any()?.get(RESOLVED_HSP)?.let { + // ohpm包存储hsp文件 + size += storeTgzArtifact(context, it as String, name, version, HSP_FILE_EXT) + } versionSizeMap[version] = size }.apply { logger.info( @@ -319,6 +334,7 @@ class NpmLocalRepository( ) { val name = packageMetaData.name!! val fullPath = NpmUtils.getPackageMetadataPath(name) + val ohpm = context.repositoryDetail.type == RepositoryType.OHPM try { with(context) { val originalPackageMetadata = npmPackageMetaData(fullPath) @@ -331,6 +347,9 @@ class NpmLocalRepository( } ?: return // 调整tarball地址 adjustTarball(newPackageMetaData, name, packageMetaData) + if (ohpm) { + newPackageMetaData.rev = newPackageMetaData.versions.map.size.toString() + } // 存储package.json文件 context.putAttribute(NPM_FILE_FULL_PATH, NpmUtils.getPackageMetadataPath(name)) val artifactFile = JsonUtils.objectMapper.writeValueAsBytes(newPackageMetaData).inputStream() @@ -339,7 +358,7 @@ class NpmLocalRepository( storageManager.storeArtifactFile(nodeCreateRequest, artifactFile, storageCredentials) // 添加依赖 npmDependentHandler.updatePackageDependents( - context.userId, context.artifactInfo, newPackageMetaData, NpmOperationAction.MIGRATION + context.userId, context.artifactInfo, newPackageMetaData, NpmOperationAction.MIGRATION, ohpm ) artifactFile.delete() } @@ -490,6 +509,10 @@ class NpmLocalRepository( private fun adjustTarball(versionMetaData: NpmVersionMetadata) { with(versionMetaData) { versionMetaData.dist!!.tarball = NpmUtils.formatTarballWithDash(name!!, version!!, dist?.tarball!!) + versionMetaData.dist!!.any()[RESOLVED_HSP]?.let { + val resolvedHsp = NpmUtils.formatTarballWithDash(name!!, version!!, it as String) + versionMetaData.dist!!.set(RESOLVED_HSP, resolvedHsp) + } } } @@ -531,14 +554,14 @@ class NpmLocalRepository( context: ArtifactMigrateContext, tarball: String, name: String, - version: String + version: String, + ext: String, ): Long { // 包的大小信息 with(context) { var size = 0L var response: Response? = null - val fullPath = NpmUtils.getTgzPath(name, version) - context.putAttribute(NPM_FILE_FULL_PATH, fullPath) + val fullPath = NpmUtils.getTgzPath(name, version, true, ext) // hit cache continue if (nodeClient.checkExist(projectId, repoName, fullPath).data!!) { logger.info( @@ -552,6 +575,12 @@ class NpmLocalRepository( response = okHttpUtil.doGet(tarball) if (checkResponse(response!!)) { val artifactFile = ArtifactFileFactory.build(response?.body!!.byteStream()) + if (fullPath.endsWith(HAR_FILE_EXT)) { + // 保存readme,changelog文件 + val readmeDir = NpmUtils.getReadmeDirFromTarballPath(fullPath) + artifactFile.getInputStream().use { storeReadmeAndChangelog(context, it, readmeDir) } + } + context.putAttribute(NPM_FILE_FULL_PATH, fullPath) val nodeCreateRequest = buildMigrationNodeCreateRequest(context, artifactFile) storageManager.storeArtifactFile(nodeCreateRequest, artifactFile, storageCredentials) size = artifactFile.getSize() @@ -572,6 +601,26 @@ class NpmLocalRepository( } } + private fun storeReadmeAndChangelog(context: ArtifactMigrateContext, inputStream: InputStream, readmeDir: String) { + try { + val (readme, changelog) = NpmUtils.getReadmeAndChangeLog(inputStream) + readme?.let { storeReadmeOrChangeLog(context, it, "$readmeDir/$OHPM_README_FILE_NAME") } + changelog?.let { storeReadmeOrChangeLog(context, it, "$readmeDir/$OHPM_CHANGELOG_FILE_NAME") } + } catch (exception: IOException) { + logger.error( + "Failed deploying npm readme [$readmeDir] due to : $exception" + ) + } + } + + private fun storeReadmeOrChangeLog(context: ArtifactMigrateContext, data: ByteArray, fullPath: String) { + context.putAttribute(NPM_FILE_FULL_PATH, fullPath) + val artifactFile = data.inputStream().use { ArtifactFileFactory.build(it) } + val nodeCreateRequest = buildMigrationNodeCreateRequest(context, artifactFile) + storageManager.storeArtifactFile(nodeCreateRequest, artifactFile, context.storageCredentials) + artifactFile.delete() + } + private fun buildMigrationNodeCreateRequest( context: ArtifactMigrateContext, file: ArtifactFile diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/controller/NpmClientController.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/controller/NpmClientController.kt index 409e22a2f4..a8fdc169ae 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/controller/NpmClientController.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/controller/NpmClientController.kt @@ -31,8 +31,13 @@ package com.tencent.bkrepo.npm.controller +import com.tencent.bkrepo.auth.pojo.enums.PermissionAction +import com.tencent.bkrepo.auth.pojo.enums.ResourceType import com.tencent.bkrepo.common.api.constant.MediaTypes +import com.tencent.bkrepo.common.api.util.readJsonString +import com.tencent.bkrepo.common.artifact.api.ArtifactFileMap import com.tencent.bkrepo.common.artifact.api.ArtifactPathVariable +import com.tencent.bkrepo.common.security.permission.Permission import com.tencent.bkrepo.common.service.util.HeaderUtils import com.tencent.bkrepo.common.service.util.HttpContextHolder import com.tencent.bkrepo.npm.artifact.NpmArtifactInfo @@ -41,16 +46,22 @@ import com.tencent.bkrepo.npm.model.metadata.NpmVersionMetadata import com.tencent.bkrepo.npm.pojo.NpmDeleteResponse import com.tencent.bkrepo.npm.pojo.NpmSearchResponse import com.tencent.bkrepo.npm.pojo.NpmSuccessResponse +import com.tencent.bkrepo.npm.pojo.OhpmResponse import com.tencent.bkrepo.npm.pojo.metadata.MetadataSearchRequest import com.tencent.bkrepo.npm.pojo.metadata.disttags.DistTags +import com.tencent.bkrepo.npm.pojo.user.OhpmUnpublishRequest +import com.tencent.bkrepo.npm.pojo.user.request.PackageVersionDeleteRequest import com.tencent.bkrepo.npm.service.NpmClientService +import com.tencent.bkrepo.npm.service.NpmWebService import com.tencent.bkrepo.npm.utils.NpmUtils import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.DeleteMapping import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestAttribute +import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMethod import org.springframework.web.bind.annotation.RestController @@ -60,9 +71,15 @@ import org.springframework.web.bind.annotation.RestController */ @RestController class NpmClientController( - private val npmClientService: NpmClientService + private val npmClientService: NpmClientService, + private val npmWebService: NpmWebService, ) { + @GetMapping("/{projectId}/{repoName}/-/ping") + fun ping(): OhpmResponse { + return OhpmResponse.success() + } + /** * npm service info */ @@ -94,6 +111,28 @@ class NpmClientController( return npmClientService.publishOrUpdatePackage(userId, artifactInfo, pkgName) } + @PostMapping( + "/{projectId}/{repoName}/stream/{name}", + "/{projectId}/{repoName}/stream/@{scope}/{name}" + ) + fun ohpmStreamPublishOrUpdatePackage( + @RequestAttribute userId: String, + @ArtifactPathVariable artifactInfo: NpmArtifactInfo, + @PathVariable name: String, + artifactFileMap: ArtifactFileMap, + ): OhpmResponse { + val npmPackageMetadata = HttpContextHolder + .getRequest() + .getParameter("metadata") + .readJsonString() + return npmClientService.ohpmStreamPublishOrUpdatePackage( + userId, + artifactInfo, + npmPackageMetadata, + artifactFileMap["pkg_stream"]!! + ) + } + /** * query package.json info */ @@ -140,7 +179,7 @@ class NpmClientController( return npmClientService.packageVersionInfo(artifactInfo, pkgName, version) } - @RequestMapping(value = ["/{projectId}/{repoName}/**/*.tgz"], method = [RequestMethod.HEAD]) + @RequestMapping(value = ["/{projectId}/{repoName}/**/*.{tgz,har,hsp}"], method = [RequestMethod.HEAD]) fun downloadHead( @ArtifactPathVariable artifactInfo: NpmArtifactInfo ) { @@ -150,7 +189,7 @@ class NpmClientController( /** * download tgz file */ - @GetMapping("/{projectId}/{repoName}/**/*.tgz") + @GetMapping("/{projectId}/{repoName}/**/*.{tgz,har,hsp,md}") fun download( @ArtifactPathVariable artifactInfo: NpmArtifactInfo ) { @@ -187,9 +226,12 @@ class NpmClientController( /** * npm dist-tag add */ - @PutMapping( - "/{projectId}/{repoName}/-/package/{name}/dist-tags/{tag}", - "/{projectId}/{repoName}/-/package/@{scope}/{name}/dist-tags/{tag}" + @RequestMapping( + method = [RequestMethod.POST, RequestMethod.PUT], + path = [ + "/{projectId}/{repoName}/-/package/{name}/dist-tags/{tag}", + "/{projectId}/{repoName}/-/package/@{scope}/{name}/dist-tags/{tag}" + ] ) fun addDistTags( @RequestAttribute userId: String, @@ -276,6 +318,32 @@ class NpmClientController( return NpmDeleteResponse(true, pkgName, rev) } + /** + * ohpm unpublish package or package version + */ + @DeleteMapping( + "/{projectId}/{repoName}/{name}", "/{projectId}/{repoName}/@{scope}/{name}" + ) + @Permission(ResourceType.REPO, PermissionAction.DELETE) + fun ohpmDeletePackage( + @RequestAttribute userId: String, + @ArtifactPathVariable artifactInfo: NpmArtifactInfo, + @PathVariable scope: String?, + @PathVariable name: String, + @RequestBody unpublishRequest: OhpmUnpublishRequest, + ) { + val pkgName = if (scope.isNullOrBlank()) name else String.format("@%s/%s", scope, name) + if (unpublishRequest.version.isEmpty()) { + npmClientService.deletePackage(userId, artifactInfo, pkgName) + } else { + val deleteRequest = PackageVersionDeleteRequest( + artifactInfo.projectId, artifactInfo.repoName, pkgName, unpublishRequest.version, userId + ) + // 删除json/har/hsp文件,移除package-version记录,更新package.json文件 + npmWebService.deleteVersion(artifactInfo, deleteRequest) + } + } + companion object { fun isFullMetadata(): Boolean { val referer = HeaderUtils.getHeader("referer") diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/controller/UserModuleDependentsController.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/controller/UserModuleDependentsController.kt index 435cc3c16d..04ba05f2de 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/controller/UserModuleDependentsController.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/controller/UserModuleDependentsController.kt @@ -35,10 +35,10 @@ import com.tencent.bkrepo.common.api.constant.DEFAULT_PAGE_NUMBER import com.tencent.bkrepo.common.api.constant.DEFAULT_PAGE_SIZE import com.tencent.bkrepo.common.api.pojo.Page import com.tencent.bkrepo.common.api.pojo.Response -import com.tencent.bkrepo.common.artifact.util.PackageKeys import com.tencent.bkrepo.common.service.util.ResponseBuilder import com.tencent.bkrepo.npm.pojo.module.des.ModuleDepsInfo import com.tencent.bkrepo.npm.service.ModuleDepsService +import com.tencent.bkrepo.npm.utils.NpmUtils import io.swagger.annotations.Api import io.swagger.annotations.ApiOperation import io.swagger.annotations.ApiParam @@ -68,7 +68,7 @@ class UserModuleDependentsController( @ApiParam(value = "资源名称", required = true) @RequestParam packageKey: String ): Response> { - val name = PackageKeys.resolveNpm(packageKey) + val name = NpmUtils.resolveNameByRepoType(packageKey) return ResponseBuilder.success(moduleDepsService.page(projectId, repoName, pageNumber, pageSize, name)) } } diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/controller/UserNpmController.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/controller/UserNpmController.kt index d320010a9a..6ef5c25064 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/controller/UserNpmController.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/controller/UserNpmController.kt @@ -35,15 +35,15 @@ import com.tencent.bkrepo.auth.pojo.enums.PermissionAction import com.tencent.bkrepo.auth.pojo.enums.ResourceType import com.tencent.bkrepo.common.api.pojo.Response import com.tencent.bkrepo.common.artifact.api.ArtifactPathVariable -import com.tencent.bkrepo.common.artifact.util.PackageKeys import com.tencent.bkrepo.common.security.permission.Permission import com.tencent.bkrepo.common.service.util.ResponseBuilder import com.tencent.bkrepo.npm.artifact.NpmArtifactInfo import com.tencent.bkrepo.npm.pojo.NpmDomainInfo +import com.tencent.bkrepo.npm.pojo.user.PackageVersionInfo import com.tencent.bkrepo.npm.pojo.user.request.PackageDeleteRequest import com.tencent.bkrepo.npm.pojo.user.request.PackageVersionDeleteRequest -import com.tencent.bkrepo.npm.pojo.user.PackageVersionInfo import com.tencent.bkrepo.npm.service.NpmWebService +import com.tencent.bkrepo.npm.utils.NpmUtils import io.swagger.annotations.Api import io.swagger.annotations.ApiOperation import io.swagger.annotations.ApiParam @@ -88,7 +88,7 @@ class UserNpmController( @RequestParam packageKey: String ): Response { with(artifactInfo) { - val pkgName = PackageKeys.resolveNpm(packageKey) + val pkgName = NpmUtils.resolveNameByRepoType(packageKey) val deleteRequest = PackageDeleteRequest( projectId, repoName, pkgName, userId ) @@ -110,7 +110,7 @@ class UserNpmController( @RequestParam version: String ): Response { with(artifactInfo) { - val pkgName = PackageKeys.resolveNpm(packageKey) + val pkgName = NpmUtils.resolveNameByRepoType(packageKey) val deleteRequest = PackageVersionDeleteRequest( projectId, repoName, pkgName, version, userId ) diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/exception/NpmExceptionHandler.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/exception/NpmExceptionHandler.kt index 49a8274547..9339f395f7 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/exception/NpmExceptionHandler.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/exception/NpmExceptionHandler.kt @@ -36,9 +36,12 @@ import com.tencent.bkrepo.common.api.constant.BASIC_AUTH_PROMPT import com.tencent.bkrepo.common.api.constant.HttpHeaders import com.tencent.bkrepo.common.api.constant.USER_KEY import com.tencent.bkrepo.common.api.util.JsonUtils +import com.tencent.bkrepo.common.artifact.pojo.RepositoryType +import com.tencent.bkrepo.common.artifact.repository.context.ArtifactContextHolder import com.tencent.bkrepo.common.security.exception.AuthenticationException import com.tencent.bkrepo.common.service.util.HttpContextHolder import com.tencent.bkrepo.npm.pojo.NpmErrorResponse +import com.tencent.bkrepo.npm.pojo.OhpmResponse import com.tencent.bkrepo.npm.pojo.auth.AuthFailInfo import com.tencent.bkrepo.npm.pojo.auth.NpmAuthFailResponse import com.tencent.bkrepo.npm.pojo.auth.NpmAuthResponse @@ -62,49 +65,49 @@ class NpmExceptionHandler { @ResponseStatus(HttpStatus.BAD_REQUEST) fun handlerBadRequestException(exception: NpmBadRequestException) { val responseObject = NpmErrorResponse("bad request", exception.message) - npmResponse(responseObject, exception) + response(responseObject, exception, HttpStatus.BAD_REQUEST, responseObject.reason) } @ExceptionHandler(NpmRepoNotFoundException::class) @ResponseStatus(HttpStatus.BAD_REQUEST) fun handlerRepoNotFoundException(exception: NpmRepoNotFoundException) { val responseObject = NpmErrorResponse("bad request", exception.message) - npmResponse(responseObject, exception) + response(responseObject, exception, HttpStatus.BAD_REQUEST, responseObject.reason) } @ExceptionHandler(ExecutionException::class) @ResponseStatus(HttpStatus.BAD_REQUEST) fun handlerExecutionException(exception: ExecutionException) { val responseObject = NpmErrorResponse("execution exception", exception.message.orEmpty()) - npmResponse(responseObject, exception) + response(responseObject, exception, HttpStatus.BAD_REQUEST, responseObject.reason) } @ExceptionHandler(NpmArgumentResolverException::class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) fun handlerNpmArgumentResolverException(exception: NpmArgumentResolverException) { val fail = NpmAuthResponse.fail(exception.message) - npmResponse(fail, exception) + response(fail, exception, HttpStatus.INTERNAL_SERVER_ERROR, exception.message) } @ExceptionHandler(AuthenticationException::class) @ResponseStatus(HttpStatus.UNAUTHORIZED) fun handlerClientAuthException(exception: AuthenticationException) { val responseObject = NpmErrorResponse("Unauthorized", "Authentication required") - npmResponse(responseObject, exception) + response(responseObject, exception, HttpStatus.UNAUTHORIZED, responseObject.reason) } @ExceptionHandler(NpmClientAuthException::class) @ResponseStatus(HttpStatus.UNAUTHORIZED) fun handlerNpmClientAuthException(exception: NpmClientAuthException) { val responseObject = NpmErrorResponse("Unauthorized", "Authentication required") - npmResponse(responseObject, exception) + response(responseObject, exception, HttpStatus.UNAUTHORIZED, responseObject.reason) } @ExceptionHandler(NpmLoginFailException::class) @ResponseStatus(HttpStatus.UNAUTHORIZED) fun handlerNpmLoginFailException(exception: NpmLoginFailException) { val fail = NpmAuthResponse.fail(exception.message) - npmResponse(fail, exception) + response(fail, exception, HttpStatus.UNAUTHORIZED, exception.message) } @ExceptionHandler(NpmTokenIllegalException::class) @@ -117,64 +120,44 @@ class NpmExceptionHandler { ) val npmAuthFailResponse = NpmAuthFailResponse(errors = listOf(authFailInfo)) - npmResponse(npmAuthFailResponse, exception) + response(npmAuthFailResponse, exception, HttpStatus.UNAUTHORIZED, exception.message) } @ExceptionHandler(NpmArtifactNotFoundException::class) @ResponseStatus(HttpStatus.NOT_FOUND) fun handlerNpmArtifactNotFoundException(exception: NpmArtifactNotFoundException) { val responseObject = NpmErrorResponse.notFound() - npmResponse(responseObject, exception) + response(responseObject, exception, HttpStatus.NOT_FOUND, responseObject.reason) } @ExceptionHandler(NpmArgumentNotFoundException::class) @ResponseStatus(HttpStatus.NOT_FOUND) fun handlerNpmArgumentNotFoundException(exception: NpmArgumentNotFoundException) { val responseObject = NpmErrorResponse.notFound() - npmResponse(responseObject, exception) + response(responseObject, exception, HttpStatus.NOT_FOUND, responseObject.reason) } @ExceptionHandler(NpmArtifactExistException::class) @ResponseStatus(HttpStatus.FORBIDDEN) fun handlerNpmArtifactExistException(exception: NpmArtifactExistException) { val responseObject = NpmErrorResponse(exception.message, "forbidden") - npmResponse(responseObject, exception) + response(responseObject, exception, HttpStatus.FORBIDDEN, responseObject.reason) } @ExceptionHandler(NpmTagNotExistException::class) @ResponseStatus(HttpStatus.NOT_FOUND) fun handlerNpmTagNotExistException(exception: NpmTagNotExistException) { val responseObject = NpmErrorResponse("not found", exception.message) - npmResponse(responseObject, exception) + response(responseObject, exception, HttpStatus.NOT_FOUND, responseObject.reason) } - private fun npmResponse(responseObject: NpmErrorResponse, exception: NpmException) { + private fun response(responseObject: Any, exception: Exception, status: HttpStatus, message: String) { logNpmException(exception) - val responseString = JsonUtils.objectMapper.writeValueAsString(responseObject) - val response = HttpContextHolder.getResponse() - response.contentType = "application/json; charset=utf-8" - response.writer.println(responseString) - } - - private fun npmResponse(npmAuthResponse: NpmAuthResponse, exception: NpmException) { - logNpmException(exception) - val responseString = JsonUtils.objectMapper.writeValueAsString(npmAuthResponse) - val response = HttpContextHolder.getResponse() - response.contentType = "application/json; charset=utf-8" - response.writer.println(responseString) - } - - private fun npmResponse(responseObject: NpmErrorResponse, exception: Exception) { - logNpmException(exception) - val responseString = JsonUtils.objectMapper.writeValueAsString(responseObject) - val response = HttpContextHolder.getResponse() - response.contentType = "application/json; charset=utf-8" - response.writer.println(responseString) - } - - private fun npmResponse(npmAuthFailResponse: NpmAuthFailResponse, exception: NpmException) { - logNpmException(exception) - val responseString = JsonUtils.objectMapper.writeValueAsString(npmAuthFailResponse) + val responseString = if (ArtifactContextHolder.getRepoDetailOrNull()?.type == RepositoryType.OHPM) { + JsonUtils.objectMapper.writeValueAsString(OhpmResponse.error(status.value(), message)) + } else { + JsonUtils.objectMapper.writeValueAsString(responseObject) + } val response = HttpContextHolder.getResponse() response.contentType = "application/json; charset=utf-8" response.writer.println(responseString) diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/handler/NpmDependentHandler.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/handler/NpmDependentHandler.kt index 0f8608c674..e832a0c60b 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/handler/NpmDependentHandler.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/handler/NpmDependentHandler.kt @@ -32,11 +32,11 @@ package com.tencent.bkrepo.npm.handler import com.tencent.bkrepo.common.artifact.api.ArtifactInfo -import com.tencent.bkrepo.common.artifact.util.PackageKeys import com.tencent.bkrepo.npm.model.metadata.NpmPackageMetaData import com.tencent.bkrepo.npm.model.metadata.NpmVersionMetadata import com.tencent.bkrepo.npm.pojo.enums.NpmOperationAction import com.tencent.bkrepo.npm.utils.NpmUtils +import com.tencent.bkrepo.npm.utils.NpmUtils.packageKey import com.tencent.bkrepo.repository.api.PackageDependentsClient import com.tencent.bkrepo.repository.pojo.dependent.PackageDependentsRelation import org.slf4j.LoggerFactory @@ -50,25 +50,32 @@ class NpmDependentHandler { @Autowired private lateinit var packageDependentsClient: PackageDependentsClient + fun existsPackageDependents(projectId: String, repoName: String, name: String, ohpm: Boolean): Boolean { + val packageKey = packageKey(name, ohpm) + val dependents = packageDependentsClient.queryDependents(projectId, repoName, packageKey).data + return !dependents.isNullOrEmpty() + } + @Async fun updatePackageDependents( userId: String, artifactInfo: ArtifactInfo, npmPackageMetaData: NpmPackageMetaData, - action: NpmOperationAction + action: NpmOperationAction, + ohpm: Boolean = false, ) { val latestVersion = NpmUtils.getLatestVersionFormDistTags(npmPackageMetaData.distTags) val versionMetaData = npmPackageMetaData.versions.map[latestVersion]!! when (action) { NpmOperationAction.PUBLISH -> { - doDependentWithPublish(userId, artifactInfo, versionMetaData) + doDependentWithPublish(userId, artifactInfo, versionMetaData, ohpm) } NpmOperationAction.UNPUBLISH -> { - doDependentWithUnPublish(userId, artifactInfo, versionMetaData) + doDependentWithUnPublish(userId, artifactInfo, versionMetaData, ohpm) } NpmOperationAction.MIGRATION -> { - doDependentWithPublish(userId, artifactInfo, versionMetaData) + doDependentWithPublish(userId, artifactInfo, versionMetaData, ohpm) } else -> { logger.warn("don't find operation action [${action.name}].") @@ -79,7 +86,8 @@ class NpmDependentHandler { private fun doDependentWithPublish( userId: String, artifactInfo: ArtifactInfo, - versionMetaData: NpmVersionMetadata + versionMetaData: NpmVersionMetadata, + ohpm: Boolean, ) { val name = versionMetaData.name.orEmpty() with(artifactInfo){ @@ -87,7 +95,7 @@ class NpmDependentHandler { projectId = projectId, repoName = repoName, packageKey = name, - dependencies = versionMetaData.dependencies?.keys.orEmpty().map { PackageKeys.ofNpm(it) }.toSet() + dependencies = versionMetaData.dependencies?.keys.orEmpty().map { packageKey(it, ohpm) }.toSet() ) packageDependentsClient.addDependents(relation) logger.info("user [$userId] publish dependent for package: [$name] success.") @@ -97,7 +105,8 @@ class NpmDependentHandler { private fun doDependentWithUnPublish( userId: String, artifactInfo: ArtifactInfo, - versionMetaData: NpmVersionMetadata + versionMetaData: NpmVersionMetadata, + ohpm: Boolean, ) { val name = versionMetaData.name.orEmpty() @@ -106,7 +115,7 @@ class NpmDependentHandler { projectId = projectId, repoName = repoName, packageKey = name, - dependencies = versionMetaData.dependencies?.keys.orEmpty().map { PackageKeys.ofNpm(it) }.toSet() + dependencies = versionMetaData.dependencies?.keys.orEmpty().map { packageKey(it, ohpm) }.toSet() ) packageDependentsClient.reduceDependents(relation) logger.info("user [$userId] delete dependent for package: [$name] success.") diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/handler/NpmPackageHandler.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/handler/NpmPackageHandler.kt index 0636433125..264df89869 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/handler/NpmPackageHandler.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/handler/NpmPackageHandler.kt @@ -35,7 +35,6 @@ import com.tencent.bkrepo.common.api.util.JsonUtils import com.tencent.bkrepo.common.artifact.util.PackageKeys import com.tencent.bkrepo.common.service.util.HttpContextHolder import com.tencent.bkrepo.npm.artifact.NpmArtifactInfo -import com.tencent.bkrepo.npm.constants.NPM_PKG_TGZ_FULL_PATH import com.tencent.bkrepo.npm.constants.SIZE import com.tencent.bkrepo.npm.model.metadata.NpmPackageMetaData import com.tencent.bkrepo.npm.model.metadata.NpmVersionMetadata @@ -139,22 +138,23 @@ class NpmPackageHandler { userId: String, artifactInfo: NpmArtifactInfo, versionMetaData: NpmVersionMetadata, - size: Long + size: Long, + ohpm: Boolean ) { versionMetaData.apply { val name = this.name!! val description = this.description val version = this.version!! val manifestPath = getManifestPath(name, version) - val contentPath = getContentPath(name, version) + val contentPath = NpmUtils.getContentPath(name, version, ohpm) val metadata = buildProperties(this) with(artifactInfo) { val packageVersionCreateRequest = PackageVersionCreateRequest( projectId = projectId, repoName = repoName, packageName = name, - packageKey = PackageKeys.ofNpm(name), - packageType = PackageType.NPM, + packageKey = NpmUtils.packageKey(name, ohpm), + packageType = if (ohpm) PackageType.OHPM else PackageType.NPM, packageDescription = description, versionName = version, size = size, @@ -185,7 +185,7 @@ class NpmPackageHandler { * 删除包 */ fun deletePackage(userId: String, name: String, artifactInfo: NpmArtifactInfo) { - val packageKey = PackageKeys.ofNpm(name) + val packageKey = NpmUtils.packageKeyByRepoType(name) with(artifactInfo) { packageClient.deletePackage(projectId, repoName, packageKey, HttpContextHolder.getClientAddress()).apply { logger.info("user: [$userId] delete package [$name] in repo [$projectId/$repoName] success!") @@ -197,7 +197,7 @@ class NpmPackageHandler { * 删除版本 */ fun deleteVersion(userId: String, name: String, version: String, artifactInfo: NpmArtifactInfo) { - val packageKey = PackageKeys.ofNpm(name) + val packageKey = NpmUtils.packageKeyByRepoType(name) with(artifactInfo) { packageClient.deleteVersion(projectId, repoName, packageKey, version, HttpContextHolder.getClientAddress()) .apply { @@ -213,10 +213,6 @@ class NpmPackageHandler { return NpmUtils.getVersionPackageMetadataPath(name, version) } - fun getContentPath(name: String, version: String): String { - return String.format(NPM_PKG_TGZ_FULL_PATH, name, name, version) - } - companion object { val logger: Logger = LoggerFactory.getLogger(NpmPackageHandler::class.java) } diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/NpmClientService.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/NpmClientService.kt index 59ba461dc0..4f36d533b3 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/NpmClientService.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/NpmClientService.kt @@ -31,11 +31,13 @@ package com.tencent.bkrepo.npm.service +import com.tencent.bkrepo.common.artifact.api.ArtifactFile import com.tencent.bkrepo.npm.artifact.NpmArtifactInfo import com.tencent.bkrepo.npm.model.metadata.NpmVersionMetadata import com.tencent.bkrepo.npm.model.metadata.NpmPackageMetaData import com.tencent.bkrepo.npm.pojo.NpmSearchResponse import com.tencent.bkrepo.npm.pojo.NpmSuccessResponse +import com.tencent.bkrepo.npm.pojo.OhpmResponse import com.tencent.bkrepo.npm.pojo.metadata.MetadataSearchRequest import com.tencent.bkrepo.npm.pojo.metadata.disttags.DistTags @@ -45,6 +47,16 @@ interface NpmClientService { */ fun publishOrUpdatePackage(userId: String, artifactInfo: NpmArtifactInfo, name: String): NpmSuccessResponse + /** + * ohpm 流式上传 + */ + fun ohpmStreamPublishOrUpdatePackage( + userId: String, + artifactInfo: NpmArtifactInfo, + npmPackageMetaData: NpmPackageMetaData, + artifactFile: ArtifactFile + ): OhpmResponse + /** * 查询npm包的package.json元数据信息 */ @@ -88,10 +100,25 @@ interface NpmClientService { /** * delete package version */ - fun deleteVersion(artifactInfo: NpmArtifactInfo, name: String, version: String, tgzPath: String) + fun deleteVersion(artifactInfo: NpmArtifactInfo, name: String, version: String, tarballPath: String) /** * delete package */ fun deletePackage(userId: String, artifactInfo: NpmArtifactInfo, name: String) + + /** + * 目标制品为OHPM类型且被依赖时,将标记指定版本为废弃并抛出异常,否则什么都不处理 + * + * @param userId 用户 + * @param artifactInfo 制品信息 + * @param packageMetaData OHPM包的package.json数据 + * @param version 指定版本,为null时将所有版本都标记为废弃 + */ + fun checkOhpmDependentsAndDeprecate( + userId: String, + artifactInfo: NpmArtifactInfo, + packageMetaData: NpmPackageMetaData, + version: String? + ) } diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/AbstractNpmService.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/AbstractNpmService.kt index d0fb4a18ad..40789795c5 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/AbstractNpmService.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/AbstractNpmService.kt @@ -36,8 +36,10 @@ import com.tencent.bkrepo.common.artifact.repository.context.ArtifactContextHold import com.tencent.bkrepo.common.artifact.repository.context.ArtifactQueryContext import com.tencent.bkrepo.common.service.util.HeaderUtils import com.tencent.bkrepo.npm.artifact.NpmArtifactInfo +import com.tencent.bkrepo.npm.constants.INTEGRITY_HSP import com.tencent.bkrepo.npm.constants.NPM_FILE_FULL_PATH import com.tencent.bkrepo.npm.constants.NPM_TGZ_TARBALL_PREFIX +import com.tencent.bkrepo.npm.constants.RESOLVED_HSP import com.tencent.bkrepo.npm.exception.NpmArtifactNotFoundException import com.tencent.bkrepo.npm.exception.NpmRepoNotFoundException import com.tencent.bkrepo.npm.model.metadata.NpmPackageMetaData @@ -73,7 +75,7 @@ open class AbstractNpmService { * 查询仓库是否存在 */ fun checkRepositoryExist(projectId: String, repoName: String): RepositoryDetail { - return repositoryClient.getRepoDetail(projectId, repoName, "NPM").data ?: run { + return ArtifactContextHolder.getRepoDetailOrNull() ?: run { logger.error("check repository [$repoName] in projectId [$projectId] failed!") throw NpmRepoNotFoundException("repository [$repoName] in projectId [$projectId] not existed.") } @@ -144,6 +146,16 @@ open class AbstractNpmService { NpmUtils.buildPackageTgzTarball( oldTarball, npmProperties.domain, npmProperties.tarball.prefix, name, artifactInfo ) + resolveOhpmHsp(versionMetadata) + } + + protected fun resolveOhpmHsp(versionMetadata: NpmVersionMetadata) { + if (versionMetadata.dist?.any()?.get(INTEGRITY_HSP) == null) { + return + } + // OHPM HSP包 + val hspTarball = NpmUtils.harPathToHspPath(versionMetadata.dist?.tarball!!) + versionMetadata.dist!!.set(RESOLVED_HSP, hspTarball) } companion object { diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/NpmClientServiceImpl.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/NpmClientServiceImpl.kt index c3672a5a99..410ee2b977 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/NpmClientServiceImpl.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/NpmClientServiceImpl.kt @@ -33,7 +33,10 @@ package com.tencent.bkrepo.npm.service.impl import com.tencent.bkrepo.auth.pojo.enums.PermissionAction import com.tencent.bkrepo.auth.pojo.enums.ResourceType +import com.tencent.bkrepo.common.api.constant.MediaTypes import com.tencent.bkrepo.common.api.util.JsonUtils.objectMapper +import com.tencent.bkrepo.common.artifact.api.ArtifactFile +import com.tencent.bkrepo.common.artifact.pojo.RepositoryType import com.tencent.bkrepo.common.artifact.repository.context.ArtifactContextHolder import com.tencent.bkrepo.common.artifact.repository.context.ArtifactDownloadContext import com.tencent.bkrepo.common.artifact.repository.context.ArtifactQueryContext @@ -41,16 +44,25 @@ import com.tencent.bkrepo.common.artifact.repository.context.ArtifactRemoveConte import com.tencent.bkrepo.common.artifact.repository.context.ArtifactSearchContext import com.tencent.bkrepo.common.artifact.repository.context.ArtifactUploadContext import com.tencent.bkrepo.common.artifact.resolve.file.ArtifactFileFactory -import com.tencent.bkrepo.common.artifact.util.PackageKeys import com.tencent.bkrepo.common.security.permission.Permission import com.tencent.bkrepo.common.service.util.HttpContextHolder import com.tencent.bkrepo.npm.artifact.NpmArtifactInfo import com.tencent.bkrepo.npm.constants.ATTRIBUTE_OCTET_STREAM_SHA1 import com.tencent.bkrepo.npm.constants.CREATED +import com.tencent.bkrepo.npm.constants.HAR_FILE_EXT +import com.tencent.bkrepo.npm.constants.HSP_TYPE +import com.tencent.bkrepo.npm.constants.HSP_TYPE_BUNDLE_APP import com.tencent.bkrepo.npm.constants.LATEST import com.tencent.bkrepo.npm.constants.MODIFIED import com.tencent.bkrepo.npm.constants.NPM_FILE_FULL_PATH import com.tencent.bkrepo.npm.constants.NPM_PACKAGE_TGZ_FILE +import com.tencent.bkrepo.npm.constants.OHPM_ARTIFACT_TYPE +import com.tencent.bkrepo.npm.constants.OHPM_CHANGELOG_FILE_NAME +import com.tencent.bkrepo.npm.constants.OHPM_DEFAULT_ARTIFACT_TYPE +import com.tencent.bkrepo.npm.constants.OHPM_DEPRECATE +import com.tencent.bkrepo.npm.constants.OHPM_PACKAGE_TYPE +import com.tencent.bkrepo.npm.constants.OHPM_PACKAGE_TYPE_HSP +import com.tencent.bkrepo.npm.constants.OHPM_README_FILE_NAME import com.tencent.bkrepo.npm.constants.SEARCH_REQUEST import com.tencent.bkrepo.npm.constants.SIZE import com.tencent.bkrepo.npm.constants.TGZ_FULL_PATH_WITH_DASH_SEPARATOR @@ -66,9 +78,12 @@ import com.tencent.bkrepo.npm.model.properties.PackageProperties import com.tencent.bkrepo.npm.pojo.NpmSearchInfoMap import com.tencent.bkrepo.npm.pojo.NpmSearchResponse import com.tencent.bkrepo.npm.pojo.NpmSuccessResponse +import com.tencent.bkrepo.npm.pojo.OhpmResponse import com.tencent.bkrepo.npm.pojo.enums.NpmOperationAction +import com.tencent.bkrepo.npm.pojo.enums.NpmOperationAction.UNPUBLISH import com.tencent.bkrepo.npm.pojo.metadata.MetadataSearchRequest import com.tencent.bkrepo.npm.pojo.metadata.disttags.DistTags +import com.tencent.bkrepo.npm.pojo.user.OhpmDistTagRequest import com.tencent.bkrepo.npm.service.NpmClientService import com.tencent.bkrepo.npm.utils.BeanUtils import com.tencent.bkrepo.npm.utils.NpmUtils @@ -77,6 +92,8 @@ import com.tencent.bkrepo.repository.api.MetadataClient import com.tencent.bkrepo.repository.pojo.metadata.MetadataModel import com.tencent.bkrepo.repository.pojo.metadata.MetadataSaveRequest import org.apache.commons.codec.binary.Base64 +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream import org.apache.commons.lang3.StringUtils import org.slf4j.Logger import org.slf4j.LoggerFactory @@ -136,6 +153,25 @@ class NpmClientServiceImpl( } } + @Permission(ResourceType.REPO, PermissionAction.WRITE) + @Transactional(rollbackFor = [Throwable::class]) + override fun ohpmStreamPublishOrUpdatePackage( + userId: String, + artifactInfo: NpmArtifactInfo, + npmPackageMetaData: NpmPackageMetaData, + artifactFile: ArtifactFile + ): OhpmResponse { + measureTimeMillis { + handlerPackagePublish(userId, artifactInfo, npmPackageMetaData, artifactFile) + }.apply { + logger.info( + "user [$userId] public ohpm package [${npmPackageMetaData.name}] " + + "to repo [${artifactInfo.getRepoIdentify()}] success, elapse $this ms" + ) + } + return OhpmResponse.success() + } + @Permission(ResourceType.REPO, PermissionAction.READ) @Transactional(rollbackFor = [Throwable::class]) override fun packageInfo(artifactInfo: NpmArtifactInfo, name: String): NpmPackageMetaData { @@ -194,7 +230,11 @@ class NpmClientServiceImpl( "in repo [${artifactInfo.getRepoIdentify()}]" ) val packageMetaData = queryPackageInfo(artifactInfo, name, false) - val version = objectMapper.readValue(HttpContextHolder.getRequest().inputStream, String::class.java) + val version = if (ArtifactContextHolder.getRepoDetail()?.type == RepositoryType.OHPM) { + objectMapper.readValue(HttpContextHolder.getRequest().inputStream, OhpmDistTagRequest::class.java).version + } else { + objectMapper.readValue(HttpContextHolder.getRequest().inputStream, String::class.java) + } if ((LATEST == tag && packageMetaData.versions.map.containsKey(version)) || LATEST != tag) { packageMetaData.distTags.getMap()[tag] = version doPackageFileUpload(userId, artifactInfo, packageMetaData) @@ -232,17 +272,23 @@ class NpmClientServiceImpl( artifactInfo: NpmArtifactInfo, name: String, version: String, - tgzPath: String + tarballPath: String ) { logger.info("handling delete version [$version] request for package [$name].") val fullPathList = mutableListOf() - val packageKey = PackageKeys.ofNpm(name) + val ohpm = ArtifactContextHolder.getRepoDetail()?.type == RepositoryType.OHPM + val packageKey = NpmUtils.packageKey(name, ohpm) with(artifactInfo) { // 判断package_version是否存在 - if (tgzPath.isEmpty() || !packageVersionExist(projectId, repoName, packageKey, version)) { + if (tarballPath.isEmpty() || !packageVersionExist(projectId, repoName, packageKey, version)) { throw NpmArtifactNotFoundException("package [$name] with version [$version] not exists.") } - fullPathList.add(tgzPath) + fullPathList.add(tarballPath) + if (ohpm) { + val hspPath = NpmUtils.harPathToHspPath(tarballPath) + fullPathList.add(hspPath) + fullPathList.add(NpmUtils.getReadmeDirFromTarballPath(tarballPath)) + } fullPathList.add(NpmUtils.getVersionPackageMetadataPath(name, version)) val context = ArtifactRemoveContext() // 删除包管理中对应的version @@ -256,8 +302,9 @@ class NpmClientServiceImpl( @Permission(ResourceType.REPO, PermissionAction.WRITE) override fun deletePackage(userId: String, artifactInfo: NpmArtifactInfo, name: String) { logger.info("handling delete package request for package [$name]") - val fullPathList = mutableListOf() val packageMetaData = queryPackageInfo(artifactInfo, name, false) + checkOhpmDependentsAndDeprecate(userId, artifactInfo, packageMetaData, null) + val fullPathList = mutableListOf() fullPathList.add(".npm/$name") fullPathList.add(name) val context = ArtifactRemoveContext() @@ -267,7 +314,31 @@ class NpmClientServiceImpl( ArtifactContextHolder.getRepository().remove(context).also { logger.info("userId [$userId] delete package [$name] success.") } - npmDependentHandler.updatePackageDependents(userId, artifactInfo, packageMetaData, NpmOperationAction.UNPUBLISH) + val ohpm = context.repositoryDetail.type == RepositoryType.OHPM + npmDependentHandler.updatePackageDependents(userId, artifactInfo, packageMetaData, UNPUBLISH, ohpm) + } + + override fun checkOhpmDependentsAndDeprecate( + userId: String, + artifactInfo: NpmArtifactInfo, + packageMetaData: NpmPackageMetaData, + version: String? + ) { + val projectId = artifactInfo.projectId + val repoName = artifactInfo.repoName + val name = packageMetaData.name!! + val ohpm = ArtifactContextHolder.getRepoDetail()!!.type == RepositoryType.OHPM + if (!ohpm || !npmDependentHandler.existsPackageDependents(projectId, repoName, name, ohpm)) { + return + } + + packageMetaData.versions.map.forEach { + if (version == null || version == it.key) { + it.value.set(OHPM_DEPRECATE, true) + } + } + doPackageFileUpload(userId, artifactInfo, packageMetaData) + throw NpmBadRequestException("The OHPM package \"${name}\" has been depended on by other components.") } private fun searchLatestVersionMetadata(artifactInfo: NpmArtifactInfo, name: String): NpmVersionMetadata { @@ -321,27 +392,37 @@ class NpmClientServiceImpl( private fun handlerPackagePublish( userId: String, artifactInfo: NpmArtifactInfo, - npmPackageMetaData: NpmPackageMetaData + npmPackageMetaData: NpmPackageMetaData, + artifactFile: ArtifactFile? = null ) { - val attachments = npmPackageMetaData.attachments - attachments ?: run { + if (npmPackageMetaData.attachments == null && artifactFile == null) { val message = "Missing attachments with tarball data, aborting upload for '${npmPackageMetaData.name}'" logger.warn(message) throw NpmBadRequestException(message) } try { - val size = attachments.getMap().values.iterator().next().length!!.toLong() - handlerAttachmentsUpload(userId, artifactInfo, npmPackageMetaData) - handlerPackageFileUpload(userId, artifactInfo, npmPackageMetaData, size) + var size = 0L + if (artifactFile != null) { + size = artifactFile.getSize() + } else { + npmPackageMetaData.attachments!!.getMap().values.forEach { size += it.length!!.toLong() } + } + val ohpm = ArtifactContextHolder.getRepoDetail()!!.type == RepositoryType.OHPM + if (ohpm) { + resolveOhpm(npmPackageMetaData) + } + handlerAttachmentsUpload(userId, artifactInfo, npmPackageMetaData, artifactFile) + handlerPackageFileUpload(userId, artifactInfo, npmPackageMetaData, size, ohpm) handlerVersionFileUpload(userId, artifactInfo, npmPackageMetaData, size) npmDependentHandler.updatePackageDependents( userId, artifactInfo, npmPackageMetaData, - NpmOperationAction.PUBLISH + NpmOperationAction.PUBLISH, + ohpm ) val versionMetadata = npmPackageMetaData.versions.map.values.iterator().next() - npmPackageHandler.createVersion(userId, artifactInfo, versionMetadata, size) + npmPackageHandler.createVersion(userId, artifactInfo, versionMetadata, size, ohpm) } catch (exception: IOException) { val version = NpmUtils.getLatestVersionFormDistTags(npmPackageMetaData.distTags) logger.error( @@ -351,14 +432,32 @@ class NpmClientServiceImpl( } } + private fun resolveOhpm(npmPackageMetaData: NpmPackageMetaData) { + npmPackageMetaData.versions.map.forEach { + val version = it.value + resolveOhpmHsp(version) + val hspType = npmPackageMetaData.any()[HSP_TYPE]?.toString() + if (!hspType.isNullOrEmpty()) { + version.set(HSP_TYPE, hspType) + } + if (version.any()[OHPM_ARTIFACT_TYPE] == null) { + // OpenHarmony包制品类型,有两个选项:original、obfuscation + // original:源码,即发布源码(.ts/.ets);obfuscation:混淆代码,即源码经过混淆之后发布上传 + // 默认为original + version.set(OHPM_ARTIFACT_TYPE, OHPM_DEFAULT_ARTIFACT_TYPE) + } + } + } + private fun handlerPackageFileUpload( userId: String, artifactInfo: NpmArtifactInfo, npmPackageMetaData: NpmPackageMetaData, - size: Long + size: Long, + ohpm: Boolean, ) { with(artifactInfo) { - val packageKey = PackageKeys.ofNpm(npmPackageMetaData.name.orEmpty()) + val packageKey = NpmUtils.packageKeyByRepoType(npmPackageMetaData.name.orEmpty()) val gmtTime = TimeUtil.getGMTTime() val npmMetadata = npmPackageMetaData.versions.map.values.iterator().next() if (!npmMetadata.dist!!.any().containsKey(SIZE)) { @@ -366,6 +465,9 @@ class NpmClientServiceImpl( } // 第一次上传 if (!packageExist(projectId, repoName, packageKey)) { + if (ohpm) { + npmPackageMetaData.rev = npmPackageMetaData.versions.map.size.toString() + } npmPackageMetaData.time.add(CREATED, gmtTime) npmPackageMetaData.time.add(MODIFIED, gmtTime) npmPackageMetaData.time.add(npmMetadata.version!!, gmtTime) @@ -377,6 +479,10 @@ class NpmClientServiceImpl( originalPackageInfo.distTags.getMap().putAll(npmPackageMetaData.distTags.getMap()) originalPackageInfo.time.add(MODIFIED, gmtTime) originalPackageInfo.time.add(npmMetadata.version!!, gmtTime) + if (ohpm) { + NpmUtils.updateLatestVersion(originalPackageInfo) + originalPackageInfo.rev = originalPackageInfo.versions.map.size.toString() + } doPackageFileUpload(userId, artifactInfo, originalPackageInfo) } } @@ -418,7 +524,8 @@ class NpmClientServiceImpl( val artifactFile = inputStream.use { ArtifactFileFactory.build(it) } val context = ArtifactUploadContext(artifactFile) context.putAttribute(NPM_FILE_FULL_PATH, fullPath) - context.putAttribute(ATTRIBUTE_OCTET_STREAM_SHA1, npmMetadata.dist?.shasum!!) + // ohpm包没有shasum字段 + npmMetadata.dist?.shasum?.let { context.putAttribute(ATTRIBUTE_OCTET_STREAM_SHA1, it) } ArtifactContextHolder.getRepository().upload(context).also { logger.info( "user [$userId] upload npm package version metadata file [$fullPath] " + @@ -432,12 +539,11 @@ class NpmClientServiceImpl( private fun handlerAttachmentsUpload( userId: String, artifactInfo: NpmArtifactInfo, - npmPackageMetaData: NpmPackageMetaData + npmPackageMetaData: NpmPackageMetaData, + artifactFile: ArtifactFile? = null, ) { - val attachmentEntry = npmPackageMetaData.attachments!!.getMap().entries.iterator().next() val versionMetadata = npmPackageMetaData.versions.map.values.iterator().next() - val fullPath = "${versionMetadata.name}/-/${attachmentEntry.key}" - val packageKey = PackageKeys.ofNpm(versionMetadata.name.orEmpty()) + val packageKey = NpmUtils.packageKeyByRepoType(versionMetadata.name.orEmpty()) val version = versionMetadata.version.orEmpty() with(artifactInfo) { // 判断包版本是否存在 如果该版本先前发布过,也不让再次发布该版本 @@ -447,26 +553,120 @@ class NpmClientServiceImpl( "You cannot publish over the previously published versions: ${versionMetadata.version}." ) } - logger.info("user [$userId] deploying npm package [$fullPath] into repo [$projectId/$repoName]") - try { - val inputStream = tgzContentToInputStream(attachmentEntry.value.data!!) - val artifactFile = inputStream.use { ArtifactFileFactory.build(it) } - val context = ArtifactUploadContext(artifactFile) - context.putAttribute(NPM_FILE_FULL_PATH, fullPath) - context.putAttribute("attachments.content_type", attachmentEntry.value.contentType!!) - context.putAttribute("attachments.length", attachmentEntry.value.length!!) - context.putAttribute("name", NPM_PACKAGE_TGZ_FILE) - // context.putAttribute(NPM_METADATA, buildProperties(versionMetadata)) - // 将attachments移除 - npmPackageMetaData.attachments = null - ArtifactContextHolder.getRepository().upload(context) - artifactFile.delete() - } catch (exception: IOException) { - logger.error( - "Failed deploying npm package [$fullPath] into repo [$projectId/$repoName] due to : $exception" + } + + if (artifactFile == null) { + // 从package metadata中获取tarball数据 + npmPackageMetaData.attachments!!.getMap().forEach { attachment -> + val fullPath = "${versionMetadata.name}/-/${attachment.key}" + val inputStream = tgzContentToInputStream(attachment.value.data!!) + val attachmentArtifactFile = inputStream.use { ArtifactFileFactory.build(it) } + handlerAttachmentsUpload( + userId, + artifactInfo, + attachment.value.contentType!!, + attachment.value.length!!, + attachmentArtifactFile, + fullPath ) + attachmentArtifactFile.delete() } + // 将attachments移除 + npmPackageMetaData.attachments = null + return } + + // 从artifactFile中获取tarball数据 + val ohpmPackageType = npmPackageMetaData.any()[OHPM_PACKAGE_TYPE] + val hspType = npmPackageMetaData.any()[HSP_TYPE] + if (ohpmPackageType != OHPM_PACKAGE_TYPE_HSP || hspType != HSP_TYPE_BUNDLE_APP) { + // har + val fullPath = NpmUtils.getTgzPath(versionMetadata.name!!, version, true, HAR_FILE_EXT) + handlerAttachmentsUpload( + userId, + artifactInfo, + MediaTypes.APPLICATION_OCTET_STREAM, + artifactFile.getSize().toInt(), + artifactFile, + fullPath + ) + return + } + + // hsp + artifactFile.getInputStream().use { artifactInputStream -> + val tgz = object : TarArchiveInputStream(GzipCompressorInputStream(artifactInputStream)) { + // 外层artifactInputStream会关闭流,此处不关闭,避免StreamArtifactFile中关闭流导致后续entry读取失败 + override fun close() {} + } + do { + val entry = tgz.nextEntry ?: break + val fileNameExt = entry.name.substring(entry.name.length - 4, entry.name.length) + val fullPath = NpmUtils.getTgzPath(versionMetadata.name!!, version, true, fileNameExt) + val tarballArtifactFile = ArtifactFileFactory.build(tgz, entry.size) + handlerAttachmentsUpload( + userId, + artifactInfo, + MediaTypes.APPLICATION_OCTET_STREAM, + entry.size.toInt(), + tarballArtifactFile, + fullPath + ) + tarballArtifactFile.delete() + } while (true) + } + } + + private fun handlerAttachmentsUpload( + userId: String, + artifactInfo: NpmArtifactInfo, + contentType: String, + contentLength: Int, + artifactFile: ArtifactFile, + fullPath: String, + ) { + val projectId = artifactInfo.projectId + val repoName = artifactInfo.repoName + logger.info("user [$userId] deploying npm package [$fullPath] into repo [$projectId/$repoName]") + try { + if (fullPath.endsWith(HAR_FILE_EXT)) { + // 保存readme,changelog文件 + val readmeDir = NpmUtils.getReadmeDirFromTarballPath(fullPath) + artifactFile.getInputStream().use { handlerOhpmReadmeAndChangelogUpload(it, readmeDir) } + } + val context = ArtifactUploadContext(artifactFile) + context.putAttribute(NPM_FILE_FULL_PATH, fullPath) + context.putAttribute("attachments.content_type", contentType) + context.putAttribute("attachments.length", contentLength) + context.putAttribute("name", NPM_PACKAGE_TGZ_FILE) + // context.putAttribute(NPM_METADATA, buildProperties(versionMetadata)) + ArtifactContextHolder.getRepository().upload(context) + } catch (exception: IOException) { + logger.error( + "Failed deploying npm package [$fullPath] into repo [$projectId/$repoName] due to : $exception" + ) + } + } + + private fun handlerOhpmReadmeAndChangelogUpload(inputStream: InputStream, readmeDir: String) { + try { + val (readme, changelog) = NpmUtils.getReadmeAndChangeLog(inputStream) + readme?.let { uploadReadmeOrChangeLog(it, "$readmeDir/$OHPM_README_FILE_NAME") } + changelog?.let { uploadReadmeOrChangeLog(it, "$readmeDir/$OHPM_CHANGELOG_FILE_NAME") } + } catch (exception: IOException) { + logger.error( + "Failed deploying npm readme [$readmeDir] due to : $exception" + ) + } + } + + private fun uploadReadmeOrChangeLog(byteArray: ByteArray, fullPath: String) { + val artifactFile = byteArray.inputStream().use { ArtifactFileFactory.build(it) } + val context = ArtifactUploadContext(artifactFile) + context.putAttribute(NPM_FILE_FULL_PATH, fullPath) + context.putAttribute("name", NPM_PACKAGE_TGZ_FILE) + ArtifactContextHolder.getRepository().upload(context) + artifactFile.delete() } private fun buildProperties(npmVersionMetadata: NpmVersionMetadata?): List { @@ -505,7 +705,7 @@ class NpmClientServiceImpl( val entry = iterator.next() val pathWithDash = entry.value.dist?.tarball?.substringAfter(npmPackageMetaData.name!!) ?.contains(TGZ_FULL_PATH_WITH_DASH_SEPARATOR) ?: true - val tgzFullPath = NpmUtils.getTgzPath(npmPackageMetaData.name!!, entry.key, pathWithDash) + val tgzFullPath = NpmUtils.getTarballPathByRepoType(npmPackageMetaData.name!!, entry.key, pathWithDash) if (entry.value.any().containsKey("deprecated")) { metadataClient.saveMetadata( MetadataSaveRequest( diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/NpmFixToolServiceImpl.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/NpmFixToolServiceImpl.kt index e452ea4f37..6db83e2349 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/NpmFixToolServiceImpl.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/NpmFixToolServiceImpl.kt @@ -13,7 +13,6 @@ import com.tencent.bkrepo.common.artifact.repository.context.ArtifactQueryContex import com.tencent.bkrepo.common.artifact.repository.context.ArtifactRemoveContext import com.tencent.bkrepo.common.artifact.repository.context.ArtifactUploadContext import com.tencent.bkrepo.common.artifact.resolve.file.ArtifactFileFactory -import com.tencent.bkrepo.common.artifact.util.PackageKeys import com.tencent.bkrepo.common.query.enums.OperationType import com.tencent.bkrepo.common.query.model.PageLimit import com.tencent.bkrepo.common.query.model.QueryModel @@ -42,6 +41,7 @@ import com.tencent.bkrepo.npm.utils.TimeUtil import com.tencent.bkrepo.repository.pojo.node.NodeInfo import com.tencent.bkrepo.repository.pojo.node.service.NodeCreateRequest import com.tencent.bkrepo.repository.pojo.packages.PackageSummary +import com.tencent.bkrepo.repository.pojo.packages.PackageType import org.slf4j.LoggerFactory import org.springframework.stereotype.Service import java.time.Duration @@ -394,7 +394,7 @@ class NpmFixToolServiceImpl( * 处理时注意tag处理,如果tag关联的版本不存在,删除该tag,如果是latest版本删除了则需要重新找个最新版本为latest版本 */ with(artifactInfo) { - val packageKey = PackageKeys.ofNpm(name) + val packageKey = NpmUtils.packageKeyByRepoType(name) val packageSummary = packageClient.findPackageByKey(projectId, repoName, packageKey).data ?: throw PackageNotFoundException("package [$name] not found") val packageInfo = queryPackageInfo(this, name, false) @@ -415,6 +415,10 @@ class NpmFixToolServiceImpl( // 删除版本索引文件 fullPathList.add(NpmUtils.getVersionPackageMetadataPath(name, it)) } + if (packageSummary.type == PackageType.OHPM) { + NpmUtils.updateLatestVersion(packageInfo) + packageInfo.rev = packageInfo.versions.map.size.toString() + } removePackageVersionMetadata(fullPathList) // 重新上传json索引文件 uploadPackageMetadata(packageInfo) diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/NpmWebServiceImpl.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/NpmWebServiceImpl.kt index 078e83f815..63e0d50ad2 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/NpmWebServiceImpl.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/service/impl/NpmWebServiceImpl.kt @@ -33,10 +33,10 @@ package com.tencent.bkrepo.npm.service.impl import com.tencent.bkrepo.common.api.util.JsonUtils import com.tencent.bkrepo.common.api.util.UrlFormatter +import com.tencent.bkrepo.common.artifact.pojo.RepositoryType import com.tencent.bkrepo.common.artifact.repository.context.ArtifactContextHolder import com.tencent.bkrepo.common.artifact.repository.context.ArtifactUploadContext import com.tencent.bkrepo.common.artifact.resolve.file.ArtifactFileFactory -import com.tencent.bkrepo.common.artifact.util.PackageKeys import com.tencent.bkrepo.npm.artifact.NpmArtifactInfo import com.tencent.bkrepo.npm.constants.LATEST import com.tencent.bkrepo.npm.constants.NPM_FILE_FULL_PATH @@ -74,14 +74,14 @@ class NpmWebServiceImpl : NpmWebService, AbstractNpmService() { @Transactional(rollbackFor = [Throwable::class]) override fun detailVersion(artifactInfo: NpmArtifactInfo, packageKey: String, version: String): PackageVersionInfo { - val name = PackageKeys.resolveNpm(packageKey) + val name = NpmUtils.resolveNameByRepoType(packageKey) val packageMetadata = queryPackageInfo(artifactInfo, name, false) if (!packageMetadata.versions.map.keys.contains(version)) { throw NpmArtifactNotFoundException("version [$version] don't found in package [$name].") } val pathWithDash = packageMetadata.versions.map[version]?.dist?.tarball?.substringAfter(name) ?.contains(TGZ_FULL_PATH_WITH_DASH_SEPARATOR) ?: true - val fullPath = NpmUtils.getTgzPath(name, version, pathWithDash) + val fullPath = NpmUtils.getTarballPathByRepoType(name, version, pathWithDash) with(artifactInfo) { checkRepositoryExist(projectId, repoName) val nodeDetail = nodeClient.getNodeDetail(projectId, repoName, fullPath).data ?: run { @@ -126,6 +126,7 @@ class NpmWebServiceImpl : NpmWebService, AbstractNpmService() { with(deleteRequest) { checkRepositoryExist(projectId, repoName) val packageMetadata = queryPackageInfo(artifactInfo, name, false) + npmClientService.checkOhpmDependentsAndDeprecate(operator, artifactInfo, packageMetadata, version) val versionEntries = packageMetadata.versions.map.entries val iterator = versionEntries.iterator() // 如果删除最后一个版本直接删除整个包 @@ -134,11 +135,11 @@ class NpmWebServiceImpl : NpmWebService, AbstractNpmService() { deletePackage(artifactInfo, deletePackageRequest) return } - val tgzPath = + val tarballPath = packageMetadata.versions.map[version]?.dist?.tarball?.substringAfterLast( artifactInfo.getRepoIdentify() ).orEmpty() - npmClientService.deleteVersion(artifactInfo, name, version, tgzPath) + npmClientService.deleteVersion(artifactInfo, name, version, tarballPath) // 修改package.json文件的内容 updatePackageWithDeleteVersion(artifactInfo, this, packageMetadata) } @@ -164,6 +165,10 @@ class NpmWebServiceImpl : NpmWebService, AbstractNpmService() { packageMetaData.time.getMap().remove(version) packageMetaData.distTags.set(LATEST, newLatest) } + if (ArtifactContextHolder.getRepoDetail()!!.type == RepositoryType.OHPM) { + NpmUtils.updateLatestVersion(packageMetaData) + packageMetaData.rev = packageMetaData.versions.map.size.toString() + } reUploadPackageJsonFile(artifactInfo, packageMetaData) } } @@ -181,7 +186,7 @@ class NpmWebServiceImpl : NpmWebService, AbstractNpmService() { private fun findNewLatest(request: PackageVersionDeleteRequest): String { return with(request) { - packageClient.findPackageByKey(projectId, repoName, PackageKeys.ofNpm(name)).data?.latest + packageClient.findPackageByKey(projectId, repoName, NpmUtils.packageKeyByRepoType(name)).data?.latest ?: run { val message = "delete version by web operator to find new latest version failed with package [$name]" diff --git a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/utils/NpmUtils.kt b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/utils/NpmUtils.kt index 4d2d739d9d..5fd5bcdd46 100644 --- a/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/utils/NpmUtils.kt +++ b/src/backend/npm/biz-npm/src/main/kotlin/com/tencent/bkrepo/npm/utils/NpmUtils.kt @@ -31,11 +31,18 @@ package com.tencent.bkrepo.npm.utils +import com.github.zafarkhaja.semver.Version import com.tencent.bkrepo.common.api.constant.CharPool.SLASH import com.tencent.bkrepo.common.api.constant.StringPool import com.tencent.bkrepo.common.api.util.UrlFormatter import com.tencent.bkrepo.common.artifact.api.ArtifactInfo +import com.tencent.bkrepo.common.artifact.pojo.RepositoryType +import com.tencent.bkrepo.common.artifact.repository.context.ArtifactContextHolder +import com.tencent.bkrepo.common.artifact.util.PackageKeys import com.tencent.bkrepo.common.service.util.HeaderUtils +import com.tencent.bkrepo.npm.constants.FILE_SUFFIX +import com.tencent.bkrepo.npm.constants.HAR_FILE_EXT +import com.tencent.bkrepo.npm.constants.HSP_FILE_EXT import com.tencent.bkrepo.npm.constants.LATEST import com.tencent.bkrepo.npm.constants.NPM_PKG_METADATA_FULL_PATH import com.tencent.bkrepo.npm.constants.NPM_PKG_TGZ_FULL_PATH @@ -43,9 +50,15 @@ import com.tencent.bkrepo.npm.constants.NPM_PKG_TGZ_WITH_DOWNLOAD_FULL_PATH import com.tencent.bkrepo.npm.constants.NPM_PKG_VERSION_METADATA_FULL_PATH import com.tencent.bkrepo.npm.constants.NPM_TGZ_TARBALL_PREFIX import com.tencent.bkrepo.npm.model.metadata.NpmPackageMetaData +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream +import org.slf4j.LoggerFactory +import java.io.InputStream object NpmUtils { + private val logger = LoggerFactory.getLogger(NpmUtils::class.java) + fun getPackageMetadataPath(packageName: String): String { return NPM_PKG_METADATA_FULL_PATH.format(packageName) } @@ -54,24 +67,29 @@ object NpmUtils { return NPM_PKG_VERSION_METADATA_FULL_PATH.format(name, name, version) } - fun getTgzPath(name: String, version: String, pathWithDash: Boolean = true): String { + fun getTgzPath(name: String, version: String, pathWithDash: Boolean = true, ext: String = FILE_SUFFIX): String { return if (pathWithDash) { - NPM_PKG_TGZ_FULL_PATH.format(name, name, version) + NPM_PKG_TGZ_FULL_PATH.format(name, name, version, ext) } else { - NPM_PKG_TGZ_WITH_DOWNLOAD_FULL_PATH.format(name, name, version) + NPM_PKG_TGZ_WITH_DOWNLOAD_FULL_PATH.format(name, name, version, ext) } } fun analyseVersionFromPackageName(filename: String, name: String): String { val unscopedName = name.substringAfterLast("/") - return filename.substringBeforeLast(".tgz").substringAfter("$unscopedName-") + val ext = if (filename.endsWith(".har")) { + ".har" + } else { + ".tgz" + } + return filename.substringBeforeLast(ext).substringAfter("$unscopedName-") } /** * 查看[tarball]里面是否使用 - 分隔符来进行分隔 */ fun isDashSeparateInTarball(name: String, version: String, tarball: String): Boolean { - val tgzPath = "/%s-%s.tgz".format(name, version) + val tgzPath = "/%s-%s".format(name, version) val separate = tarball.substringBeforeLast(tgzPath).substringAfterLast('/') return separate == StringPool.DASH } @@ -81,7 +99,7 @@ object NpmUtils { * http://xxx/helloworld/download/hellworld-1.0.0.tgz -> http://xxx/helloworld/-/hellworld-1.0.0.tgz */ fun formatTarballWithDash(name: String, version: String, tarball: String): String { - val tgzPath = "/%s-%s.tgz".format(name, version) + val tgzPath = "/%s-%s".format(name, version) val separate = tarball.substringBeforeLast(tgzPath).substringAfterLast('/') return tarball.replace("$name/$separate/$name", "$name/-/$name") } @@ -94,6 +112,16 @@ object NpmUtils { return distTags.getMap()[LATEST]!! } + fun updateLatestVersion(npmPackageMetaData: NpmPackageMetaData) { + try { + npmPackageMetaData.versions.map.keys + .maxByOrNull { Version.valueOf(it) } + ?.let { npmPackageMetaData.distTags.set(LATEST, it) } + } catch (e: Exception) { + logger.error("update latest failed, current version will be used as the latest", e) + } + } + fun parseNameAndVersionFromFullPath(artifactFullPath: String): Pair { val splitList = artifactFullPath.split('/').filter { it.isNotBlank() }.map { it.trim() }.toList() val name = if (splitList.size == 3) { @@ -153,4 +181,97 @@ object NpmUtils { } return newTarball.toString() } + + fun packageKeyByRepoType( + name: String, + repoType: RepositoryType = ArtifactContextHolder.getRepoDetail()!!.type + ): String { + return if (repoType == RepositoryType.OHPM) { + PackageKeys.ofOhpm(name) + } else { + PackageKeys.ofNpm(name) + } + } + + fun packageKey(name: String, ohpm: Boolean = false) = if (ohpm) { + PackageKeys.ofOhpm(name) + } else { + PackageKeys.ofNpm(name) + } + + fun resolveNameByRepoType( + packageKey: String, + repoType: RepositoryType = ArtifactContextHolder.getRepoDetail()!!.type + ): String { + return if (repoType == RepositoryType.OHPM) { + PackageKeys.resolveOhpm(packageKey) + } else { + PackageKeys.resolveNpm(packageKey) + } + } + + fun getTarballPathByRepoType( + name: String, + version: String, + pathWithDash: Boolean = true, + repoType: RepositoryType = ArtifactContextHolder.getRepoDetail()!!.type + ): String { + val ext = getContentFileExt(repoType == RepositoryType.OHPM) + return getTgzPath(name, version, pathWithDash, ext) + } + + /** + * 转换har tarball url为 hsp url + * + * demo-1.0.0.har -> demo-1.0.0.hsp + */ + fun harPathToHspPath(harTarball: String): String { + return harTarball.substring(0, harTarball.length - 4) + HSP_FILE_EXT + } + + fun getContentPath(name: String, version: String, ohpm: Boolean): String { + return String.format(NPM_PKG_TGZ_FULL_PATH, name, name, version, getContentFileExt(ohpm)) + } + + fun getContentFileExt(ohpm: Boolean) = if (ohpm) { + HAR_FILE_EXT + } else { + FILE_SUFFIX + } + + /** + * 从har包中解析出readme与changelog文件数据 + * + * @param inputStream har文件,由调用方负责关闭 + * @return (readme,changelog),不存在时为null + */ + fun getReadmeAndChangeLog(inputStream: InputStream): Pair { + var changelog: ByteArray? = null + var readme: ByteArray? = null + val tar = TarArchiveInputStream(GzipCompressorInputStream(inputStream)) + do { + val entry = tar.nextEntry + // package最外层目录未找到readme时放弃搜索 + if (entry == null || entry.name.endsWith("/")) { + break + } + if (entry.name == "package/README.md" || entry.name == "package/readme.md") { + readme = tar.readBytes() + } + if (entry.name == "package/CHANGELOG.md" || entry.name == "package/changelog.md") { + changelog = tar.readBytes() + } + if (changelog != null && readme != null) { + break + } + } while (true) + return Pair(readme, changelog) + } + + /** + * 移除文件后缀作为readme文件存放目录,例:demo-1.0.0.har -> demo-1.0.0 + */ + fun getReadmeDirFromTarballPath(tarballPath: String): String { + return tarballPath.substring(0, tarballPath.length - 4) + } } diff --git a/src/backend/replication/biz-replication/src/main/kotlin/com/tencent/bkrepo/replication/replica/repository/internal/type/NpmPackageNodeMapper.kt b/src/backend/replication/biz-replication/src/main/kotlin/com/tencent/bkrepo/replication/replica/repository/internal/type/NpmPackageNodeMapper.kt index 2dcdbc28ba..ba5f0ee981 100644 --- a/src/backend/replication/biz-replication/src/main/kotlin/com/tencent/bkrepo/replication/replica/repository/internal/type/NpmPackageNodeMapper.kt +++ b/src/backend/replication/biz-replication/src/main/kotlin/com/tencent/bkrepo/replication/replica/repository/internal/type/NpmPackageNodeMapper.kt @@ -44,7 +44,11 @@ class NpmPackageNodeMapper : PackageNodeMapper { packageVersion: PackageVersion, type: RepositoryType ): List { - val name = PackageKeys.resolveNpm(packageSummary.key) + val name = if (type == RepositoryType.OHPM) { + PackageKeys.resolveOhpm(packageSummary.key) + } else { + PackageKeys.resolveNpm(packageSummary.key) + } val version = packageVersion.name return listOf( NPM_PKG_TGZ_FULL_PATH.format(name, name, version), diff --git a/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/packages/PackageType.kt b/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/packages/PackageType.kt index 6d57e923fc..d8233af378 100644 --- a/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/packages/PackageType.kt +++ b/src/backend/repository/api-repository/src/main/kotlin/com/tencent/bkrepo/repository/pojo/packages/PackageType.kt @@ -46,5 +46,6 @@ enum class PackageType { NUGET, GIT, CONAN, - OCI + OCI, + OHPM }