From 73327ba3e085477203e7abd960a9a1644ccd625e Mon Sep 17 00:00:00 2001 From: Yao Jingxi <23722032@bjtu.edu.cn> Date: Thu, 5 Nov 2026 19:16:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(new-project):=20=E6=8E=A5=E6=89=8B=20Java?= =?UTF-8?q?=20Support=20=E7=9A=84=20Maven=20=E6=96=B0=E5=BB=BA=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E5=85=A5=E5=8F=A3=E3=80=81Archetype=20=E4=B8=8E?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maven 的新建项目入口(maven-java)和 Maven Archetype 生成器从 Java Support 移到本插件:模板与图标逐字节搬来,字段、条件和步骤与原定义一致。打包脚本检查每个入口引用的图标和模板目录确实在包内。 --- NOTICE | 5 + README.md | 13 +- .../icons/maven-project.svg | 4 + .../icons/maven-project_dark.svg | 4 + io.github.lithe-idea.maven-build/plugin.json | 206 +++++++++++++++++- .../templates/java/maven/dot-gitignore | 19 ++ .../templates/java/maven/pom.xml | 43 ++++ .../standard-empty/src/main/java/dot-gitkeep | 0 .../src/main/resources/dot-gitkeep | 0 .../standard-empty/src/test/java/dot-gitkeep | 0 .../java/standard-sample-main/Main.java | 7 + .../src/main/resources/dot-gitkeep | 0 .../standard-sample/src/test/java/dot-gitkeep | 0 scripts/package.mjs | 20 +- 14 files changed, 317 insertions(+), 4 deletions(-) create mode 100644 io.github.lithe-idea.maven-build/icons/maven-project.svg create mode 100644 io.github.lithe-idea.maven-build/icons/maven-project_dark.svg create mode 100644 io.github.lithe-idea.maven-build/templates/java/maven/dot-gitignore create mode 100644 io.github.lithe-idea.maven-build/templates/java/maven/pom.xml create mode 100644 io.github.lithe-idea.maven-build/templates/java/standard-empty/src/main/java/dot-gitkeep create mode 100644 io.github.lithe-idea.maven-build/templates/java/standard-empty/src/main/resources/dot-gitkeep create mode 100644 io.github.lithe-idea.maven-build/templates/java/standard-empty/src/test/java/dot-gitkeep create mode 100644 io.github.lithe-idea.maven-build/templates/java/standard-sample-main/Main.java create mode 100644 io.github.lithe-idea.maven-build/templates/java/standard-sample/src/main/resources/dot-gitkeep create mode 100644 io.github.lithe-idea.maven-build/templates/java/standard-sample/src/test/java/dot-gitkeep diff --git a/NOTICE b/NOTICE index a81ec60..c8828eb 100644 --- a/NOTICE +++ b/NOTICE @@ -11,6 +11,11 @@ Version 2.0, at revision 158b854d47ac9af2bbd99c0d7c354a5875cb6f81: offline.svg platform/icons/src/actions/offlineMode.svg skipTests.svg platform/icons/src/expui/run/showIgnored.svg +maven-project.svg and maven-project_dark.svg, the icons of the New Project +entries, come from the same repository's Maven plugin resources (plugins/maven +resources, moved from the Java Support plugin), under the Apache License, +Version 2.0. Their exact file paths and revision are not recorded. + Copyright 2000-2024 JetBrains s.r.o. and contributors. The backend includes the Apache-2.0 licensed official Lithe Java plugin SDK diff --git a/README.md b/README.md index 74474c4..12b8bc9 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,16 @@ | 快照 | 项目和依赖保存在工作区状态目录,重新打开项目时直接显示;POM、`.mvn/` 配置、settings.xml、选项或 Maven/Java 变化后才重新运行 Maven,Reload 总是重新运行 | | 同步 | `pom.xml`、`.mvn/maven.config`、`.mvn/extensions.xml` 变化后重新读取项目 | +## 新建项目 + +插件在 **New Project** 窗口提供两个条目: + +- **Maven**:生成 Maven Java 项目,可选示例代码,可设置 GroupId 和 ArtifactId。 +- **Maven Archetype**:用内置原型或自定义原型,由 `mvn` 生成项目。 + +模板在 `io.github.lithe-idea.maven-build/templates/java/`,从 Java Support 插件移来。 +生成的项目需要 Java Support 提供 Java 语言支持。 + ## 依赖的程序 插件不带 Maven,使用你项目或电脑上的 Maven,按以下顺序查找: @@ -92,7 +102,8 @@ Windows 的日志实现仍为 v1,未声明 Windows v2 已完成验收。 ```text io.github.lithe-idea.maven-build/plugin.json # 插件描述,目录名必须等于插件 id -io.github.lithe-idea.maven-build/icons/ # 工具窗口和工具栏图标(来源见 NOTICE) +io.github.lithe-idea.maven-build/icons/ # 工具窗口、工具栏和新建项目图标(来源见 NOTICE) +io.github.lithe-idea.maven-build/templates/ # 新建项目的文件模板(Maven 项目),打包时检查路径是否存在 extension/ # Maven 核心扩展,导出模型和依赖树 backend/ # 构建后端(Java 8,Lithe 插件 SDK),快照和 Maven 运行 vendor/ # 官方 Java SDK 生产源码和许可证(不带测试、JAR、target) diff --git a/io.github.lithe-idea.maven-build/icons/maven-project.svg b/io.github.lithe-idea.maven-build/icons/maven-project.svg new file mode 100644 index 0000000..bb88b7a --- /dev/null +++ b/io.github.lithe-idea.maven-build/icons/maven-project.svg @@ -0,0 +1,4 @@ + + + + diff --git a/io.github.lithe-idea.maven-build/icons/maven-project_dark.svg b/io.github.lithe-idea.maven-build/icons/maven-project_dark.svg new file mode 100644 index 0000000..e26ebc1 --- /dev/null +++ b/io.github.lithe-idea.maven-build/icons/maven-project_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/io.github.lithe-idea.maven-build/plugin.json b/io.github.lithe-idea.maven-build/plugin.json index ecafdac..ab73a4b 100644 --- a/io.github.lithe-idea.maven-build/plugin.json +++ b/io.github.lithe-idea.maven-build/plugin.json @@ -4,13 +4,13 @@ "displayName": "Maven", "version": "0.1.2", "pluginAPIVersion": 3, - "requires": { "buildProviders": 1, "buildSystems": 1, "serverProcess": 1 }, + "requires": { "buildProviders": 1, "buildSystems": 1, "projectGenerators": 1, "serverProcess": 1 }, "vendor": { "id": "io.github.lithe-idea", "displayName": "Lithe Community", "signatureRequirement": "none" }, - "permissions": ["languageServer.launch", "process.build"], + "permissions": ["languageServer.launch", "process.build", "project.generate"], "activationEvents": ["onBuildSystem:maven"], "languages": [], "buildSystems": [ @@ -288,5 +288,207 @@ "options": { "profiles": "profiles", "settingsFile": "settingsFile", "localRepository": "localRepository" } } } + ], + "projectGenerators": [ + { + "id": "maven-java", + "displayName": "Maven", + "description": "A Java project built with Maven.", + "section": "project", + "order": 20, + "icon": { "path": "icons/maven-project.svg", "darkPath": "icons/maven-project_dark.svg", "mode": "original" }, + "fields": [ + { + "id": "jdk", + "type": "runtime", + "title": "JDK", + "runtime": "java", + "minimumVersion": "11" + }, + { + "id": "addSampleCode", + "type": "boolean", + "title": "Add sample code", + "default": true + }, + { + "id": "groupId", + "type": "text", + "title": "GroupId", + "description": "Usually the reversed domain name of your organization, such as com.example.", + "advanced": true, + "default": "org.example", + "pattern": "[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*", + "maxLength": 200 + }, + { + "id": "artifactId", + "type": "text", + "title": "ArtifactId", + "description": "The name of the artifact the build produces.", + "advanced": true, + "default": "${project.name}", + "pattern": "[A-Za-z0-9_][A-Za-z0-9_.-]*", + "maxLength": 200 + } + ], + "requirements": [ + { "languageID": "java" }, + { "buildSystemID": "maven" } + ], + "steps": [ + { "kind": "files", "source": "templates/java/maven" }, + { + "kind": "files", + "source": "templates/java/standard-sample", + "when": { "field": "addSampleCode", "equals": true } + }, + { + "kind": "files", + "source": "templates/java/standard-sample-main", + "target": "src/main/java/${field.groupId|package|path}", + "when": { "field": "addSampleCode", "equals": true } + }, + { + "kind": "files", + "source": "templates/java/standard-empty", + "when": { "field": "addSampleCode", "equals": false } + } + ], + "openFiles": [ + { + "path": "src/main/java/${field.groupId|package|path}/Main.java", + "when": { "field": "addSampleCode", "equals": true } + } + ] + }, + { + "id": "maven-archetype", + "displayName": "Maven Archetype", + "description": "A project generated by Maven from an archetype.", + "section": "generator", + "order": 10, + "icon": { "path": "icons/maven-project.svg", "darkPath": "icons/maven-project_dark.svg", "mode": "original" }, + "fields": [ + { + "id": "jdk", + "type": "runtime", + "title": "JDK", + "description": "Maven runs with this JDK, and the new project uses it.", + "runtime": "java", + "minimumVersion": "11" + }, + { + "id": "archetype", + "type": "enum", + "title": "Archetype", + "presentation": "menu", + "default": "maven-archetype-quickstart", + "options": [ + { "value": "maven-archetype-quickstart", "title": "maven-archetype-quickstart" }, + { "value": "maven-archetype-webapp", "title": "maven-archetype-webapp" }, + { "value": "maven-archetype-simple", "title": "maven-archetype-simple" }, + { "value": "maven-archetype-j2ee-simple", "title": "maven-archetype-j2ee-simple" }, + { "value": "maven-archetype-plugin", "title": "maven-archetype-plugin" }, + { "value": "maven-archetype-archetype", "title": "maven-archetype-archetype" }, + { "value": "maven-archetype-site-simple", "title": "maven-archetype-site-simple" }, + { "value": "custom", "title": "Custom archetype…" } + ] + }, + { + "id": "archetypeGroupId", + "type": "text", + "title": "Archetype GroupId", + "pattern": "[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_-]*)*", + "maxLength": 200, + "placeholder": "org.example.archetypes", + "visibleWhen": { "field": "archetype", "equals": "custom" } + }, + { + "id": "archetypeArtifactId", + "type": "text", + "title": "Archetype ArtifactId", + "pattern": "[A-Za-z0-9_][A-Za-z0-9_.-]*", + "maxLength": 200, + "placeholder": "my-archetype", + "visibleWhen": { "field": "archetype", "equals": "custom" } + }, + { + "id": "archetypeVersion", + "type": "text", + "title": "Archetype version", + "description": "Leave empty to use the latest version Maven finds in its catalog.", + "optional": true, + "pattern": "[A-Za-z0-9_.-]+", + "maxLength": 64, + "placeholder": "Latest" + }, + { + "id": "groupId", + "type": "text", + "title": "GroupId", + "advanced": true, + "default": "org.example", + "pattern": "[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*", + "maxLength": 200 + }, + { + "id": "artifactId", + "type": "text", + "title": "ArtifactId", + "advanced": true, + "default": "${project.name}", + "pattern": "[A-Za-z0-9_][A-Za-z0-9_.-]*", + "maxLength": 200 + }, + { + "id": "version", + "type": "text", + "title": "Version", + "advanced": true, + "default": "1.0-SNAPSHOT", + "pattern": "[A-Za-z0-9_.-]+", + "maxLength": 64 + }, + { + "id": "packageName", + "type": "text", + "title": "Package", + "advanced": true, + "default": "${field.groupId}.${field.artifactId|package}", + "pattern": "[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*", + "maxLength": 200 + } + ], + "requirements": [ + { "languageID": "java" }, + { "buildSystemID": "maven" } + ], + "steps": [ + { + "kind": "command", + "command": "mvn", + "arguments": [ + "-B", + "org.apache.maven.plugins:maven-archetype-plugin:3.3.1:generate", + "-DinteractiveMode=false", + { "value": "-DarchetypeGroupId=org.apache.maven.archetypes", "when": { "field": "archetype", "notEquals": "custom" } }, + { "value": "-DarchetypeArtifactId=${field.archetype}", "when": { "field": "archetype", "notEquals": "custom" } }, + { "value": "-DarchetypeGroupId=${field.archetypeGroupId}", "when": { "field": "archetype", "equals": "custom" } }, + { "value": "-DarchetypeArtifactId=${field.archetypeArtifactId}", "when": { "field": "archetype", "equals": "custom" } }, + { "value": "-DarchetypeVersion=${field.archetypeVersion}", "when": { "field": "archetypeVersion", "notEquals": "" } }, + "-DgroupId=${field.groupId}", + "-DartifactId=${field.artifactId}", + "-Dversion=${field.version}", + "-Dpackage=${field.packageName}" + ], + "environment": { "JAVA_HOME": "${field.jdk}" }, + "collect": "${field.artifactId}", + "mayUseNetwork": true, + "timeoutSeconds": 900 + } + ], + "openFiles": ["pom.xml"] + } ] } diff --git a/io.github.lithe-idea.maven-build/templates/java/maven/dot-gitignore b/io.github.lithe-idea.maven-build/templates/java/maven/dot-gitignore new file mode 100644 index 0000000..f5ed2ce --- /dev/null +++ b/io.github.lithe-idea.maven-build/templates/java/maven/dot-gitignore @@ -0,0 +1,19 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### Lithe ### +.lithe/workspace/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### macOS ### +.DS_Store diff --git a/io.github.lithe-idea.maven-build/templates/java/maven/pom.xml b/io.github.lithe-idea.maven-build/templates/java/maven/pom.xml new file mode 100644 index 0000000..3fa0e0e --- /dev/null +++ b/io.github.lithe-idea.maven-build/templates/java/maven/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + {{ field.groupId }} + {{ field.artifactId }} + 1.0-SNAPSHOT + + + {{ field.jdk.major }} + UTF-8 + + + + + org.junit.jupiter + junit-jupiter + 5.13.4 + test + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.4 + + + + + diff --git a/io.github.lithe-idea.maven-build/templates/java/standard-empty/src/main/java/dot-gitkeep b/io.github.lithe-idea.maven-build/templates/java/standard-empty/src/main/java/dot-gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/io.github.lithe-idea.maven-build/templates/java/standard-empty/src/main/resources/dot-gitkeep b/io.github.lithe-idea.maven-build/templates/java/standard-empty/src/main/resources/dot-gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/io.github.lithe-idea.maven-build/templates/java/standard-empty/src/test/java/dot-gitkeep b/io.github.lithe-idea.maven-build/templates/java/standard-empty/src/test/java/dot-gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/io.github.lithe-idea.maven-build/templates/java/standard-sample-main/Main.java b/io.github.lithe-idea.maven-build/templates/java/standard-sample-main/Main.java new file mode 100644 index 0000000..0550c62 --- /dev/null +++ b/io.github.lithe-idea.maven-build/templates/java/standard-sample-main/Main.java @@ -0,0 +1,7 @@ +package {{ field.groupId | package }}; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello, World!"); + } +} diff --git a/io.github.lithe-idea.maven-build/templates/java/standard-sample/src/main/resources/dot-gitkeep b/io.github.lithe-idea.maven-build/templates/java/standard-sample/src/main/resources/dot-gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/io.github.lithe-idea.maven-build/templates/java/standard-sample/src/test/java/dot-gitkeep b/io.github.lithe-idea.maven-build/templates/java/standard-sample/src/test/java/dot-gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/package.mjs b/scripts/package.mjs index 18e958b..dcb4e7b 100644 --- a/scripts/package.mjs +++ b/scripts/package.mjs @@ -20,7 +20,7 @@ import { createReport } from "./report.mjs"; import { verifySdk } from "./sdk/verify-sdk.mjs"; import { createHash } from "node:crypto"; import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, rmSync } from "node:fs"; -import { join, resolve } from "node:path"; +import { isAbsolute, join, resolve } from "node:path"; const PACKAGE_DIR = "io.github.lithe-idea.maven-build"; const ASSET_PREFIX = "maven-build"; @@ -53,6 +53,24 @@ if (tagFlag >= 0) { const tag = process.argv[tagFlag + 1]; if (tag !== `v${manifest.version}`) problems.push(`tag ${tag} does not match plugin.json version ${manifest.version} (expected v${manifest.version})`); } + +// New Project reads a generator's icons and file templates from the installed +// package, so a wrong path would otherwise only fail on a user's machine. +const checkGeneratorPath = (generatorID, label, path, isExpected) => { + if (typeof path !== "string" || path === "" || isAbsolute(path) || path.split(/[\\/]/).includes("..")) { + problems.push(`generator ${generatorID}: ${label} ${JSON.stringify(path)} must be a relative path inside the package without ".."`); + return; + } + const target = join(PACKAGE_DIR, path); + if (!existsSync(target) || !isExpected(lstatSync(target))) problems.push(`generator ${generatorID}: missing ${label} ${path}`); +}; +for (const generator of manifest.projectGenerators ?? []) { + if (generator.icon?.path !== undefined) checkGeneratorPath(generator.id, "icon file", generator.icon.path, (stat) => stat.isFile()); + if (generator.icon?.darkPath !== undefined) checkGeneratorPath(generator.id, "dark icon file", generator.icon.darkPath, (stat) => stat.isFile()); + for (const step of generator.steps ?? []) { + if (step.source !== undefined) checkGeneratorPath(generator.id, "template directory", step.source, (stat) => stat.isDirectory()); + } +} if (problems.length > 0) { for (const problem of problems) console.error(`✗ ${problem}`); process.exit(1);