diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillPublishController.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillPublishController.java index f03be1e6..7e214131 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillPublishController.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/controller/portal/SkillPublishController.java @@ -49,7 +49,8 @@ public class SkillPublishController extends BaseApiController { throw new DomainForbiddenException("error.auth.required"); } try { - return ok("response.success.read", packagePreview.preview(skillPackageArchiveExtractor.extract(file))); + return ok("response.success.read", packagePreview.preview( + skillPackageArchiveExtractor.extractWithWarnings(file).entries())); } catch (IllegalArgumentException exception) { throw new DomainBadRequestException("error.skill.publish.package.invalid", exception.getMessage()); } diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/portal/SkillPublishControllerTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/portal/SkillPublishControllerTest.java index d5b5f737..57701679 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/portal/SkillPublishControllerTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/controller/portal/SkillPublishControllerTest.java @@ -261,6 +261,33 @@ class SkillPublishControllerTest { org.mockito.Mockito.verifyNoInteractions(skillPublishService); } + @Test + void packagePreviewNormalizesNestedSkillAlongsideRootReadmeLikePublish() throws Exception { + byte[] archive; + try (var output = new ByteArrayOutputStream(); var zip = new ZipOutputStream(output)) { + zip.putNextEntry(new ZipEntry("README.md")); + zip.write("Repository overview".getBytes(StandardCharsets.UTF_8)); + zip.closeEntry(); + zip.putNextEntry(new ZipEntry("foo/SKILL.md")); + zip.write("---\nname: foo\ndescription: Nested skill\nversion: 1.0.0\nmetadata:\n display-name: 嵌套技能\n---\nBody".getBytes(StandardCharsets.UTF_8)); + zip.closeEntry(); + zip.finish(); + archive = output.toByteArray(); + } + var principal = new PlatformPrincipal("preview_nested", "publisher", "publisher@example.com", "", "local", Set.of()); + var auth = new UsernamePasswordAuthenticationToken(principal, null, List.of(new SimpleGrantedAuthority("ROLE_USER"))); + for (String base : List.of("/api/v1", "/api/web")) { + mockMvc.perform(multipart(base + "/skills/package-preview") + .file(new MockMultipartFile("file", "skill.zip", "application/zip", archive)) + .with(authentication(auth)).with(csrf())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.name").value("foo")) + .andExpect(jsonPath("$.data.displayName").value("嵌套技能")) + .andExpect(jsonPath("$.data.description").value("Nested skill")); + } + org.mockito.Mockito.verifyNoInteractions(skillPublishService); + } + @Test void packagePreviewRejectsAnonymousWithoutPublishing() throws Exception { mockMvc.perform(multipart("/api/v1/skills/package-preview") diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index 5ba6be87..8fec1ae6 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1831,6 +1831,7 @@ "copyright": "© 2026 SkillHub. Apache-2.0." }, "publish": { + "packageName": "Package name", "displayName": "Display name", "displayNamePlaceholder": "For example: Meeting notes assistant", "displayNameHelp": "Up to 200 characters. Defaults to the package title. Changes follow this version’s review and do not alter the identifier or package files. Leave blank to use the package title.", diff --git a/web/src/i18n/locales/ru.json b/web/src/i18n/locales/ru.json index dff63a08..ef4b6248 100644 --- a/web/src/i18n/locales/ru.json +++ b/web/src/i18n/locales/ru.json @@ -1822,6 +1822,7 @@ "goToPage": "Перейти на страницу {{page}}" }, "publish": { + "packageName": "Имя в пакете", "displayName": "Отображаемое имя", "displayNamePlaceholder": "Например: Помощник для протоколов встреч", "displayNameHelp": "До 200 символов. По умолчанию — имя из пакета. Изменения проходят проверку версии и не меняют идентификатор или файлы пакета. Пустое поле означает имя из пакета.", diff --git a/web/src/i18n/locales/zh.json b/web/src/i18n/locales/zh.json index d165fe9e..dc45fd32 100644 --- a/web/src/i18n/locales/zh.json +++ b/web/src/i18n/locales/zh.json @@ -1830,6 +1830,7 @@ "copyright": "© 2026 SkillHub. Apache-2.0." }, "publish": { + "packageName": "包内名称", "displayName": "显示名称", "displayNamePlaceholder": "例如:会议纪要助手", "displayNameHelp": "支持中文,最多 200 个字符。默认读取包内名称;修改后随本次版本审核,不改变技能标识或包内文件。留空则使用包内名称。", diff --git a/web/src/pages/dashboard/publish.tsx b/web/src/pages/dashboard/publish.tsx index 1c8f7d1b..85cbd695 100644 --- a/web/src/pages/dashboard/publish.tsx +++ b/web/src/pages/dashboard/publish.tsx @@ -330,7 +330,7 @@ export function PublishPage() {
{t('publish.packagePreviewFailed')}
} - {selectedFile && packagePreview.data &&@{namespaceSlug || '…'}/{packagePreview.data.name}
} + {selectedFile && packagePreview.data &&{t('publish.packageName')}: {packagePreview.data.name}