如下腳本內容為 idea 插件開發項目中的 build.gradle.kts
文件示例,其中自定了 updatePluginsXmlToNexus
和 uploadPluginToNexus
兩個任務,一個用來自動修改 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("")// 更新 plugins.xml 的任務val updatePluginsXmlToNexus = register("updatePluginsXmlToNexus") {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)// 下載現有的 plugins.xmlval tempFile = File.createTempFile("plugins", ".xml")try {client.newCall(Request.Builder().url("$nexusBaseUrl/repository/raw-public/idea-plugin/plugins.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' 在 plugins.xml 中不存在,請先在 plugins.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", "plugins.xml").addFormDataPart("raw.asset1","plugins.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, updatePluginsXmlToNexus)}
}sourceSets {main {resources {// 指定內容區的前端靜態資源目錄,這個決定了最終被作為資源打包到插件中srcDir("${project.projectDir}/editor-web/dist")}}
}
(END)