diff --git a/package-lock.json b/package-lock.json index 38a4179..fbae542 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "vscode": "^1.60.0" }, "peerDependencies": { - "mcdev": "^6.0.0" + "mcdev": ">=6.0.0" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/package.json b/package.json index 147c9d4..1c18560 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Accenture Salesforce Marketing Cloud DevTools Vscode Extension", "version": "0.0.1", "peerDependencies": { - "mcdev": "^6.0.0" + "mcdev": ">=5.2.0" }, "repository": { "type": "git", @@ -43,6 +43,16 @@ "command": "sfmc-devtools-vscext.devtoolsCMDeploy", "group": "devtools" } + ], + "commandPalette": [ + { + "command": "sfmc-devtools-vscext.devtoolsCMRetrieve", + "when": "false" + }, + { + "command": "sfmc-devtools-vscext.devtoolsCMDeploy", + "when": "false" + } ] } }, diff --git a/src/config/containers.config.ts b/src/config/containers.config.ts index 9dff48b..5192f1c 100644 --- a/src/config/containers.config.ts +++ b/src/config/containers.config.ts @@ -14,7 +14,7 @@ export const containersConfig: { contextMenuRetrieveCommand: string, contextMenuDeployCommand: string } = { - statusBarDevToolsName: "devtoolsmcdev", + statusBarDevToolsName: "mcdev", statusBarDevToolsTitle: "mcdev", statusBarDevToolsCommand: "sfmc-devtools-vscext.devtoolsSBMcdev", statusBarDevToolsCredentialBUName: "devtoolscredentialbu", diff --git a/src/config/main.config.ts b/src/config/main.config.ts index 62af963..d77b973 100644 --- a/src/config/main.config.ts +++ b/src/config/main.config.ts @@ -1,6 +1,7 @@ export const mainConfig: { credentialsFilename: string, requiredFiles: string[], + fileExtensions: string[], allPlaceholder: string, messages: { selectedCredentialsBU: string, @@ -17,6 +18,7 @@ export const mainConfig: { } = { credentialsFilename: ".mcdevrc.json", requiredFiles: [".mcdevrc.json", ".mcdev-auth.json"], + fileExtensions: ["meta.json", "meta.sql", "meta.html", "meta.ssjs", "doc.md"], allPlaceholder: "*All*", messages: { selectedCredentialsBU: "Please select a Credential/BU before running the command", diff --git a/src/devtools/commands/DevToolsAdminCommands.ts b/src/devtools/commands/DevToolsAdminCommands.ts index 866c543..f39da1e 100644 --- a/src/devtools/commands/DevToolsAdminCommands.ts +++ b/src/devtools/commands/DevToolsAdminCommands.ts @@ -9,10 +9,10 @@ class DevToolsAdminCommands extends DevToolsCommands { private commandMethods: { [key: string]: ( config: DevToolsCommandSetting, - args: {[key: string]: any }, + args: {[key: string]: string | string[] | boolean }, path: string, - handleResult: (result?: any) => void) - => void + commandHandlers: { [key: string]: (args?: any) => void } + ) => void } = {}; constructor(){ super(); @@ -29,12 +29,12 @@ class DevToolsAdminCommands extends DevToolsCommands { commandConfig, commandArgs, commandPath, - commandResultHandler + commandHandlers }: DevToolsCommandRunner = commandRunner; log("debug", `Running DevTools Admin Command for id '${commandId}'.`); if(commandId in this.commandMethods){ - this.commandMethods[commandId](commandConfig, commandArgs, commandPath, commandResultHandler); + this.commandMethods[commandId](commandConfig, commandArgs, commandPath, commandHandlers); }else{ log("error", `DevTools Admin Command method for id '${commandId}' is not implemented.`); } @@ -42,7 +42,12 @@ class DevToolsAdminCommands extends DevToolsCommands { setMetadataTypes(_: SupportedMetadataTypes[]): void {} - async init(config: DevToolsCommandSetting, _: {[key: string]: any}, path: string, handleResult: (result?: any) => void){ + async init( + config: DevToolsCommandSetting, + _: {[key: string]: string | string[] | boolean}, + path: string, + { handleCommandResult }: { [key: string]: (args?: any) => void }){ + log("info", `Running DevTools Admin Command: Init...`); const initArgs: {[key: string]: string } = {}; if("command" in config && config.command){ @@ -67,39 +72,49 @@ class DevToolsAdminCommands extends DevToolsCommands { // Checks if the command is still missing so required parameter if(this.hasPlaceholders(commandConfigured)){ log("debug", `Required Parameters missing from Init command: ${commandConfigured}`); + handleCommandResult({ success: false, cancelled: true }); return; } log("debug", `Init final command: ${commandConfigured}`); - await this.executeCommand(commandConfigured, path, true); - handleResult(); + const commandResult: string | number = await this.executeCommand(commandConfigured, path, true); + if(typeof(commandResult) === "number"){ + handleCommandResult({ success: commandResult === 0, cancelled: false }); + } }else{ log("error", "DevToolsAdminCommand_Init: Command is empty or missing the configuration."); } } - async explainTypes(config: DevToolsCommandSetting, args: {[key: string]: any }, path: string, handleResult: (result: any) => void){ - try{ - log("info", `Running DevTools Admin Command: Explain Types...`); - if("command" in config && config.command){ - const commandConfigured: string | undefined = - await this.configureCommandWithParameters( - config, - args, - [] - ); - log("debug", `Explain types final command: ${commandConfigured}`); - const commandResult = await this.executeCommand( - commandConfigured, - path, - !("json" in args)); - handleResult(commandResult); - }else{ - log("error", "DevToolsAdminCommand_explainTypes: Command is empty or missing the configuration."); + async explainTypes( + config: DevToolsCommandSetting, + args: {[key: string]: string | string[] | boolean }, + path: string, + { handleCommandResult }: { [key: string]: (args?: any) => void }){ + + try{ + log("info", `Running DevTools Admin Command: Explain Types...`); + if("command" in config && config.command){ + const commandConfigured: string | undefined = + await this.configureCommandWithParameters( + config, + args, + [] + ); + log("debug", `Explain types final command: ${commandConfigured}`); + const commandResult: string | number = await this.executeCommand( + commandConfigured, + path, + !("json" in args)); + if(typeof(commandResult) === "string"){ + handleCommandResult({ success: commandResult.length > 0, data: commandResult}); + } + }else{ + log("error", "DevToolsAdminCommand_explainTypes: Command is empty or missing some configuration."); + } + }catch(error){ + log("error", `DevToolsAdminCommand_explainTypes Error: ${error}`); } - }catch(error){ - log("error", `DevToolsAdminCommand_explainTypes Error: ${error}`); - } } } diff --git a/src/devtools/commands/DevToolsCommands.ts b/src/devtools/commands/DevToolsCommands.ts index 04227a9..918af25 100644 --- a/src/devtools/commands/DevToolsCommands.ts +++ b/src/devtools/commands/DevToolsCommands.ts @@ -30,16 +30,10 @@ abstract class DevToolsCommands { resolve(code); } if(error){ - log("error", - `[DevToolsCommands_executeCommand] Exit Code: ${error}` - ); + log("error", `[DevToolsCommands_executeCommand] Exit Code: ${error}`); } if(output){ - if(showOnTerminal){ - log("info", output); - }else{ - resolve(output); - } + showOnTerminal ? log("info", output) : resolve(output); } }, }); @@ -48,7 +42,7 @@ abstract class DevToolsCommands { async configureCommandWithParameters( config: DevToolsCommandSetting, - args: {[key: string]: string }, + args: {[key: string]: string | string[] | boolean }, mdTypes: SupportedMetadataTypes[]): Promise { log("debug", `ConfigureCommandWithParameters: ${JSON.stringify(config)}`); @@ -57,7 +51,7 @@ abstract class DevToolsCommands { if("requiredParams" in config && config.requiredParams.length){ for(const param of config.requiredParams){ if(param in args && args[param]){ - command = command.replace(`{{${param}}}`, args[param]); + command = command.replace(`{{${param}}}`, args[param] as string); }else{ // Requests user if(param.toLowerCase() === "mdtypes" && mdTypes.length){ @@ -79,7 +73,7 @@ abstract class DevToolsCommands { ? `--${param}` : ""; } - command = command.replace(`{{${param}}}`, param in args ? args[param] : ""); + command = command.replace(`{{${param}}}`, param in args ? args[param] as string : ""); }); } return command; @@ -145,28 +139,35 @@ abstract class DevToolsCommands { "etypes", path, { json: true }, - ((result: any) => { - // Parses the list of supported mtdata types - const parsedResult: SupportedMetadataTypes[] = JSON.parse(result); - if(parsedResult && parsedResult.length){ - // Sends the supported mtdata types to each DevTools Command - Object.keys(this.commandMap).forEach((key: string) => { - const devToolCommand: DevToolsCommands = - this.commandMap[key]; - devToolCommand.setMetadataTypes(parsedResult); - }); - }else{ - log("error", "DevToolsCommands_init: Failed to parse supported metadata type result."); + { + handleCommandResult: ({ success, data }: { success: boolean, data: string}) => { + if(success){ + // Parses the list of supported mtdata types + const parsedResult: SupportedMetadataTypes[] = JSON.parse(data); + if(parsedResult && parsedResult.length){ + // Sends the supported mtdata types to each DevTools Command + Object.keys(this.commandMap).forEach((key: string) => { + const devToolCommand: DevToolsCommands = + this.commandMap[key]; + devToolCommand.setMetadataTypes(parsedResult); + }); + }else{ + log("error", "DevToolsCommands_init: Failed to parse supported metadata type result."); + } + }else{ + log("error", "DevToolsCommands_init: Admin Command etypes failed."); + } + } } - })); + ); } static async runCommand( - typeId: string, + typeId: string | null, commandId: string, commandPath: string, - args: any, - handleResult: (result: any) => void) { + args: {[key: string]: string | string[] | boolean}, + commandHandlers: {[key: string]: (args?: any) => void}){ // When the DevTools command type is unknown to the application if(!typeId && commandId){ const [{ id }]: { id: string }[] = @@ -186,7 +187,7 @@ abstract class DevToolsCommands { } if(this.commandMap){ - if(typeId in this.commandMap){ + if(typeId && typeId in this.commandMap){ const [ commandConfig ]: DevToolsCommandSetting[] = this.getCommandsListByType(typeId) .filter((commandSetting: DevToolsCommandSetting) => commandSetting.id === commandId); @@ -198,7 +199,7 @@ abstract class DevToolsCommands { commandConfig, commandArgs: args, commandPath, - commandResultHandler: handleResult + commandHandlers: commandHandlers }); return; } diff --git a/src/devtools/commands/DevToolsStandardCommands.ts b/src/devtools/commands/DevToolsStandardCommands.ts index c728a04..be4f2b3 100644 --- a/src/devtools/commands/DevToolsStandardCommands.ts +++ b/src/devtools/commands/DevToolsStandardCommands.ts @@ -9,10 +9,10 @@ class DevToolsStandardCommands extends DevToolsCommands { private commandMethods: { [key: string]: ( config: DevToolsCommandSetting, - args: {[key: string]: any }, + args: {[key: string]: string | string[] | boolean }, path: string, - handleResult: (result: any) => void) - => void + commandHandlers: { [key: string]: (args?: any) => void } + ) => void } = {}; private metadataTypes: SupportedMetadataTypes[] = []; constructor(){ @@ -30,12 +30,12 @@ class DevToolsStandardCommands extends DevToolsCommands { commandConfig, commandArgs, commandPath, - commandResultHandler + commandHandlers }: DevToolsCommandRunner = commandRunner; log("debug", `Running DevTools Standard Command for id '${commandId}'.`); if(commandId in this.commandMethods){ - this.commandMethods[commandId](commandConfig, commandArgs, commandPath, commandResultHandler); + this.commandMethods[commandId](commandConfig, commandArgs, commandPath, commandHandlers); }else{ log("error", `DevTools Standard Command method for id '${commandId}' is not implemented.`); } @@ -45,7 +45,12 @@ class DevToolsStandardCommands extends DevToolsCommands { this.metadataTypes = mdTypes; } - async retrieve(config: DevToolsCommandSetting, args: {[key: string]: string }, path: string, handleResult: (result: any) => void){ + async retrieve( + config: DevToolsCommandSetting, + args: {[key: string]: string | string[] | boolean }, + path: string, + { handleCommandResult, loadingNotification }: { [key: string]: (args?: any) => void }){ + log("info", `Running DevTools Standard Command: Retrieve...`); if("command" in config && config.command){ // Gets that metadata types that are supported for retrieve @@ -63,18 +68,27 @@ class DevToolsStandardCommands extends DevToolsCommands { // Checks if the command is still missing so required parameter if(this.hasPlaceholders(commandConfigured)){ log("debug", `Required Parameters missing from Retrieve command: ${commandConfigured}`); + handleCommandResult({ success: false, cancelled: true }); return; } log("debug", `Retrieve Command configured: ${commandConfigured}`); - const commandResult: Promise = this.executeCommand(commandConfigured, path, true); - handleResult(commandResult); + loadingNotification(); + const commandResult: string | number = await this.executeCommand(commandConfigured, path, true); + if(typeof(commandResult) === "number"){ + handleCommandResult({ success: commandResult === 0, cancelled: false }); + } }else{ log("error", "DevToolsStandardCommand_retrieve: Command is empty or missing the configuration."); } } - async deploy(config: DevToolsCommandSetting, args: {[key: string]: any }, path: string, handleResult: (result: any) => void){ + async deploy( + config: DevToolsCommandSetting, + args: {[key: string]: string | string[] | boolean }, + path: string, + { handleCommandResult, loadingNotification }: { [key: string]: (args?: any) => void }){ + log("info", `Running DevTools Standard Command: Deploy...`); if("command" in config && config.command){ // Gets that metadata types that are supported for deploy @@ -92,12 +106,16 @@ class DevToolsStandardCommands extends DevToolsCommands { // Checks if the command is still missing so required parameter if(this.hasPlaceholders(commandConfigured)){ log("debug", `Required Parameters missing from Deploy command: ${commandConfigured}`); + handleCommandResult({ success: false, cancelled: true }); return; } log("debug", `Deploy Command configured: ${commandConfigured}`); - const commandResult: Promise = this.executeCommand(commandConfigured, path, true); - handleResult(commandResult); + loadingNotification(); + const commandResult: string | number = await this.executeCommand(commandConfigured, path, true); + if(typeof(commandResult) === "number"){ + handleCommandResult({ success: commandResult === 0, cancelled: false }); + } }else{ log("error", "DevToolsStandardCommand_deploy: Command is empty or missing the configuration."); } diff --git a/src/devtools/containers.ts b/src/devtools/containers.ts index 64dd305..d603674 100644 --- a/src/devtools/containers.ts +++ b/src/devtools/containers.ts @@ -16,7 +16,7 @@ enum StatusBarIcon { let statusBarContainer: StatusBarItem | StatusBarItem[]; function activateStatusBar(/*isDevtoolsProject: boolean, commandPrefix: string*/): void { - log("info", "Activating Status Bar Options..."); + log("debug", "Activating Status Bar Options..."); const { subscriptions }: ExtensionContext = editorContext.get(); // Gets the command prefix for @@ -117,7 +117,7 @@ function activateStatusBar(/*isDevtoolsProject: boolean, commandPrefix: string*/ function modifyStatusBar(statusBarId: string, action: keyof typeof StatusBarIcon): void { if(statusBarContainer && Array.isArray(statusBarContainer)){ const [ statusBar ] = statusBarContainer.filter( - (sb: StatusBarItem) => sb.name === `devtools${statusBarId}` + (sb: StatusBarItem) => sb.name?.toLowerCase() === `${statusBarId.toLowerCase()}` ); if(statusBar){ diff --git a/src/devtools/main.ts b/src/devtools/main.ts index ce97d73..4bc025a 100644 --- a/src/devtools/main.ts +++ b/src/devtools/main.ts @@ -40,14 +40,14 @@ async function initDevToolsExtension(): Promise{ } async function isADevToolsProject(projectName?: string): Promise { - log("info", "Checking if folder is a SFMC DevTools project..."); + log("debug", "Checking if folder is a SFMC DevTools project..."); log("debug", `DevTools files: [${mainConfig.requiredFiles}]`); const findMcdevFiles: boolean[] = await Promise.all(mainConfig.requiredFiles .map(async(filename: string) => editorWorkspace.isFileInFolder( `${projectName || '' }${filename}` ))); - log("info", + log("debug", `Folder ${findMcdevFiles.every((result: boolean) => result === true) ? 'is' : 'is not'} a SFMC DevTools project.` ); return findMcdevFiles.every((result: boolean) => result === true); @@ -294,7 +294,7 @@ async function handleDevToolsSBCommand(): Promise{ selectedCommandOption.id, editorWorkspace.getWorkspaceURIPath(), { bu: selectedCredentialBU.replace(mainConfig.allPlaceholder, "'*'") }, - (result: any) => log("info", result) + { handleCommandResult: (result: any) => log("info", result) } ); }else{ log("error", @@ -319,7 +319,7 @@ async function handleDevToolsSBCommand(): Promise{ selectedCommandOption.id, editorWorkspace.getWorkspaceURIPath(), {}, - (result: any) => log("info", result) + { handleCommandResult: (result: any) => log("info", result) } ); } } @@ -339,10 +339,18 @@ async function initialize(): Promise{ if(userResponse && InstallDevToolsResponseOptions[userResponse as keyof typeof InstallDevToolsResponseOptions]){ log("info", "Initializing SFMC DevTools project..."); - DevToolsCommands.runCommand("", "init", editorWorkspace.getWorkspaceURIPath(), [], () => { - log("info", "Reloading VSCode workspace window..."); - lib.waitTime(5000, () => editorWorkspace.reloadWorkspace()); - }); + DevToolsCommands.runCommand( + null, + "init", + editorWorkspace.getWorkspaceURIPath(), + {}, + { + handleCommandResult: () => { + log("info", "Reloading VSCode workspace window..."); + lib.waitTime(5000, () => editorWorkspace.reloadWorkspace()); + } + } + ); } } @@ -368,7 +376,7 @@ async function handleDevToolsCMCommand(action: string, selectedPaths: string[]): // Removes duplicate files (eg. some files have the same name with md and json) if(filesType.length){ filesType = lib.removeDuplicates( - lib.removeExtensionFromFile(filesType) + lib.removeExtensionFromFile(filesType, mainConfig.fileExtensions) ) as string[]; } @@ -408,14 +416,14 @@ async function handleDevToolsCMCommand(action: string, selectedPaths: string[]): log("debug", `Project path: ${projectPath}`); log("debug", `Context Menu path: ${cmPath}`); - log("info", `Project ${workspaceFolderPath === projectPath ? 'is': 'is not'} the workspace folder.`); + log("debug", `Project ${workspaceFolderPath === projectPath ? 'is': 'is not'} the workspace folder.`); // Check if context menu being triggered is from outside of the workspace folder if(workspaceFolderPath !== projectPath){ // Check if folder is a DevTools project const isSubFolderDevToolsProject: boolean = await isADevToolsProject( projectName + "/" ); - log("info", + log("debug", `SubFolder project '${projectPath}' ${ isSubFolderDevToolsProject ? 'is': 'is not'} a DevTools Project.` ); if(!isSubFolderDevToolsProject){ @@ -461,6 +469,9 @@ async function handleDevToolsCMCommand(action: string, selectedPaths: string[]): // if user selects a file inside a subfolder of asset // the key will be the name of the file keys = assetKey ? [ assetKey ] : []; + }else if(type === "folder" && keys.length){ + // Nested folders are not supported as keys for the metadata type folder + keys = []; } key = keys.length ? keys[0] : ""; @@ -502,7 +513,7 @@ async function handleDevToolsCMCommand(action: string, selectedPaths: string[]): for(const optionType of [filesType, folderType]){ if(optionType.length){ - const projectMap: {[key: string]: ProjectConfig} = + const projectMap: {[key: string]: ProjectConfig} = await configureArgsProject(action, optionType); await Promise.all(Object.keys(projectMap).map(async (projName: string) => { log("debug", `Running DevTools Command for project ${projName}`); @@ -524,29 +535,25 @@ async function handleDevToolsCMCommand(action: string, selectedPaths: string[]): await editorInput.handleInProgressMessage( "Notification", (progress) => { - progress.report({message: mainConfig.messages.runningCommand}); return new Promise(resolve => DevToolsCommands.runCommand( - "", + null, action, path, dtArgs, - async(dataResult: Promise) => - dataResult - .then((res: number) => { - devtoolsContainers.modifyStatusBar( - "mcdev", - !res ? 'success' : 'error' - ); - editorInput.handleShowNotificationMessage( - !res - ? 'info' - : 'error', - !res - ? mainConfig.messages.successRunningCommand - : mainConfig.messages.failureRunningCommand, - ); - resolve(); - }) + { + loadingNotification: () => progress.report({message: mainConfig.messages.runningCommand}), + handleCommandResult: ({ success, cancelled }: { success: boolean, cancelled: boolean }) => { + if(!cancelled){ + editorInput.handleShowNotificationMessage( + success ? "info" : "error", + success ? mainConfig.messages.successRunningCommand : + mainConfig.messages.failureRunningCommand + ); + devtoolsContainers.modifyStatusBar( "mcdev", success ? "success" : "error"); + resolve(); + } + } + } )); } ); diff --git a/src/shared/interfaces/devToolsCommandRunner.ts b/src/shared/interfaces/devToolsCommandRunner.ts index ecb559f..f1f76bb 100644 --- a/src/shared/interfaces/devToolsCommandRunner.ts +++ b/src/shared/interfaces/devToolsCommandRunner.ts @@ -3,9 +3,9 @@ import DevToolsCommandSetting from "./devToolsCommandSetting"; interface DevToolsCommandRunner{ commandId: string, commandConfig: DevToolsCommandSetting, - commandArgs: { [key: string]: any }, + commandArgs: { [key: string]: string | string[] | boolean }, commandPath: string, - commandResultHandler: (result: any) => void + commandHandlers: { [key: string]: (args?: any) => void } } export default DevToolsCommandRunner; \ No newline at end of file diff --git a/src/shared/utils/lib.ts b/src/shared/utils/lib.ts index 43b0bdd..d3d47cc 100644 --- a/src/shared/utils/lib.ts +++ b/src/shared/utils/lib.ts @@ -53,17 +53,21 @@ function removeNonValues(array: (string | number)[]): (string | number)[]{ ); } -function removeExtensionFromFile(files: string | string[]): string[] { +function removeExtensionFromFile(files: string | string[], extensions: string[]): string[] { return [files] .flat() .map((file: string) => { const filePathSplit: string[] = file.split("/"); let fileName: string | undefined = filePathSplit.pop(); if(fileName){ - fileName = fileName.startsWith(".") - ? `.${fileName.substring(1).split(".")[0]}` - : fileName.split(".")[0]; - filePathSplit.push(fileName); + let [ extensionValue ] : string[] = extensions.filter((ext: string) => fileName?.endsWith(ext)); + if(extensionValue){ + const regex: RegExp = new RegExp(`(\\.(\\w|-)+-${extensionValue})`); + fileName = fileName.split(regex)[0]; + filePathSplit.push(fileName); + }else{ + throw Error(`[lib_removeExtensionFromFile]: Failed to get file extension for file ${file}`); + } }else{ throw Error(`[lib_removeExtensionFromFile]: Failed to get filename for file ${file}`); }