support to create new project without archetype (#962)
This commit is contained in:
Родитель
f4618b70d7
Коммит
ff196168a4
|
@ -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;
|
||||
|
|
|
@ -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) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче