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);