当前位置: 首页 > backend >正文

idea 插件开发自动发布到 nexus 私服中(脚本实例)

如下脚本内容为 idea 插件开发项目中的 build.gradle.kts 文件示例,其中自定了 updatePluginsXmluploadPluginToNexus 两个任务,一个用来自动修改 nexus 中的配置文件,一个用来自动将当前插件打包后的 zip 文件上传到 nexus 私服中。

脚本仅供参考:

import okhttp3.Credentials
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.asRequestBody
import org.dom4j.io.OutputFormat
import org.dom4j.io.SAXReader
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.w3c.dom.Element
import java.io.StringReader
import java.io.StringWriter
import java.nio.file.Files
import java.nio.file.Paths
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import javax.xml.xpath.XPathConstants
import javax.xml.xpath.XPathFactoryplugins {id("java") // 使用Java插件id("org.jetbrains.kotlin.jvm") version "2.1.20-RC" // 使用Kotlin JVM插件,指定版本id("org.jetbrains.intellij.platform") version "2.4.0" // 使用IntelliJ插件,用于开发JetBrains插件,指定版本id("com.github.node-gradle.node") version "7.1.0" // Node.js插件,集成Node.js和npm到Gradle构建中
}
buildscript {repositories {mavenCentral()}dependencies {// 添加 OkHttp 依赖classpath("com.squareup.okhttp3:okhttp:4.12.0")classpath("org.dom4j:dom4j:2.1.4")}
}
repositories {// mavenCentral() // 定义Maven中央仓库作为依赖库来源mavenLocal()intellijPlatform {defaultRepositories()}maven(url = "https://plugins.gradle.org/m2/")maven(url = "https://oss.sonatype.org/content/repositories/releases/")
}dependencies {compileOnly("org.projectlombok:lombok:1.18.36") // 提供Lombok支持,仅在编译时使用annotationProcessor("org.projectlombok:lombok:1.18.36") // Lombok注解处理器intellijPlatform {// 获取在gradle.properties中配置的参数platformVersionval version = providers.gradleProperty("platformVersion") // 2023.2.6// IntellijIdeaCommunity=IC, IntellijIdeaUltimate=IUcreate(org.jetbrains.intellij.platform.gradle.IntelliJPlatformType.IntellijIdeaCommunity, version)// 使用bundledPlugin设置捆绑的插件//bundledPlugin("com.intellij.java")// 使用plugin设置当前插件依赖的其他插件//plugin("org.intellij.scala", "2024.1.4")pluginVerifier()testFramework(org.jetbrains.intellij.platform.gradle.TestFrameworkType.Platform)}
}group = "com.shanhy.plugin"
version = "1.0.0-20250520"// 文档 https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html#intellijPlatform
intellijPlatform {pluginConfiguration {id = "demo-idea-apiflow-designer"name = "demo ApiFlow Designer" // 名称// 直接使用项目级别的 version 属性version = project.version.toString()description ="该插件时一个为 IntelliJ IDEA 打造的 demo Http 接口设计器插件。它提供了一个 `.apiflow` 文件编辑器,具有可视化设计快速开发 Http 接口的能力。"// 读取 CHANGELOG.md 文件的内容changeNotes = Files.readString(Paths.get(project.projectDir.path, "CHANGELOG.md"))// 适用的idea版本ideaVersion {sinceBuild = "232" // 插件支持的最低IDE构建号untilBuild = "251.*" // 插件支持的最高IDE构建号}// 产品描述productDescriptor {// ...}// 开发者信息vendor {name = "shanhy"email = "shanhy@shanhy.com"url = "http://www.shanhy.com"}}// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html#intellijPlatform-signingsigning {// 配置在gradle.properties中certificateChain = providers.gradleProperty("certificateChain") // 签名证书链privateKey = providers.gradleProperty("privateKey") // 签名私钥password = providers.gradleProperty("privateKeyPassword") // 签名私钥的密码}// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html#intellijPlatform-publishingpublishing {token = providers.gradleProperty("publishToken") // 发布插件时使用的令牌}// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html#intellijPlatform-pluginVerificationpluginVerification {// ...}
}node {version.set("20.11.1") // Node.js版本设置download.set(true) // 自动下载Node.js
}tasks {// 设置JVM兼容性版本withType<JavaCompile> {sourceCompatibility = JvmTarget.JVM_17.target // Java源代码兼容版本targetCompatibility = JvmTarget.JVM_17.target // 编译目标版本}withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {compilerOptions {jvmTarget.set(JvmTarget.JVM_17) // Kotlin编译目标版本}}val buildNpm = register<com.github.gradle.node.npm.task.NpmTask>("buildNpm") {args.set(listOf("run", "build")) // NPM任务参数,运行npm buildworkingDir.set(file("${project.projectDir}/editor-web")) // 设置工作目录}buildPlugin {// 构建插件前先执行NPM构建任务// 如果你的前端页面没有做修改,在反复进行插件开发打包测试,可以注释掉这个构建依赖,不然每次都重复构建没有修改的前端浪费时间dependsOn(buildNpm)}// 定义 Nexus 相关配置val nexusBaseUrl = providers.gradleProperty("nexusBaseUrl").getOrElse("https://nexus.shanhy.com")val nexusUsername = providers.gradleProperty("nexusUsername").getOrElse("")val nexusPassword = providers.gradleProperty("nexusPassword").getOrElse("")// 更新 updatePlugins.xml 的任务val updatePluginsXml = register("updatePluginsXml") {group = "deployment"description = "更新 Nexus 上的 pluginsXml 文件版本信息"// 使用 Provider 延迟获取所有值val pluginId = providers.provider { intellijPlatform.pluginConfiguration.id.get() }val pluginVersion = providers.provider { project.version.toString() }val sinceBuild = providers.provider { intellijPlatform.pluginConfiguration.ideaVersion.sinceBuild.get() }val changelogFile = providers.provider { layout.projectDirectory.file("CHANGELOG.md").asFile }val projectName = providers.provider { project.name }outputs.upToDateWhen { false } // 总是执行更新doLast {// 在任务执行时获取所有需要的值val pluginIdValue = pluginId.get()val pluginVersionValue = pluginVersion.get()val sinceBuildValue = sinceBuild.get()val changelogFileValue = changelogFile.get()val projectNameValue = projectName.get()// 读取 CHANGELOG.md 内容val changelogContent = changelogFileValue.readText()val client = OkHttpClient.Builder().followRedirects(true).followSslRedirects(true).build()// 创建认证信息val credentials = Credentials.basic(nexusUsername, nexusPassword)// 下载现有的 updatePlugins.xmlval tempFile = File.createTempFile("updatePlugins", ".xml")try {client.newCall(Request.Builder().url("$nexusBaseUrl/repository/raw-public/idea-plugin/updatePlugins.xml").build()).execute().use { response ->if (!response.isSuccessful) {val errorBody = response.body?.string() ?: "无错误详情"throw GradleException("下载文件失败: ${response.code} ${response.message}\n错误详情: $errorBody")}val contentType = response.header("Content-Type", "")if (contentType != null) {if (!contentType.contains("xml") && !contentType.contains("text/plain")) {println("警告: 响应Content-Type不是XML: $contentType")}}response.body?.byteStream()?.use { input ->tempFile.outputStream().use { output ->input.copyTo(output)}} ?: throw GradleException("下载文件失败: 响应体为空")}// 检查下载的文件if (!tempFile.exists() || tempFile.length() == 0L) {throw GradleException("下载文件失败,文件不存在或为空")}// 读取文件内容进行检查val content = tempFile.readText()// 验证下载的文件是否为有效的 XMLtry {DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(tempFile)} catch (e: Exception) {throw GradleException("下载的文件不是有效的 XML: ${e.message}\n文件内容: ${content}...")}// 读取并解析 XMLval document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(tempFile)val xpath = XPathFactory.newInstance().newXPath()// 查找对应的插件节点val pluginNode = xpath.evaluate("//plugin[@id='$pluginIdValue']", document, XPathConstants.NODE)as? Element?: throw GradleException("插件 '$pluginIdValue' 在 updatePlugins.xml 中不存在,请先在 updatePlugins.xml 中手动添加插件配置")// 更新 plugin 节点的属性pluginNode.setAttribute("url", "$nexusBaseUrl/repository/raw-hosted/idea-plugin/files/$projectNameValue-$pluginVersionValue.zip")pluginNode.setAttribute("version", pluginVersionValue)// 更新 idea-version 节点val ideaVersionNode = xpath.evaluate(".//idea-version", pluginNode, XPathConstants.NODE) as? ElementideaVersionNode?.setAttribute("since-build", sinceBuildValue)// 更新或创建 change-notes 节点val changeNotesNode = xpath.evaluate(".//change-notes", pluginNode, XPathConstants.NODE) as? Elementif (changeNotesNode != null) {while (changeNotesNode.hasChildNodes()) {changeNotesNode.removeChild(changeNotesNode.firstChild)}val cdataNode = document.createCDATASection(changelogContent)changeNotesNode.appendChild(cdataNode)}// 保存更新后的文件val transformer = TransformerFactory.newInstance().newTransformer()transformer.setOutputProperty(OutputKeys.INDENT, "yes")transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8")transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")// 使用 StringWriter 先获取转换后的内容val writer = StringWriter()transformer.transform(DOMSource(document),StreamResult(writer))// 使用 dom4j 重新格式化 XML,去除xml中的多余空行和美化xml格式val xmlContent = writer.toString()val reader = SAXReader()val dom4jDocument = reader.read(StringReader(xmlContent))// 创建格式化配置val format = OutputFormat.createPrettyPrint().apply {encoding = "UTF-8"              // 设置输出编码isNewlines = true              // 每个节点换行isTrimText = true              // 是否去除元素中的文本前后空白isPadText = true               // 补齐空白以对齐标签isExpandEmptyElements = true   // <tag/> 变成 <tag></tag>isSuppressDeclaration = false  // 是否省略头部 <?xml ...?> 声明isOmitEncoding = false         // 是否省略 encoding 属性}// 将格式化后的 XML 写入字符串val stringWriter = StringWriter()val xmlWriter = org.dom4j.io.XMLWriter(stringWriter, format)xmlWriter.write(dom4jDocument)xmlWriter.close()// 将清理后的内容写入临时文件tempFile.writeText(stringWriter.toString())// 上传新文件println("正在上传新文件...")val requestBody = MultipartBody.Builder().setType(MultipartBody.FORM).addFormDataPart("raw.directory", "/idea-plugin").addFormDataPart("raw.asset1.filename", "updatePlugins.xml").addFormDataPart("raw.asset1","updatePlugins.xml","application/xml".toMediaTypeOrNull()?.let { mediaType ->tempFile.asRequestBody(mediaType)} ?: throw GradleException("无法创建请求体")).build()val uploadRequest = Request.Builder().url("$nexusBaseUrl/service/rest/v1/components?repository=raw-hosted").post(requestBody).header("Authorization", credentials).build()client.newCall(uploadRequest).execute().use { response ->if (!response.isSuccessful) {throw GradleException("上传文件失败: ${response.code} ${response.message}")}}} finally {tempFile.delete()}}}// 上传 zip 文件到 Nexus 的任务val uploadPluginToNexus = register("uploadPluginToNexus") {group = "deployment"description = "上传插件 zip 文件到 Nexus"// 声明任务的输入和输出,以支持配置缓存val zipFileName = "${project.name}-${project.version}.zip"val zipFile = layout.buildDirectory.file("distributions/$zipFileName").get().asFileinputs.file(zipFile)outputs.upToDateWhen { false } // 总是执行上传dependsOn(buildPlugin)doLast {if (!zipFile.exists()) {throw GradleException("插件 zip 文件未找到: ${zipFile.absolutePath}")}val client = okhttp3.OkHttpClient.Builder().followRedirects(true).followSslRedirects(true).build()// 创建认证信息val credentials = okhttp3.Credentials.basic(nexusUsername, nexusPassword)// 构建上传请求val requestBody = okhttp3.MultipartBody.Builder().setType(okhttp3.MultipartBody.FORM).addFormDataPart("raw.directory", "/idea-plugin/files").addFormDataPart("raw.asset1.filename", zipFileName).addFormDataPart("raw.asset1",zipFileName,"application/zip".toMediaTypeOrNull()?.let { mediaType ->zipFile.asRequestBody(mediaType)} ?: throw GradleException("无法创建请求体")).build()val uploadRequest = Request.Builder().url("$nexusBaseUrl/service/rest/v1/components?repository=raw-hosted").post(requestBody).header("Authorization", credentials).build()client.newCall(uploadRequest).execute().use { response ->if (!response.isSuccessful) {val errorBody = response.body?.string() ?: "无错误详情"throw GradleException("上传文件失败: ${response.code} ${response.message}\n错误详情: $errorBody")}println("插件文件上传成功: $zipFileName")}}}// 组合任务:部署到 Nexusregister("deployToNexus") {group = "deployment"description = "部署插件到 Nexus 仓库"dependsOn(uploadPluginToNexus, updatePluginsXml)}
}sourceSets {main {resources {// 指定内容区的前端静态资源目录,这个决定了最终被作为资源打包到插件中srcDir("${project.projectDir}/editor-web/dist")}}
}

(END)

http://www.xdnf.cn/news/7525.html

相关文章:

  • 界面控件DevExpress WinForms v24.2——PDF Viewer功能升级
  • Visual Studio 2019/2022:当前不会命中断点,还没有为该文档加载任何符号。
  • 基于海绵结构的密码杂凑算法Master
  • 云原生主要架构模式
  • C++(4)if的终极使用 +三目运算符
  • Java 08集合
  • 网络安全之网络攻击spring临时文件利用
  • 2024年热门AI趋势及回顾
  • CPQ报价系统多层战略,加快企业销售周期
  • 利用Spring Boot和Redis构建高性能缓存系统
  • List优雅分组
  • 开源CMS系统中哪些常见的安全漏洞最需要注意?
  • AWS CodePipeline+ Elastic Beanstalk(AWS中国云CI/CD)
  • HCIP实验五
  • MyBatis实战指南(一)MyBatis入门基础与利用IDEA从零开始搭建你的第一个MyBatis系统
  • linux关闭某端口暂用的进程
  • 【前端开发】Uniapp日期时间选择器:实现分钟动态步长设置
  • 链表面试题9之环形链表进阶
  • 微服务架构中的多进程通信--内存池、共享内存、socket
  • Canvas SVG BpmnJS编辑器中Canvas与SVG职能详解
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Rotating Navigation (旋转导航)
  • 新浪《经济新闻》丨珈和科技联合蒲江政府打造“数字茶园+智能工厂+文旅综合体“创新模式
  • Python、Pytorch、TensorFlow、Anconda、PySide、Jupyter
  • 欧拉系统离线部署docker
  • iOS苹果和Android安卓测试APP应用程序的区别差异
  • 【Linux】进程间通信(三):命名管道
  • 嵌入式开发学习日志(linux系统编程--文件读写函数)Day24
  • vr制作公司提供什么服务?
  • Linux跨网络通信中IP与MAC的作用
  • Electron+vite+vue3 从0到1搭建项目,开发Win、Mac客户端