[vscode-spring-cli] User defined command remove, execution and simple new

This commit is contained in:
aboyko
2024-01-22 18:57:34 -05:00
parent 607d235319
commit 0b71189821
6 changed files with 291 additions and 24 deletions

View File

@@ -9,10 +9,12 @@
"version": "1.52.0",
"license": "EPL-1.0",
"dependencies": {
"lodash.debounce": "4.0.8"
"js-yaml": "^4.1.0",
"lodash.debounce": "^4.0.8"
},
"devDependencies": {
"@types/lodash.debounce": "4.0.9",
"@types/js-yaml": "^4.0.9",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^18.8.0",
"@types/vscode": "1.75.0",
"@vscode/vsce": "^2.22.0",
@@ -23,6 +25,12 @@
"vscode": "^1.75.0"
}
},
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.14.202",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
@@ -133,8 +141,7 @@
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"node_modules/azure-devops-node-api": {
"version": "11.2.0",
@@ -729,6 +736,17 @@
"dev": true,
"optional": true
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
@@ -1411,6 +1429,12 @@
}
},
"dependencies": {
"@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
"dev": true
},
"@types/lodash": {
"version": "4.14.202",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
@@ -1503,8 +1527,7 @@
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"azure-devops-node-api": {
"version": "11.2.0",
@@ -1936,6 +1959,14 @@
"dev": true,
"optional": true
},
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"requires": {
"argparse": "^2.0.1"
}
},
"jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",

View File

@@ -74,7 +74,22 @@
},
{
"command": "vscode-spring-cli.command.add",
"title": "Add Command",
"title": "Add User Defined Command",
"category": "Spring CLI"
},
{
"command": "vscode-spring-cli.command.remove",
"title": "Remove User Defined Command",
"category": "Spring CLI"
},
{
"command": "vscode-spring-cli.command.new",
"title": "New User Defined Command",
"category": "Spring CLI"
},
{
"command": "vscode-spring-cli.command.execute",
"title": "Execute User Defined Command",
"category": "Spring CLI"
}
],
@@ -123,9 +138,11 @@
"vsce-pre-release-package": "vsce package --pre-release"
},
"dependencies": {
"lodash.debounce": "^4.0.8"
"lodash.debounce": "^4.0.8",
"js-yaml": "^4.1.0"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^18.8.0",
"@types/vscode": "1.75.0",

View File

@@ -20,13 +20,41 @@ export interface BootAddMetadata {
catalog?: string;
}
export interface commandAddMetadata {
export interface CommandAddMetadata {
url: string;
}
export interface CommandRemoveMetadata {
command: string;
subcommand: string;
cwd: string;
}
export interface ProjectCatalog {
name: string;
url: string;
description?: string;
tags?: string[];
}
export interface CommandInfo {
description?: string
options: CommandOptions[];
}
export interface CommandOptions {
name: string;
description?: string;
paramLabel?: string;
dataType: string;
defaultValue: string;
inputType: string;
required: boolean;
choices: {[name: string]: string}
}
export interface CommandExecuteMetadata {
command: string;
subcommand: string;
params: {[name: string]: string};
}

View File

@@ -1,8 +1,11 @@
import { BootAddMetadata, BootNewMetadata, Project, ProjectCatalog, commandAddMetadata } from "./cli-types";
import { BootAddMetadata, BootNewMetadata, Project, ProjectCatalog, CommandAddMetadata, CommandRemoveMetadata, CommandInfo, CommandExecuteMetadata } from "./cli-types";
import { CancellationToken, ProcessExecution, ProgressLocation, ProviderResult, Task, TaskProvider, TaskScope, Uri, tasks, window, workspace } from "vscode";
import cp from "child_process";
import { homedir } from "os";
import { getWorkspaceRoot } from "./utils";
import path from "path";
import fs from "fs";
import yaml from "js-yaml";
export const SPRING_CLI_TASK_TYPE = 'spring-cli';
@@ -12,10 +15,6 @@ export class Cli {
return workspace.getConfiguration("spring-cli").get("executable") || "spring";
}
isBorderLine(s: string) {
return !/(\s|\S)+/.test(s);
}
projectList() : Promise<Project[]> {
return this.fetchJson("Fetching projects...", undefined, ["project", "list"]);
}
@@ -28,14 +27,87 @@ export class Cli {
return this.fetchJson("Fetching Available Catalogs...", undefined, ["project-catalog", "list-available"]);
}
commandAdd(metadata: commandAddMetadata) {
commandAdd(metadata: CommandAddMetadata, cwd?: string) {
const args = [
"command",
"add",
"--from",
metadata.url
];
this.exec("Add Command", metadata.url, args);
this.exec("Add Command", metadata.url, args, cwd || homedir());
}
commandRemove(metadata: CommandRemoveMetadata) {
const args = [
"command",
"remove",
"--commandName",
metadata.command,
"--subCommandName",
metadata.subcommand
];
this.exec("Remove Command", `'${metadata.command} ${metadata.subcommand}'`, args, metadata.cwd);
}
commandList(cwd: string, command?: string): Promise<string[]> {
return new Promise(async(resolve, reject) => {
let p = path.join(cwd, ".spring", "commands");
if (command) {
p = path.join(p, command);
}
fs.readdir(p, { withFileTypes: true }, (error, files) => {
if (error) {
reject(error.message);
} else {
resolve(files.filter(d => d.isDirectory()).map(d => d.name));
}
})
});
}
commandInfo(cwd: string, command: string, subcommand: string): Promise<CommandInfo> {
return new Promise((resolve, reject) => {
try {
const parentFolder = path.join(cwd, ".spring", "commands", command, subcommand);
let file;
if (fs.existsSync(path.join(parentFolder, "command.yaml"))) {
file = path.join(parentFolder, "command.yaml");
} else if (fs.existsSync(path.join(parentFolder, "command.yml"))) {
file = path.join(parentFolder, "command.yml");
}
if (file) {
const content = fs.readFileSync(file, "utf-8");
const obj = yaml.load(content, {json: true}) as any;
if (typeof obj === 'object' && obj?.command) {
resolve(obj.command as CommandInfo);
} else {
reject(`Unable to read command '${command} ${subcommand}' info`);
}
}
} catch (error) {
reject(error);
}
});
}
commandNew(cwd: string) {
const args = [
"command",
"new",
];
return this.exec("Remove Command", undefined, args, cwd);
}
commandExecute(metadata: CommandExecuteMetadata, cwd: string) {
const args = [
metadata.command,
metadata.subcommand,
];
Object.keys(metadata.params).forEach(p => {
args.push(`--${p}`);
args.push(metadata.params[p]);
});
return this.exec("Executing Command", `'${metadata.command} ${metadata.subcommand}'`, args, cwd);
}
projectCatalogAdd(catalog: ProjectCatalog): Promise<void> {

View File

@@ -1,8 +1,31 @@
import { Uri, window } from "vscode";
import { QuickPickItem, Uri, WorkspaceFolder, window, workspace } from "vscode";
import { enterText } from "./utils";
import { CLI } from "./extension";
import { homedir } from "os";
import { CommandInfo } from "./cli-types";
export async function handleCommandAdd() {
interface SubCommandQuickPickItem extends QuickPickItem {
info?: CommandInfo;
}
interface ChoiceQuickPickItem extends QuickPickItem {
value: string;
}
const GLOBAL: QuickPickItem = {
label: "GLOBAL",
description: "Globally available for all user projects",
detail: homedir()
};
const LOCAL_SCOPE_DESCRIPTION = "Available only within selected workspace folder"
export async function handleCommandAdd(uri?: Uri) {
// Enter CWD for CLI (global vs workspace folder local command)
const cwd = await enterCwd(uri);
// Enter URL of the repo for the command to add
let url;
try {
url = await enterText({
@@ -22,14 +45,109 @@ export async function handleCommandAdd() {
}
if (url) {
CLI.commandAdd({url});
CLI.commandAdd({url}, cwd);
}
}
export async function handleCommandRemove() {
window.showErrorMessage("Not Implemented!");
export async function handleCommandRemove(uri?: Uri) {
// Enter CWD for CLI (global vs workspace folder local command)
const cwd = await enterCwd(uri);
// Select the command
const command = await pickCommand(cwd);
// Select the subcommand
const subcommand = (await pickSubCommand(cwd, command))?.label;
return CLI.commandRemove({
command,
subcommand,
cwd
});
}
export async function handleCommandNew() {
window.showErrorMessage("Not Implemented!");
export async function handleCommandNew(uri?: Uri) {
const cwd = await enterCwd(uri);
return CLI.commandNew(cwd);
}
export async function handleCommandExecute(uri?: Uri) {
// Enter CWD for CLI (global vs workspace folder local command)
const cwd = await enterCwd(uri);
// Select the command
const command = await pickCommand(cwd);
// Select the subcommand
const subcommand = (await pickSubCommand(cwd, command));
const params = {};
if (Array.isArray(subcommand?.info?.options)) {
for (const o of subcommand.info.options) {
let value;
if (o.choices) {
const quickPickItems = Object.keys(o.choices).map(s => ({
label: s,
value: o.choices[s]
}) as ChoiceQuickPickItem);
value = (await window.showQuickPick(quickPickItems, { canPickMany: false, ignoreFocusOut: true })).value;
} else {
value = await enterText({
title: o.paramLabel || o.name,
defaultValue: o.defaultValue,
placeholder: o.defaultValue,
prompt: `Enter ${o.paramLabel || o.name}${o.description ? " - " : ""}${o.description}`
});
}
if (value) {
params[o.name] = value;
}
}
}
return CLI.commandExecute({
command,
subcommand: subcommand.label,
params
}, cwd);
}
function mapFolderToQuickPickItem(folder: WorkspaceFolder): QuickPickItem {
return {
label: folder.name,
description: LOCAL_SCOPE_DESCRIPTION,
detail: folder.uri.fsPath
};
}
async function enterCwd(uri?: Uri): Promise<string> {
const commandScopes = [ GLOBAL ];
if (uri) {
const folder = workspace.getWorkspaceFolder(uri);
if (folder) {
commandScopes.push(mapFolderToQuickPickItem(folder));
}
} else {
commandScopes.push(...workspace.workspaceFolders.map(mapFolderToQuickPickItem));
}
return commandScopes.length > 1 ? (await window.showQuickPick(commandScopes, { canPickMany: false, ignoreFocusOut: true }))?.detail : GLOBAL.detail;
}
async function pickCommand(cwd: string): Promise<string> {
return await window.showQuickPick(CLI.commandList(cwd), { canPickMany: false, ignoreFocusOut: true});
}
async function pickSubCommand(cwd: string, command: string): Promise<SubCommandQuickPickItem> {
const deferredItems = CLI.commandList(cwd, command).then(async subcommands => {
return await Promise.all(subcommands.map(async sc => {
const qp: SubCommandQuickPickItem = {
label: sc,
};
try {
qp.info = await CLI.commandInfo(cwd, command, sc);
qp.description = qp.info.description;
} catch (error) {
window.showWarningMessage(`Could not fetch info for CLI command: '${command} ${sc}'`);
}
return qp;
}));
});
return await window.showQuickPick(deferredItems, {canPickMany: false, ignoreFocusOut: true});
}

View File

@@ -3,7 +3,7 @@ import { Cli, CliTaskProvider, SPRING_CLI_TASK_TYPE } from "./cli";
import { handleBootAdd, handleBootNew } from "./boot";
import { handleCatalogAdd, handleCatalogRemove } from "./project-catalog";
import { handleProjectAdd, handleProjectRemove } from "./project";
import { handleCommandAdd, handleCommandNew, handleCommandRemove } from "./command";
import { handleCommandAdd, handleCommandExecute, handleCommandNew, handleCommandRemove } from "./command";
import { ExtensionContext, commands, tasks } from "vscode";
export const CLI = new Cli();
@@ -25,6 +25,7 @@ export async function activate(context: ExtensionContext): Promise<void> {
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.command.add', handleCommandAdd));
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.command.remove', handleCommandRemove));
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.command.new', handleCommandNew));
context.subscriptions.push(commands.registerCommand('vscode-spring-cli.command.execute', handleCommandExecute));
context.subscriptions.push(tasks.registerTaskProvider(SPRING_CLI_TASK_TYPE, new CliTaskProvider(CLI)));