support to create new project without archetype (#962)

This commit is contained in:
Yan Zhang 2023-04-14 16:09:05 +08:00 коммит произвёл GitHub
Родитель f4618b70d7
Коммит ff196168a4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 207 добавлений и 89 удалений

Просмотреть файл

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>

Просмотреть файл

@ -15,6 +15,8 @@ import { Utils } from "../utils/Utils";
import { Archetype } from "./Archetype";
import { runSteps, selectArchetypeStep, specifyArchetypeVersionStep, specifyArtifactIdStep, specifyGroupIdStep, specifyTargetFolderStep } from "./createProject";
import { IProjectCreationMetadata, IProjectCreationStep } from "./createProject/types";
import { promptOnDidProjectCreated } from "./utils";
const REMOTE_ARCHETYPE_CATALOG_URL = "https://repo.maven.apache.org/maven2/archetype-catalog.xml";
export class ArchetypeModule {
@ -45,7 +47,11 @@ export class ArchetypeModule {
const success: boolean = await runSteps(steps, metadata);
if (success) {
await executeInTerminalHandler(metadata);
if (metadata.archetype) {
await executeInTerminalHandler(metadata);
} else {
await createBasicMavenProject(metadata);
}
}
}
@ -140,6 +146,59 @@ async function executeInTerminalHandler(metadata: IProjectCreationMetadata): Pro
vscode.tasks.executeTask(createProjectTask);
}
async function createBasicMavenProject(metadata: IProjectCreationMetadata): Promise<void> {
const {
groupId,
artifactId,
targetFolder
} = metadata;
if (!groupId || !artifactId || !targetFolder) {
return;
}
const task = async (p: vscode.Progress<{ message?: string; increment?: number }>) => {
// copy from template
p.report({ message: "Generating project from template...", increment: 20 });
const templateUri = vscode.Uri.file(getPathToExtensionRoot("resources", "projectTemplate"));
const targetUri = vscode.Uri.joinPath(vscode.Uri.file(targetFolder), artifactId);
await workspace.fs.copy(templateUri, targetUri, { overwrite: true });
// update groupId/artifactId in pom.xml
p.report({ message: "Updating pom.xml file...", increment: 20 });
const pomUri = vscode.Uri.joinPath(targetUri, "pom.xml");
let pomContent = (await workspace.fs.readFile(pomUri)).toString();
pomContent = pomContent.replace("${groupId}", groupId);
pomContent = pomContent.replace("${artifactId}", artifactId);
await workspace.fs.writeFile(pomUri, Buffer.from(pomContent));
// create source files
p.report({ message: "Creating source files...", increment: 20 });
await vscode.workspace.fs.createDirectory(vscode.Uri.joinPath(targetUri, "src", "main", "java"));
await vscode.workspace.fs.createDirectory(vscode.Uri.joinPath(targetUri, "src", "main", "resources"));
await vscode.workspace.fs.createDirectory(vscode.Uri.joinPath(targetUri, "src", "test", "java"));
const packageUri = vscode.Uri.joinPath(targetUri, "src", "main", "java", ...groupId.split("."));
await vscode.workspace.fs.createDirectory(packageUri);
const mainUri = vscode.Uri.joinPath(packageUri, "Main.java");
const content: string = [
`package ${groupId};`,
"",
"public class Main {",
" public static void main(String[] args) {",
" System.out.println(\"Hello world!\");",
" }",
"}"
].join("\n");
await vscode.workspace.fs.writeFile(mainUri, Buffer.from(content));
// TODO: update modules of parent project, on demand
};
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
}, task);
await promptOnDidProjectCreated(artifactId, targetFolder);
}
export class ArchetypeMetadata {
public groupId: string;

Просмотреть файл

@ -3,7 +3,7 @@
import * as fse from "fs-extra";
import * as path from "path";
import { Disposable, QuickInputButtons, QuickPick, QuickPickItem, window } from "vscode";
import { Disposable, QuickInputButtons, QuickPick, QuickPickItem, QuickPickItemKind, window } from "vscode";
import { getMavenLocalRepository, getPathToExtensionRoot } from "../../utils/contextUtils";
import { Archetype } from "../Archetype";
import { ArchetypeModule } from "../ArchetypeModule";
@ -13,6 +13,9 @@ interface IArchetypePickItem extends QuickPickItem {
archetype?: Archetype;
}
const LABEL_NO_ARCHETYPE = "No Archetype...";
const LABEL_MORE = "More...";
export class SelectArchetypeStep implements IProjectCreationStep {
/**
* This has to be the first step, no back buttons provided for previous steps.
@ -37,8 +40,17 @@ export class SelectArchetypeStep implements IProjectCreationStep {
}),
pickBox.onDidAccept(async () => {
if (pickBox.selectedItems[0].archetype === undefined) {
pickBox.items = await this.getArchetypePickItems(true);
pickBox.buttons = [QuickInputButtons.Back];
if (pickBox.selectedItems[0].label === LABEL_NO_ARCHETYPE) {
// Basic project without archetype
resolve(StepResult.NEXT);
} else if (pickBox.selectedItems[0].label === LABEL_MORE) {
// More archetypes...
pickBox.items = await this.getArchetypePickItems(true);
pickBox.buttons = [QuickInputButtons.Back];
} else {
// IMPOSSIBLE
console.warn(pickBox.selectedItems[0], "unexpected archetype");
}
} else {
metadata.archetypeArtifactId = pickBox.selectedItems[0].archetype.artifactId;
metadata.archetypeGroupId = pickBox.selectedItems[0].archetype.groupId;
@ -62,31 +74,15 @@ export class SelectArchetypeStep implements IProjectCreationStep {
}
}
public async run_simple(metadata: IProjectCreationMetadata): Promise<StepResult> {
let choice = await window.showQuickPick(this.getArchetypePickItems(false), {
placeHolder: "Select an archetype ...",
ignoreFocusOut: true, matchOnDescription: true
});
if (choice === undefined) {
return StepResult.STOP;
}
if (choice.archetype === undefined) { // More...
choice = await window.showQuickPick(this.getArchetypePickItems(true), {
placeHolder: "Select an archetype ...",
ignoreFocusOut: true, matchOnDescription: true
});
}
if (choice?.archetype !== undefined) {
metadata.archetypeArtifactId = choice.archetype.artifactId;
metadata.archetypeGroupId = choice.archetype.groupId;
metadata.archetype = choice.archetype; // perserved for archetype version selection.
}
return StepResult.NEXT;
}
private async getArchetypePickItems(all?: boolean): Promise<IArchetypePickItem[]> {
const noArchetypeButton: IArchetypePickItem = {
label: LABEL_NO_ARCHETYPE,
description: "",
detail: "Create a basic Maven project directly.",
alwaysShow: true
};
const moreButton: IArchetypePickItem = {
label: "More...",
label: LABEL_MORE,
description: "",
detail: "Find more archetypes available in remote catalog.",
alwaysShow: true
@ -98,7 +94,11 @@ export class SelectArchetypeStep implements IProjectCreationStep {
description: archetype.groupId ? `${archetype.groupId}` : "",
detail: archetype.description
}));
return all ? pickItems : [moreButton, ...pickItems];
const SEP_ARCHETYPE: IArchetypePickItem = {
label: "Popular Archetypes",
kind: QuickPickItemKind.Separator
};
return all ? pickItems : [noArchetypeButton, moreButton, SEP_ARCHETYPE, ...pickItems];
}
private async loadArchetypePickItems(all?: boolean): Promise<Archetype[]> {

Просмотреть файл

@ -10,7 +10,12 @@ export class SpecifyArchetypeVersionStep implements IProjectCreationStep {
public async run(metadata: IProjectCreationMetadata): Promise<StepResult> {
const disposables: Disposable[] = [];
const specifyAchetypeVersionPromise = new Promise<StepResult>((resolve, reject) => {
if (metadata.archetype?.versions === undefined) {
if (!metadata.archetype) {
// no archetype
resolve(StepResult.NEXT);
return;
}
if (metadata.archetype.versions === undefined) {
reject("Invalid archetype selected.");
return;
}

Просмотреть файл

@ -13,7 +13,7 @@ export class SpecifyArtifactIdStep implements IProjectCreationStep {
const inputBox: InputBox = window.createInputBox();
inputBox.title = "Create Maven Project";
inputBox.placeholder = "e.g. demo";
inputBox.prompt = "Input artifact Id of your project.";
inputBox.prompt = "Input artifact Id (also as project name) of your project.";
inputBox.value = metadata.artifactId ?? "demo";
inputBox.ignoreFocusOut = true;
if (this.previousStep) {

Просмотреть файл

@ -1,18 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Uri } from "vscode";
import { pathExistsSync } from "fs-extra";
import * as path from "path";
import * as vscode from "vscode";
import { openDialogForFolder } from "../../utils/uiUtils";
import { IProjectCreationMetadata, IProjectCreationStep, StepResult } from "./types";
export class SpecifyTargetFolderStep implements IProjectCreationStep {
public async run(metadata: IProjectCreationMetadata): Promise<StepResult> {
if (!metadata.artifactId) {
return StepResult.STOP;
}
const LABEL_CHOOSE_FOLDER = "Select Destination Folder";
const OPTION_CONTINUE = "Continue";
const OPTION_CHOOSE_ANOTHER_FOLDER = "Choose another folder";
const OPTION_CHANGE_PROJECT_NAME = "Change project name";
const MESSAGE_EXISTING_FOLDER = `A folder [${metadata.artifactId}] already exists in the selected folder.`;
// choose target folder.
const result: Uri | undefined = await openDialogForFolder({
defaultUri: metadata.targetFolderHint !== undefined ? Uri.file(metadata.targetFolderHint) : undefined,
openLabel: "Select Destination Folder"
let result: vscode.Uri | undefined = await openDialogForFolder({
defaultUri: metadata.targetFolderHint !== undefined ? vscode.Uri.file(metadata.targetFolderHint) : undefined,
openLabel: LABEL_CHOOSE_FOLDER
});
while (result && pathExistsSync(path.join(result.fsPath, metadata.artifactId))) {
const overrideChoice = await vscode.window.showWarningMessage(MESSAGE_EXISTING_FOLDER, OPTION_CONTINUE, OPTION_CHOOSE_ANOTHER_FOLDER, OPTION_CHANGE_PROJECT_NAME);
if (overrideChoice === OPTION_CHOOSE_ANOTHER_FOLDER) {
result = await openDialogForFolder({
defaultUri: result,
openLabel: LABEL_CHOOSE_FOLDER
});
} else if (overrideChoice === OPTION_CHANGE_PROJECT_NAME) {
return StepResult.PREVIOUS;
} else {
break;
}
}
const targetFolder: string | undefined = result?.fsPath;
if (targetFolder === undefined) {
return StepResult.STOP;

61
src/archetype/utils.ts Normal file
Просмотреть файл

@ -0,0 +1,61 @@
import assert = require("assert");
import path = require("path");
import * as vscode from "vscode";
import { sendInfo } from "vscode-extension-telemetry-wrapper";
// corresponding to setting values
const OPEN_IN_NEW_WORKSPACE = "Open";
const OPEN_IN_CURRENT_WORKSPACE = "Add as Workspace Folder";
const OPEN_INTERACTIVE = "Interactive";
export function registerProjectCreationEndListener(context: vscode.ExtensionContext): void {
context.subscriptions.push(vscode.tasks.onDidEndTaskProcess(async (e) => {
if (e.execution.task.name === "createProject" && e.execution.task.source === "maven") {
if (e.exitCode !== 0) {
vscode.window.showErrorMessage("Failed to create the project, check terminal output for more details.");
return;
}
const { targetFolder, artifactId } = e.execution.task.definition;
const projectFolder = path.join(targetFolder, artifactId);
await promptOnDidProjectCreated(artifactId, projectFolder);
}
}));
}
export async function promptOnDidProjectCreated(projectName: string, projectFolderPath: string) {
// Open project either is the same workspace or new workspace
const hasOpenFolder = vscode.workspace.workspaceFolders !== undefined;
const choice = await specifyOpenMethod(hasOpenFolder, projectName, projectFolderPath);
if (choice === OPEN_IN_NEW_WORKSPACE) {
vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(projectFolderPath), hasOpenFolder);
} else if (choice === OPEN_IN_CURRENT_WORKSPACE) {
assert(vscode.workspace.workspaceFolders !== undefined);
if (!vscode.workspace.workspaceFolders?.find((workspaceFolder) => projectFolderPath.startsWith(workspaceFolder.uri?.fsPath))) {
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders.length, null, { uri: vscode.Uri.file(projectFolderPath) });
}
}
}
async function specifyOpenMethod(hasOpenFolder: boolean, projectName: string, projectLocation: string) {
let openMethod = vscode.workspace.getConfiguration("maven").get<string>("projectOpenBehavior");
sendInfo("", {
name: "projectOpenBehavior(from setting)",
value: openMethod ?? "undefined"
}, {});
if (openMethod === OPEN_INTERACTIVE) {
let alreadyInCurrentWorkspace = false;
if(vscode.workspace.workspaceFolders?.find(wf => projectLocation.startsWith(wf.uri.fsPath))) {
alreadyInCurrentWorkspace = true;
}
const candidates: string[] = alreadyInCurrentWorkspace ? ["OK"] : [
OPEN_IN_NEW_WORKSPACE,
hasOpenFolder ? OPEN_IN_CURRENT_WORKSPACE : undefined
].filter(Boolean) as string[];
openMethod = await vscode.window.showInformationMessage(`Maven project [${projectName}] is created under: ${projectLocation}`, ...candidates);
sendInfo("", {
name: "projectOpenBehavior(from choice)",
value: openMethod ?? "cancelled"
}, {});
}
return openMethod;
}

Просмотреть файл

@ -2,22 +2,23 @@
// Licensed under the MIT license.
"use strict";
import * as assert from "assert";
import * as path from "path";
import * as vscode from "vscode";
import { Progress, Uri } from "vscode";
import { dispose as disposeTelemetryWrapper, initialize, instrumentOperation, sendInfo } from "vscode-extension-telemetry-wrapper";
import { diagnosticProvider } from "./DiagnosticProvider";
import { Settings } from "./Settings";
import { ArchetypeModule } from "./archetype/ArchetypeModule";
import { registerProjectCreationEndListener } from "./archetype/utils";
import { codeActionProvider } from "./codeAction/codeActionProvider";
import { ConflictResolver, conflictResolver } from "./codeAction/conflictResolver";
import { DEFAULT_MAVEN_LIFECYCLES } from "./completion/constants";
import { PomCompletionProvider } from "./completion/PomCompletionProvider";
import { DEFAULT_MAVEN_LIFECYCLES } from "./completion/constants";
import { contentProvider } from "./contentProvider";
import { definitionProvider } from "./definition/definitionProvider";
import { diagnosticProvider } from "./DiagnosticProvider";
import { initExpService } from "./experimentationService";
import { decorationProvider } from "./explorer/decorationProvider";
import { MavenExplorerProvider } from "./explorer/MavenExplorerProvider";
import { decorationProvider } from "./explorer/decorationProvider";
import { Dependency } from "./explorer/model/Dependency";
import { ITreeItem } from "./explorer/model/ITreeItem";
import { MavenProfile } from "./explorer/model/MavenProfile";
@ -40,12 +41,11 @@ import { mavenOutputChannel } from "./mavenOutputChannel";
import { mavenTerminal } from "./mavenTerminal";
import { init as initMavenXsd } from "./mavenXsd";
import { MavenProjectManager } from "./project/MavenProjectManager";
import { Settings } from "./Settings";
import { taskExecutor } from "./taskExecutor";
import { Utils } from "./utils/Utils";
import { getAiKey, getExtensionId, getExtensionVersion, loadMavenSettingsFilePath, loadPackageInfo } from "./utils/contextUtils";
import { executeInTerminal } from "./utils/mavenUtils";
import { dependenciesContentUri, effectivePomContentUri, openFileIfExists, registerCommand, registerCommandRequiringTrust } from "./utils/uiUtils";
import { Utils } from "./utils/Utils";
export async function activate(context: vscode.ExtensionContext): Promise<void> {
await loadPackageInfo(context);
@ -280,52 +280,3 @@ async function openPomHandler(node: MavenProject | { uri: string }): Promise<voi
}
}
}
function registerProjectCreationEndListener(context: vscode.ExtensionContext): void {
// corresponding to setting values
const OPEN_IN_NEW_WORKSPACE = "Open";
const OPEN_IN_CURRENT_WORKSPACE = "Add to Workspace";
const OPEN_INTERACTIVE = "Interactive";
const specifyOpenMethod = async (hasOpenFolder: boolean, projectName: string, projectLocation: string) => {
let openMethod = vscode.workspace.getConfiguration("maven").get<string>("projectOpenBehavior");
sendInfo("", {
name: "projectOpenBehavior(from setting)",
value: openMethod ?? "undefined"
}, {});
if (openMethod === OPEN_INTERACTIVE) {
const candidates: string[] = [
OPEN_IN_NEW_WORKSPACE,
hasOpenFolder ? OPEN_IN_CURRENT_WORKSPACE : undefined
].filter(Boolean) as string[];
openMethod = await vscode.window.showInformationMessage(`Maven project [${projectName}] is created under: ${projectLocation}`, ...candidates);
sendInfo("", {
name: "projectOpenBehavior(from choice)",
value: openMethod ?? "cancelled"
}, {});
}
return openMethod;
};
context.subscriptions.push(vscode.tasks.onDidEndTaskProcess(async (e) => {
if (e.execution.task.name === "createProject" && e.execution.task.source === "maven") {
if (e.exitCode !== 0) {
vscode.window.showErrorMessage("Failed to create the project, check terminal output for more details.");
return;
}
const { targetFolder, artifactId } = e.execution.task.definition;
const projectFolder = path.join(targetFolder, artifactId);
// Open project either is the same workspace or new workspace
const hasOpenFolder = vscode.workspace.workspaceFolders !== undefined;
const choice = await specifyOpenMethod(hasOpenFolder, artifactId, targetFolder);
if (choice === OPEN_IN_NEW_WORKSPACE) {
vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(projectFolder), hasOpenFolder);
} else if (choice === OPEN_IN_CURRENT_WORKSPACE) {
assert(vscode.workspace.workspaceFolders !== undefined);
if (!vscode.workspace.workspaceFolders?.find((workspaceFolder) => projectFolder.startsWith(workspaceFolder.uri?.fsPath))) {
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders.length, null, { uri: vscode.Uri.file(projectFolder) });
}
}
}
}));
}