Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New setting to control creation of Artifact Id folder #197

Merged
merged 5 commits into from
Feb 21, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor for better maintenance
 - config is now an enum, allowing future extensibility
 - removed all .fsPath calls and replaced them with pure vscode.Uri usage (with .path and .parse)
 - we don't rely on start.spring.io's `baseDir`param, creating the sub-folder directly (single-responsibility and dry)
 - other improvements, removing unnecessary variables and responsibility leaks out of `specifyTargetFolder`
  • Loading branch information
brunovieira97 committed Feb 15, 2022
commit 9470ad87653b9b992dd4fadf144143a837b4800b
16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,19 @@
"Add to Workspace"
]
},
"spring.initializr.createArtifactIdFolder": {
"default": true,
"type": "boolean",
"spring.initializr.parentFolder": {
"default": "artifactId",
"type": "string",
"enum": [
"artifactId",
"none"
],
"enumDescriptions": [
"Place project in a folder named after Artifact Id",
"Do not create a folder for the project"
],
"scope": "window",
"description": "Place the newly generated project inside a folder named after Artifact Id."
"description": "Controls if a new sub-folder should be created for the newly generated project."
}
}
}
Expand Down
49 changes: 24 additions & 25 deletions src/handler/GenerateProjectHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
// Licensed under the MIT license.

import * as extract from "extract-zip";
import * as fse from "fs-extra";
import * as path from "path";
import { URL } from "url";
import * as vscode from "vscode";
import { instrumentOperationStep } from "vscode-extension-telemetry-wrapper";
import { OperationCanceledError } from "../Errors";
import { downloadFile } from "../Utils";
import { pathExists } from "../Utils/fsHelper";
import { openDialogForFolder } from "../Utils/VSCodeUI";
import { BaseHandler } from "./BaseHandler";
import { IDefaultProjectData, IProjectMetadata, IStep } from "./HandlerInterfaces";
import { IDefaultProjectData, IProjectMetadata, IStep, ParentFolder } from "./HandlerInterfaces";
import { SpecifyArtifactIdStep } from "./SpecifyArtifactIdStep";
import { SpecifyGroupIdStep } from "./SpecifyGroupIdStep";
import { SpecifyServiceUrlStep } from "./SpecifyServiceUrlStep";
Expand All @@ -31,7 +30,7 @@ export class GenerateProjectHandler extends BaseHandler {
this.metadata = {
pickSteps: [],
defaults: defaults || {},
createArtifactIdFolder: vscode.workspace.getConfiguration("spring.initializr").get<boolean>("createArtifactIdFolder")
parentFolder: vscode.workspace.getConfiguration("spring.initializr").get<ParentFolder>("parentFolder")
};
}

Expand All @@ -54,24 +53,23 @@ export class GenerateProjectHandler extends BaseHandler {
if (this.outputUri === undefined) { throw new OperationCanceledError("Target folder not specified."); }

// Step: Download & Unzip
await instrumentOperationStep(operationId, "DownloadUnzip", downloadAndUnzip)(this.downloadUrl, this.outputUri.fsPath);
await instrumentOperationStep(operationId, "DownloadUnzip", downloadAndUnzip)(this.downloadUrl, this.outputUri);

// Open project either is the same workspace or new workspace
const hasOpenFolder = vscode.workspace.workspaceFolders !== undefined || vscode.workspace.rootPath !== undefined;
const projectLocation = this.metadata.createArtifactIdFolder ? path.join(this.outputUri.fsPath, this.metadata.artifactId) : this.outputUri.fsPath;

// Don't prompt to open projectLocation if it's already a currently opened folder
if (hasOpenFolder && (vscode.workspace.workspaceFolders.some(folder => folder.uri.fsPath === projectLocation) || vscode.workspace.rootPath === projectLocation)) {
if (hasOpenFolder && (vscode.workspace.workspaceFolders.some(folder => folder.uri === this.outputUri) || vscode.workspace.rootPath === this.outputUri.path)) {
return;
}

const choice = await specifyOpenMethod(hasOpenFolder, this.outputUri.fsPath);
const choice = await specifyOpenMethod(hasOpenFolder, this.outputUri);

if (choice === OPEN_IN_NEW_WORKSPACE) {
vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(projectLocation), hasOpenFolder);
vscode.commands.executeCommand("vscode.openFolder", this.outputUri, hasOpenFolder);
} else if (choice === OPEN_IN_CURRENT_WORKSPACE) {
if (!vscode.workspace.workspaceFolders.find((workspaceFolder) => workspaceFolder.uri && this.outputUri.fsPath.startsWith(workspaceFolder.uri.fsPath))) {
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders.length, null, { uri: vscode.Uri.file(projectLocation) });
if (!vscode.workspace.workspaceFolders.find((workspaceFolder) => workspaceFolder.uri && this.outputUri.path.startsWith(workspaceFolder.uri.path))) {
vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders.length, null, { uri: this.outputUri });
}
}
}
Expand All @@ -89,10 +87,6 @@ export class GenerateProjectHandler extends BaseHandler {
`dependencies=${this.metadata.dependencies.id}`,
];

if (this.metadata.createArtifactIdFolder) {
params.push(`baseDir=${ this.metadata.artifactId}`);
}

const targetUrl = new URL(this.metadata.serviceUrl);
targetUrl.pathname = "/starter.zip";
targetUrl.search = `?${params.join("&")}`;
Expand All @@ -106,17 +100,22 @@ async function specifyTargetFolder(metadata: IProjectMetadata): Promise<vscode.U
const LABEL_CHOOSE_FOLDER: string = "Generate into this folder";
const MESSAGE_EXISTING_FOLDER: string = `A folder [${metadata.artifactId}] already exists in the selected folder. Continue to overwrite or Choose another folder?`;
const MESSAGE_FOLDER_NOT_EMPTY: string = "The selected folder is not empty. Existing files with same names will be overwritten. Continue to overwrite or Choose another folder?"

const MESSAGE: string = metadata.createArtifactIdFolder ? MESSAGE_EXISTING_FOLDER : MESSAGE_FOLDER_NOT_EMPTY;

let outputUri: vscode.Uri = metadata.defaults.targetFolder ? vscode.Uri.file(metadata.defaults.targetFolder) : await openDialogForFolder({ openLabel: LABEL_CHOOSE_FOLDER });
const projectLocation = metadata.createArtifactIdFolder ? path.join(outputUri.fsPath, metadata.artifactId) : outputUri.fsPath;
const useArtifactId: boolean = metadata.parentFolder === ParentFolder.ARTIFACT_ID;

const MESSAGE: string = useArtifactId ? MESSAGE_EXISTING_FOLDER : MESSAGE_FOLDER_NOT_EMPTY;

let outputUri: vscode.Uri = metadata.defaults.targetFolder ? vscode.Uri.parse(metadata.defaults.targetFolder) : await openDialogForFolder({ openLabel: LABEL_CHOOSE_FOLDER });

if (outputUri && useArtifactId) {
outputUri = vscode.Uri.parse(`${outputUri.path}/${metadata.artifactId}`);
}

// If not using Artifact Id as folder name, we assume any existing files with same names will be overwritten
// So we check if the folder is not empty, to avoid deleting files without user's consent
while (
(!metadata.createArtifactIdFolder && outputUri && ((await fse.readdir(projectLocation)).length > 0))
|| (metadata.createArtifactIdFolder && outputUri && await fse.pathExists(projectLocation))
(!useArtifactId && outputUri && ((await vscode.workspace.fs.readDirectory(outputUri)).length > 0))
|| (useArtifactId && outputUri && await pathExists(outputUri))
) {
const overrideChoice: string = await vscode.window.showWarningMessage(MESSAGE, OPTION_CONTINUE, OPTION_CHOOSE_ANOTHER_FOLDER);
if (overrideChoice === OPTION_CHOOSE_ANOTHER_FOLDER) {
Expand All @@ -128,7 +127,7 @@ async function specifyTargetFolder(metadata: IProjectMetadata): Promise<vscode.U
return outputUri;
}

async function downloadAndUnzip(targetUrl: string, targetFolder: string): Promise<void> {
async function downloadAndUnzip(targetUrl: string, targetFolder: vscode.Uri): Promise<void> {
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification }, (p: vscode.Progress<{ message?: string }>) => new Promise<void>(
async (resolve: () => void, reject: (e: Error) => void): Promise<void> => {
let filepath: string;
Expand All @@ -140,7 +139,7 @@ async function downloadAndUnzip(targetUrl: string, targetFolder: string): Promis
}

p.report({ message: "Starting to unzip..." });
extract(filepath, { dir: targetFolder }, (err) => {
extract(filepath, { dir: targetFolder.path }, (err) => {
if (err) {
return reject(err);
}
Expand All @@ -150,14 +149,14 @@ async function downloadAndUnzip(targetUrl: string, targetFolder: string): Promis
));
}

async function specifyOpenMethod(hasOpenFolder: boolean, projectLocation: string): Promise<string> {
async function specifyOpenMethod(hasOpenFolder: boolean, projectLocation: vscode.Uri): Promise<string> {
let openMethod = vscode.workspace.getConfiguration("spring.initializr").get<string>("defaultOpenProjectMethod");
if (openMethod !== OPEN_IN_CURRENT_WORKSPACE && openMethod !== OPEN_IN_NEW_WORKSPACE) {
const candidates: string[] = [
OPEN_IN_NEW_WORKSPACE,
hasOpenFolder ? OPEN_IN_CURRENT_WORKSPACE : undefined,
].filter(Boolean);
openMethod = await vscode.window.showInformationMessage(`Successfully generated. Location: ${projectLocation}`, ...candidates);
openMethod = await vscode.window.showInformationMessage(`Successfully generated. Location: ${projectLocation.path}`, ...candidates);
}
return openMethod;
}
7 changes: 6 additions & 1 deletion src/handler/HandlerInterfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface IProjectMetadata {
dependencies?: IDependenciesItem;
pickSteps: IStep[];
defaults: IDefaultProjectData;
createArtifactIdFolder?: boolean;
parentFolder?: ParentFolder;
}

export interface IDefaultProjectData {
Expand Down Expand Up @@ -55,3 +55,8 @@ export interface IInputMetaData {
prompt: string;
defaultValue: string;
}

export enum ParentFolder {
ARTIFACT_ID = "artifactId",
NONE = "none"
}