From d1b8622c6029ba0369e49453ef6b5689a2b88763 Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Tue, 4 Jun 2024 15:22:49 +0200 Subject: [PATCH 01/14] prettier added to project --- .prettierrc | 11 + src/config/containers.config.ts | 62 +- src/config/installer.config.ts | 34 +- src/config/main.config.ts | 82 +- src/config/metadatatypes.config.ts | 1065 ++++++------- src/config/prerequisites.config.ts | 50 +- .../commands/DevToolsAdminCommands.ts | 206 ++- src/devtools/commands/DevToolsCommands.ts | 444 +++--- .../commands/DevToolsStandardCommands.ts | 274 ++-- src/devtools/containers.ts | 193 +-- src/devtools/installer.ts | 145 +- src/devtools/main.ts | 1420 +++++++++-------- src/devtools/prerequisites.ts | 150 +- src/editor/commands.ts | 33 +- src/editor/containers.ts | 28 +- src/editor/context.ts | 8 +- src/editor/dependencies.ts | 87 +- src/editor/input.ts | 95 +- src/editor/output.ts | 73 +- src/editor/webview.ts | 97 +- src/editor/workspace.ts | 122 +- src/extension.ts | 4 +- .../interfaces/devToolsCommandRunner.ts | 14 +- .../interfaces/devToolsCommandSetting.ts | 16 +- .../interfaces/devToolsPathComponents.ts | 10 +- src/shared/interfaces/inputOptionsSettings.ts | 10 +- .../interfaces/supportedMetadataTypes.ts | 12 +- .../interfaces/terminalCommandRunner.ts | 12 +- src/shared/utils/file.ts | 86 +- src/shared/utils/fileLogger.ts | 33 +- src/shared/utils/lib.ts | 118 +- src/shared/utils/terminal.ts | 38 +- 32 files changed, 2504 insertions(+), 2528 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..4c8819e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "tabWidth": 4, + "semi": true, + "singleQuote": false, + "trailingComma": "none", + "printWidth": 120, + "useTabs": true, + "endOfLine":"auto", + "bracketSpacing": true, + "arrowParens": "avoid" +} \ No newline at end of file diff --git a/src/config/containers.config.ts b/src/config/containers.config.ts index ae99a28..d19709a 100644 --- a/src/config/containers.config.ts +++ b/src/config/containers.config.ts @@ -1,33 +1,33 @@ export const containersConfig: { - statusBarDevToolsName: string, - statusBarDevToolsTitle: string, - statusBarDevToolsCommand: string, - statusBarDevToolsCredentialBUName: string, - statusBarDevToolsCredentialBUTitle: string, - statusBarDevToolsCredentialBUCommand: string, - statusBarDevToolsCommandName: string, - statusBarDevToolsCommandTitle: string, - statusBarDevToolsCommandCommand: string, - statusBarDevToolsInitializeName: string, - statusBarDevToolsInitializeTitle: string, - statusBarDevToolsInitializeCommand: string, - contextMenuRetrieveCommand: string, - contextMenuDeployCommand: string, - contextMenuCopyToBUCommand: string + statusBarDevToolsName: string; + statusBarDevToolsTitle: string; + statusBarDevToolsCommand: string; + statusBarDevToolsCredentialBUName: string; + statusBarDevToolsCredentialBUTitle: string; + statusBarDevToolsCredentialBUCommand: string; + statusBarDevToolsCommandName: string; + statusBarDevToolsCommandTitle: string; + statusBarDevToolsCommandCommand: string; + statusBarDevToolsInitializeName: string; + statusBarDevToolsInitializeTitle: string; + statusBarDevToolsInitializeCommand: string; + contextMenuRetrieveCommand: string; + contextMenuDeployCommand: string; + contextMenuCopyToBUCommand: string; } = { - statusBarDevToolsName: "mcdev", - statusBarDevToolsTitle: "mcdev", - statusBarDevToolsCommand: "sfmc-devtools-vscext.devtoolsSBMcdev", - statusBarDevToolsCredentialBUName: "devtoolscredentialbu", - statusBarDevToolsCredentialBUTitle: "Credential/BU", - statusBarDevToolsCredentialBUCommand: "sfmc-devtools-vscext.devtoolsSBCredentialBU", - statusBarDevToolsCommandName: "devtoolscommand", - statusBarDevToolsCommandTitle: "Command", - statusBarDevToolsCommandCommand: "sfmc-devtools-vscext.devtoolsSBCommand", - statusBarDevToolsInitializeName: "devtoolsinitialize", - statusBarDevToolsInitializeTitle: "Initialize", - statusBarDevToolsInitializeCommand: "sfmc-devtools-vscext.devtoolsSBInitialize", - contextMenuRetrieveCommand: "sfmc-devtools-vscext.devtoolsCMRetrieve", - contextMenuDeployCommand: "sfmc-devtools-vscext.devtoolsCMDeploy", - contextMenuCopyToBUCommand: "sfmc-devtools-vscext.devtoolsCMCopyToBU" -}; \ No newline at end of file + statusBarDevToolsName: "mcdev", + statusBarDevToolsTitle: "mcdev", + statusBarDevToolsCommand: "sfmc-devtools-vscext.devtoolsSBMcdev", + statusBarDevToolsCredentialBUName: "devtoolscredentialbu", + statusBarDevToolsCredentialBUTitle: "Credential/BU", + statusBarDevToolsCredentialBUCommand: "sfmc-devtools-vscext.devtoolsSBCredentialBU", + statusBarDevToolsCommandName: "devtoolscommand", + statusBarDevToolsCommandTitle: "Command", + statusBarDevToolsCommandCommand: "sfmc-devtools-vscext.devtoolsSBCommand", + statusBarDevToolsInitializeName: "devtoolsinitialize", + statusBarDevToolsInitializeTitle: "Initialize", + statusBarDevToolsInitializeCommand: "sfmc-devtools-vscext.devtoolsSBInitialize", + contextMenuRetrieveCommand: "sfmc-devtools-vscext.devtoolsCMRetrieve", + contextMenuDeployCommand: "sfmc-devtools-vscext.devtoolsCMDeploy", + contextMenuCopyToBUCommand: "sfmc-devtools-vscext.devtoolsCMCopyToBU" +}; diff --git a/src/config/installer.config.ts b/src/config/installer.config.ts index 93cb81f..89c97b9 100644 --- a/src/config/installer.config.ts +++ b/src/config/installer.config.ts @@ -1,22 +1,22 @@ export enum InstallDevToolsResponseOptions { - "Yes" = 1, - "No" = 0 + "Yes" = 1, + "No" = 0 } export const installerConfig: { - package: { mcdev: { version: string, install: string } }, - messages: { - noDevToolsInstalled: string, - askUserToInstallDevTools: string, - installingDevToolsProgress: string - } + package: { mcdev: { version: string; install: string } }; + messages: { + noDevToolsInstalled: string; + askUserToInstallDevTools: string; + installingDevToolsProgress: string; + }; } = { - package: { - mcdev: { version: "mcdev --version", install: "npm install -g mcdev" } - }, - messages: { - noDevToolsInstalled: "SFMC DevTools could not be located on your system.", - askUserToInstallDevTools: "Would you like to install SFMC DevTools?", - installingDevToolsProgress: "Please wait while SFMC DevTools is being installed..." - } -}; \ No newline at end of file + package: { + mcdev: { version: "mcdev --version", install: "npm install -g mcdev" } + }, + messages: { + noDevToolsInstalled: "SFMC DevTools could not be located on your system.", + askUserToInstallDevTools: "Would you like to install SFMC DevTools?", + installingDevToolsProgress: "Please wait while SFMC DevTools is being installed..." + } +}; diff --git a/src/config/main.config.ts b/src/config/main.config.ts index 2b573d3..d290155 100644 --- a/src/config/main.config.ts +++ b/src/config/main.config.ts @@ -1,43 +1,43 @@ export const mainConfig: { - credentialsFilename: string, - requiredFiles: string[], - fileExtensions: string[], - noCopyFileExtensions: string[], - allPlaceholder: string, - extensionsDependencies: string[], - messages: { - selectedCredentialsBU: string, - selectCredential: string, - selectBusinessUnit: string, - selectCommandType: string, - selectCommand: string, - initDevTools: string, - initiatingDevTools: string, - copyToBUInput: string, - runningCommand: string, - successRunningCommand: string, - failureRunningCommand: string, - unsupportedMetadataType: string - } + credentialsFilename: string; + requiredFiles: string[]; + fileExtensions: string[]; + noCopyFileExtensions: string[]; + allPlaceholder: string; + extensionsDependencies: string[]; + messages: { + selectedCredentialsBU: string; + selectCredential: string; + selectBusinessUnit: string; + selectCommandType: string; + selectCommand: string; + initDevTools: string; + initiatingDevTools: string; + copyToBUInput: string; + runningCommand: string; + successRunningCommand: string; + failureRunningCommand: string; + unsupportedMetadataType: string; + }; } = { - credentialsFilename: ".mcdevrc.json", - requiredFiles: [".mcdevrc.json", ".mcdev-auth.json"], - fileExtensions: ["meta.json", "meta.sql", "meta.html", "meta.ssjs", "meta.amp", "doc.md"], - noCopyFileExtensions: ["doc.md"], - allPlaceholder: "*All*", - extensionsDependencies: ["IBM.output-colorizer"], - messages: { - selectedCredentialsBU: "Select a Credential/BU before running the command", - selectCredential: "Select one of the credentials below...", - selectBusinessUnit: "Select all or one of the business units below...", - selectCommandType: "Select one DevTools command type...", - selectCommand: "Select one DevTools Command...", - initDevTools: "Do you wish to initialize SFMC DevTools project in the current directory?", - initiatingDevTools: "Initiating SFMC DevTools project...", - copyToBUInput: "Select one of the actions below...", - runningCommand: "Running DevTools Command...", - successRunningCommand: "DevTools Command has run successfully.", - failureRunningCommand: "Oh no. Something went wrong while running DevTools Command.", - unsupportedMetadataType: "SFMC DevTools currently does not support one or more of the selected metadata types." - } -}; \ No newline at end of file + credentialsFilename: ".mcdevrc.json", + requiredFiles: [".mcdevrc.json", ".mcdev-auth.json"], + fileExtensions: ["meta.json", "meta.sql", "meta.html", "meta.ssjs", "meta.amp", "doc.md"], + noCopyFileExtensions: ["doc.md"], + allPlaceholder: "*All*", + extensionsDependencies: ["IBM.output-colorizer"], + messages: { + selectedCredentialsBU: "Select a Credential/BU before running the command", + selectCredential: "Select one of the credentials below...", + selectBusinessUnit: "Select all or one of the business units below...", + selectCommandType: "Select one DevTools command type...", + selectCommand: "Select one DevTools Command...", + initDevTools: "Do you wish to initialize SFMC DevTools project in the current directory?", + initiatingDevTools: "Initiating SFMC DevTools project...", + copyToBUInput: "Select one of the actions below...", + runningCommand: "Running DevTools Command...", + successRunningCommand: "DevTools Command has run successfully.", + failureRunningCommand: "Oh no. Something went wrong while running DevTools Command.", + unsupportedMetadataType: "SFMC DevTools currently does not support one or more of the selected metadata types." + } +}; diff --git a/src/config/metadatatypes.config.ts b/src/config/metadatatypes.config.ts index a7bb80e..35952b5 100644 --- a/src/config/metadatatypes.config.ts +++ b/src/config/metadatatypes.config.ts @@ -1,535 +1,532 @@ export const metadatatypes = [ - { - "name": "Asset-[Subtype]", - "apiName": "asset", - "retrieveByDefault": [ - "asset", - "code", - "textfile", - "block", - "message", - "template", - "other" - ], - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": false, - "changeKey": true, - "buildTemplate": true, - "retrieveAsTemplate": true - }, - "description": "Assets from Content Builder grouped into subtypes." - }, - { - "name": "Data Designer Attribute Groups", - "apiName": "attributeGroup", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": false, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "Groupings of Attribute Sets (Data Extensions) in Data Designer." - }, - { - "name": "Data Designer Attribute Sets", - "apiName": "attributeSet", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": false, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "Data Extensions linked together in Attribute Groups in Data Designer." - }, - { - "name": "Automation", - "apiName": "automation", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": true, - "buildTemplate": true, - "retrieveAsTemplate": true - }, - "description": "Used via Automation Studio directly - or indirectly via Journey Builder & MC Connect." - }, - { - "name": "Campaign Tag", - "apiName": "campaign", - "retrieveByDefault": false, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": false, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "Way of tagging/categorizing emails, journeys and alike." - }, - { - "name": "Content Area (Classic)", - "apiName": "contentArea", - "retrieveByDefault": false, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": false, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "DEPRECATED: Old way of saving Content Blocks; please migrate these to new Content Blocks (`Asset: ...`)." - }, - { - "name": "Data Extension", - "apiName": "dataExtension", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": true, - "buildTemplate": true, - "retrieveAsTemplate": true - }, - "description": "Database table schemas." - }, - { - "name": "Data Extension Field", - "apiName": "dataExtensionField", - "retrieveByDefault": false, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": true, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "Internal Type: Fields for type dataExtension." - }, - { - "name": "Data Extension Template", - "apiName": "dataExtensionTemplate", - "retrieveByDefault": false, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": false, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "Templates used for special DE use cases like Triggered Send." - }, - { - "name": "Automation: Data Extract Activity", - "apiName": "dataExtract", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": false, - "changeKey": true, - "buildTemplate": true, - "retrieveAsTemplate": true - }, - "description": "Creates zipped files in your FTP directory or convert XML into CSV." - }, - { - "name": "Data Extract Type", - "apiName": "dataExtractType", - "retrieveByDefault": false, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": false, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "Types of Data Extracts enabled for a specific business unit. This normally should not be stored." - }, - { - "name": "API Discovery", - "apiName": "discovery", - "retrieveByDefault": false, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": false, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "Description of all API endpoints accessible via REST API; only relevant for developers of Accenture SFMC DevTools." - }, - { - "name": "E-Mail (Classic)", - "apiName": "email", - "retrieveByDefault": false, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": false, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "DEPRECATED: Old way of saving E-Mails; please migrate these to new E-Mail (`Asset: message`)." - }, - { - "name": "E-Mail Send Definition", - "apiName": "emailSend", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": true, - "buildTemplate": true, - "retrieveAsTemplate": false - }, - "description": "Mainly used in Automations as \"Send Email Activity\"." - }, - { - "name": "Journey: Entry Event Definition", - "apiName": "event", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": true, - "buildTemplate": true, - "retrieveAsTemplate": true - }, - "description": "Used in Journeys (Interactions) to define Entry Events." - }, - { - "name": "File Location", - "apiName": "fileLocation", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": false, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "Used for export or import of files to/from Marketing Cloud. Previously this was labeled ftpLocation." - }, - { - "name": "Automation: File Transfer Activity", - "apiName": "fileTransfer", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": false, - "changeKey": true, - "buildTemplate": true, - "retrieveAsTemplate": true - }, - "description": "Unzip, decrypt a file or move a file from secure location into FTP directory." - }, - { - "name": "Automation: Filter Activity", - "apiName": "filter", - "retrieveByDefault": false, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": false, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "BETA: Part of how filtered Data Extensions are created. Depends on type \"FilterDefinitions\"." - }, - { - "name": "Folder", - "apiName": "folder", - "retrieveByDefault": false, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": false, - "changeKey": false, - "buildTemplate": true, - "retrieveAsTemplate": false - }, - "description": "Used to structure all kinds of other metadata." - }, - { - "name": "Automation: Import File Activity", - "apiName": "importFile", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": false, - "changeKey": true, - "buildTemplate": true, - "retrieveAsTemplate": true - }, - "description": "Reads files in FTP directory for further processing." - }, - { - "name": "Journey", - "apiName": "journey", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": false, - "buildTemplate": true, - "retrieveAsTemplate": false - }, - "description": "Journey (internally called \"Interaction\")." - }, - { - "name": "List", - "apiName": "list", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": true, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "Old way of storing data. Still used for central Email Subscriber DB." - }, - { - "name": "Mobile Code", - "apiName": "mobileCode", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": false, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "Used to send SMS Messages" - }, - { - "name": "Mobile Keyword", - "apiName": "mobileKeyword", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": false, - "buildTemplate": true, - "retrieveAsTemplate": true - }, - "description": "Used for managing subscriptions for Mobile numbers in Mobile Connect" - }, - { - "name": "MobileConnect SMS", - "apiName": "mobileMessage", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": false, - "buildTemplate": true, - "retrieveAsTemplate": false - }, - "description": "Used by Journey Builder and to send SMS from MobileConnect triggered by API or manually on-the-fly" - }, - { - "name": "Automation: SQL Query Activity", - "apiName": "query", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": true, - "buildTemplate": true, - "retrieveAsTemplate": true - }, - "description": "Select & transform data using SQL." - }, - { - "name": "Role", - "apiName": "role", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": false, - "changeKey": true, - "buildTemplate": true, - "retrieveAsTemplate": false - }, - "description": "User Roles define groups that are used to grant users access to SFMC systems." - }, - { - "name": "Automation: Script Activity", - "apiName": "script", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": false, - "changeKey": null, - "buildTemplate": true, - "retrieveAsTemplate": true - }, - "description": "Execute more complex tasks via SSJS or AMPScript." - }, - { - "name": "Send Classification", - "apiName": "sendClassification", - "retrieveByDefault": false, - "supports": { - "retrieve": true, - "create": false, - "update": false, - "delete": false, - "changeKey": false, - "buildTemplate": false, - "retrieveAsTemplate": false - }, - "description": "Lets admins define Delivery Profile, Sender Profile and CAN-SPAM for an email job in a central location." - }, - { - "name": "Transactional Email", - "apiName": "transactionalEmail", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": false, - "buildTemplate": true, - "retrieveAsTemplate": false - }, - "description": "Lets you send immediate Email messages via API events" - }, - { - "name": "Transactional Push", - "apiName": "transactionalPush", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": false, - "buildTemplate": true, - "retrieveAsTemplate": false - }, - "description": "Lets you send immediate Push messages via API events" - }, - { - "name": "Transactional SMS", - "apiName": "transactionalSMS", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": false, - "buildTemplate": true, - "retrieveAsTemplate": false - }, - "description": "Lets you send immediate SMS messages via API events" - }, - { - "name": "Triggered Send", - "apiName": "triggeredSend", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": true, - "buildTemplate": true, - "retrieveAsTemplate": false - }, - "description": "DEPRECATED: Sends emails via API or DataExtension Event." - }, - { - "name": "User", - "apiName": "user", - "retrieveByDefault": false, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": false, - "changeKey": true, - "buildTemplate": true, - "retrieveAsTemplate": false - }, - "description": "Marketing Cloud users" - }, - { - "name": "Automation: Verification Activity", - "apiName": "verification", - "retrieveByDefault": true, - "supports": { - "retrieve": true, - "create": true, - "update": true, - "delete": true, - "changeKey": false, - "buildTemplate": true, - "retrieveAsTemplate": false - }, - "description": "Check DataExtension for a row count" - } -]; \ No newline at end of file + { + name: "Asset-[Subtype]", + apiName: "asset", + retrieveByDefault: ["asset", "code", "textfile", "block", "message", "template", "other"], + supports: { + retrieve: true, + create: true, + update: true, + delete: false, + changeKey: true, + buildTemplate: true, + retrieveAsTemplate: true + }, + description: "Assets from Content Builder grouped into subtypes." + }, + { + name: "Data Designer Attribute Groups", + apiName: "attributeGroup", + retrieveByDefault: true, + supports: { + retrieve: true, + create: false, + update: false, + delete: false, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: "Groupings of Attribute Sets (Data Extensions) in Data Designer." + }, + { + name: "Data Designer Attribute Sets", + apiName: "attributeSet", + retrieveByDefault: true, + supports: { + retrieve: true, + create: false, + update: false, + delete: false, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: "Data Extensions linked together in Attribute Groups in Data Designer." + }, + { + name: "Automation", + apiName: "automation", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: true, + buildTemplate: true, + retrieveAsTemplate: true + }, + description: "Used via Automation Studio directly - or indirectly via Journey Builder & MC Connect." + }, + { + name: "Campaign Tag", + apiName: "campaign", + retrieveByDefault: false, + supports: { + retrieve: true, + create: false, + update: false, + delete: false, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: "Way of tagging/categorizing emails, journeys and alike." + }, + { + name: "Content Area (Classic)", + apiName: "contentArea", + retrieveByDefault: false, + supports: { + retrieve: true, + create: false, + update: false, + delete: false, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: + "DEPRECATED: Old way of saving Content Blocks; please migrate these to new Content Blocks (`Asset: ...`)." + }, + { + name: "Data Extension", + apiName: "dataExtension", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: true, + buildTemplate: true, + retrieveAsTemplate: true + }, + description: "Database table schemas." + }, + { + name: "Data Extension Field", + apiName: "dataExtensionField", + retrieveByDefault: false, + supports: { + retrieve: true, + create: false, + update: false, + delete: true, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: "Internal Type: Fields for type dataExtension." + }, + { + name: "Data Extension Template", + apiName: "dataExtensionTemplate", + retrieveByDefault: false, + supports: { + retrieve: true, + create: false, + update: false, + delete: false, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: "Templates used for special DE use cases like Triggered Send." + }, + { + name: "Automation: Data Extract Activity", + apiName: "dataExtract", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: false, + changeKey: true, + buildTemplate: true, + retrieveAsTemplate: true + }, + description: "Creates zipped files in your FTP directory or convert XML into CSV." + }, + { + name: "Data Extract Type", + apiName: "dataExtractType", + retrieveByDefault: false, + supports: { + retrieve: true, + create: false, + update: false, + delete: false, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: "Types of Data Extracts enabled for a specific business unit. This normally should not be stored." + }, + { + name: "API Discovery", + apiName: "discovery", + retrieveByDefault: false, + supports: { + retrieve: true, + create: false, + update: false, + delete: false, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: + "Description of all API endpoints accessible via REST API; only relevant for developers of Accenture SFMC DevTools." + }, + { + name: "E-Mail (Classic)", + apiName: "email", + retrieveByDefault: false, + supports: { + retrieve: true, + create: false, + update: false, + delete: false, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: "DEPRECATED: Old way of saving E-Mails; please migrate these to new E-Mail (`Asset: message`)." + }, + { + name: "E-Mail Send Definition", + apiName: "emailSend", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: true, + buildTemplate: true, + retrieveAsTemplate: false + }, + description: 'Mainly used in Automations as "Send Email Activity".' + }, + { + name: "Journey: Entry Event Definition", + apiName: "event", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: true, + buildTemplate: true, + retrieveAsTemplate: true + }, + description: "Used in Journeys (Interactions) to define Entry Events." + }, + { + name: "File Location", + apiName: "fileLocation", + retrieveByDefault: true, + supports: { + retrieve: true, + create: false, + update: false, + delete: false, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: + "Used for export or import of files to/from Marketing Cloud. Previously this was labeled ftpLocation." + }, + { + name: "Automation: File Transfer Activity", + apiName: "fileTransfer", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: false, + changeKey: true, + buildTemplate: true, + retrieveAsTemplate: true + }, + description: "Unzip, decrypt a file or move a file from secure location into FTP directory." + }, + { + name: "Automation: Filter Activity", + apiName: "filter", + retrieveByDefault: false, + supports: { + retrieve: true, + create: false, + update: false, + delete: false, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: 'BETA: Part of how filtered Data Extensions are created. Depends on type "FilterDefinitions".' + }, + { + name: "Folder", + apiName: "folder", + retrieveByDefault: false, + supports: { + retrieve: true, + create: true, + update: true, + delete: false, + changeKey: false, + buildTemplate: true, + retrieveAsTemplate: false + }, + description: "Used to structure all kinds of other metadata." + }, + { + name: "Automation: Import File Activity", + apiName: "importFile", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: false, + changeKey: true, + buildTemplate: true, + retrieveAsTemplate: true + }, + description: "Reads files in FTP directory for further processing." + }, + { + name: "Journey", + apiName: "journey", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: false, + buildTemplate: true, + retrieveAsTemplate: false + }, + description: 'Journey (internally called "Interaction").' + }, + { + name: "List", + apiName: "list", + retrieveByDefault: true, + supports: { + retrieve: true, + create: false, + update: false, + delete: true, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: "Old way of storing data. Still used for central Email Subscriber DB." + }, + { + name: "Mobile Code", + apiName: "mobileCode", + retrieveByDefault: true, + supports: { + retrieve: true, + create: false, + update: false, + delete: false, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: "Used to send SMS Messages" + }, + { + name: "Mobile Keyword", + apiName: "mobileKeyword", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: false, + buildTemplate: true, + retrieveAsTemplate: true + }, + description: "Used for managing subscriptions for Mobile numbers in Mobile Connect" + }, + { + name: "MobileConnect SMS", + apiName: "mobileMessage", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: false, + buildTemplate: true, + retrieveAsTemplate: false + }, + description: + "Used by Journey Builder and to send SMS from MobileConnect triggered by API or manually on-the-fly" + }, + { + name: "Automation: SQL Query Activity", + apiName: "query", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: true, + buildTemplate: true, + retrieveAsTemplate: true + }, + description: "Select & transform data using SQL." + }, + { + name: "Role", + apiName: "role", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: false, + changeKey: true, + buildTemplate: true, + retrieveAsTemplate: false + }, + description: "User Roles define groups that are used to grant users access to SFMC systems." + }, + { + name: "Automation: Script Activity", + apiName: "script", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: false, + changeKey: null, + buildTemplate: true, + retrieveAsTemplate: true + }, + description: "Execute more complex tasks via SSJS or AMPScript." + }, + { + name: "Send Classification", + apiName: "sendClassification", + retrieveByDefault: false, + supports: { + retrieve: true, + create: false, + update: false, + delete: false, + changeKey: false, + buildTemplate: false, + retrieveAsTemplate: false + }, + description: + "Lets admins define Delivery Profile, Sender Profile and CAN-SPAM for an email job in a central location." + }, + { + name: "Transactional Email", + apiName: "transactionalEmail", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: false, + buildTemplate: true, + retrieveAsTemplate: false + }, + description: "Lets you send immediate Email messages via API events" + }, + { + name: "Transactional Push", + apiName: "transactionalPush", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: false, + buildTemplate: true, + retrieveAsTemplate: false + }, + description: "Lets you send immediate Push messages via API events" + }, + { + name: "Transactional SMS", + apiName: "transactionalSMS", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: false, + buildTemplate: true, + retrieveAsTemplate: false + }, + description: "Lets you send immediate SMS messages via API events" + }, + { + name: "Triggered Send", + apiName: "triggeredSend", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: true, + buildTemplate: true, + retrieveAsTemplate: false + }, + description: "DEPRECATED: Sends emails via API or DataExtension Event." + }, + { + name: "User", + apiName: "user", + retrieveByDefault: false, + supports: { + retrieve: true, + create: true, + update: true, + delete: false, + changeKey: true, + buildTemplate: true, + retrieveAsTemplate: false + }, + description: "Marketing Cloud users" + }, + { + name: "Automation: Verification Activity", + apiName: "verification", + retrieveByDefault: true, + supports: { + retrieve: true, + create: true, + update: true, + delete: true, + changeKey: false, + buildTemplate: true, + retrieveAsTemplate: false + }, + description: "Check DataExtension for a row count" + } +]; diff --git a/src/config/prerequisites.config.ts b/src/config/prerequisites.config.ts index 4fbaf9c..72d7d40 100644 --- a/src/config/prerequisites.config.ts +++ b/src/config/prerequisites.config.ts @@ -1,28 +1,28 @@ export enum NoPrerequisitesResponseOptions { - "Yes" = 1, - "No" = 0 -}; + "Yes" = 1, + "No" = 0 +} export const prerequisitesConfig: { - packages: { node: string, git: string }, - messages: { - onePrerequisiteMissing: string, - multiplePrerequisitesMissing: string, - askPrerequisitesToUser: string - }, - webview: { id: string, title: string, filename: string } + packages: { node: string; git: string }; + messages: { + onePrerequisiteMissing: string; + multiplePrerequisitesMissing: string; + askPrerequisitesToUser: string; + }; + webview: { id: string; title: string; filename: string }; } = { - packages: { - node: "node -v", - git: "git --version" - }, - messages: { - onePrerequisiteMissing: "Unfortunately the SFMC DevTools Pre-Requisite {{prerequisites}} is missing.", - multiplePrerequisitesMissing: "Unfortunately the SFMC DevTools Pre-Requisites {{prerequisites}} are missing.", - askPrerequisitesToUser: "Do you want to open the installation guide?" - }, - webview: { - id: "prerequisitesPanel", - title: "Prerequisites Installation", - filename: "devtoolsPrerequisites" - } -}; \ No newline at end of file + packages: { + node: "node -v", + git: "git --version" + }, + messages: { + onePrerequisiteMissing: "Unfortunately the SFMC DevTools Pre-Requisite {{prerequisites}} is missing.", + multiplePrerequisitesMissing: "Unfortunately the SFMC DevTools Pre-Requisites {{prerequisites}} are missing.", + askPrerequisitesToUser: "Do you want to open the installation guide?" + }, + webview: { + id: "prerequisitesPanel", + title: "Prerequisites Installation", + filename: "devtoolsPrerequisites" + } +}; diff --git a/src/devtools/commands/DevToolsAdminCommands.ts b/src/devtools/commands/DevToolsAdminCommands.ts index 213b9a9..58f2854 100644 --- a/src/devtools/commands/DevToolsAdminCommands.ts +++ b/src/devtools/commands/DevToolsAdminCommands.ts @@ -5,119 +5,111 @@ import SupportedMetadataTypes from "../../shared/interfaces/supportedMetadataTyp import { log } from "../../editor/output"; class DevToolsAdminCommands extends DevToolsCommands { + private commandMethods: { + [key: string]: ( + config: DevToolsCommandSetting, + args: { [key: string]: string | string[] | boolean }, + path: string, + commandHandlers: { [key: string]: (args?: any) => void } + ) => void; + } = {}; + constructor() { + super(); + log("debug", "DevToolsAdminCommands Class created"); + this.commandMethods = { + init: this.init.bind(this), + etypes: this.explainTypes.bind(this) + }; + } - private commandMethods: { - [key: string]: ( - config: DevToolsCommandSetting, - args: {[key: string]: string | string[] | boolean }, - path: string, - commandHandlers: { [key: string]: (args?: any) => void } - ) => void - } = {}; - constructor(){ - super(); - log("debug", "DevToolsAdminCommands Class created"); - this.commandMethods = { - init: this.init.bind(this), - etypes: this.explainTypes.bind(this) - }; - } + run(commandRunner: DevToolsCommandRunner): void { + const { commandId, commandConfig, commandArgs, commandPath, commandHandlers }: DevToolsCommandRunner = + commandRunner; - run(commandRunner: DevToolsCommandRunner): void { - const { - commandId, - commandConfig, - commandArgs, - commandPath, - commandHandlers - }: DevToolsCommandRunner = commandRunner; + log("debug", `Running DevTools Admin Command for id '${commandId}'.`); + if (commandId in this.commandMethods) { + this.commandMethods[commandId](commandConfig, commandArgs, commandPath, commandHandlers); + } else { + log("error", `DevTools Admin Command method for id '${commandId}' is not implemented.`); + } + } - log("debug", `Running DevTools Admin Command for id '${commandId}'.`); - if(commandId in this.commandMethods){ - this.commandMethods[commandId](commandConfig, commandArgs, commandPath, commandHandlers); - }else{ - log("error", `DevTools Admin Command method for id '${commandId}' is not implemented.`); - } - } + getMetadataTypes(): SupportedMetadataTypes[] | void {} + setMetadataTypes(_: SupportedMetadataTypes[]): void {} + isSupportedMetadataType(_action: string, _metadataType: string): boolean | void {} - getMetadataTypes(): SupportedMetadataTypes[] | void {} - setMetadataTypes(_: SupportedMetadataTypes[]): void {} - isSupportedMetadataType(_action: string, _metadataType: string): boolean | 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) { + for (const param of config.requiredParams) { + const userResponse: string | undefined = await this.handleUserInputBox(param); + if (!userResponse) { + log("debug", `User did not insert a param for '${param}'`); + break; + } - async init( - config: DevToolsCommandSetting, - _: {[key: string]: string | string[] | boolean}, - path: string, - { handleCommandResult }: { [key: string]: (args?: any) => void }){ + // Remove all whitespace from response (including spaces, tabs and newline characters) + initArgs[param] = userResponse.replace(/\s/g, ""); + } + log("debug", `Init payload: ${JSON.stringify(initArgs)}`); - log("info", `Running DevTools Admin Command: Init...`); - const initArgs: {[key: string]: string } = {}; - if("command" in config && config.command){ - for(const param of config.requiredParams){ - const userResponse: string | undefined = await this.handleUserInputBox(param); - if(!userResponse){ - log("debug", `User did not insert a param for '${param}'`); - break; - } + const commandConfigured: string | undefined = await this.configureCommandWithParameters( + config, + initArgs, + [] + ); + // 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}`); + 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."); + } + } - // Remove all whitespace from response (including spaces, tabs and newline characters) - initArgs[param] = userResponse.replace(/\s/g, ''); - } - log("debug", `Init payload: ${JSON.stringify(initArgs)}`); - - const commandConfigured: string | undefined = - await this.configureCommandWithParameters( - config, - initArgs, - [] - ); - // 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}`); - 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]: 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}`); - } - } + 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}`); + } + } } -export default DevToolsAdminCommands; \ No newline at end of file +export default DevToolsAdminCommands; diff --git a/src/devtools/commands/DevToolsCommands.ts b/src/devtools/commands/DevToolsCommands.ts index fc89c09..39824f7 100644 --- a/src/devtools/commands/DevToolsCommands.ts +++ b/src/devtools/commands/DevToolsCommands.ts @@ -11,229 +11,225 @@ import { terminal } from "../../shared/utils/terminal"; import { metadatatypes } from "../../config/metadatatypes.config"; abstract class DevToolsCommands { - - static readonly commandPrefix: string = "mcdev"; - static commandMap: { [key: string]: DevToolsCommands }; - - abstract run(commandRunner: DevToolsCommandRunner): void; - abstract setMetadataTypes(mdTypes: SupportedMetadataTypes[]): void; - abstract getMetadataTypes(): SupportedMetadataTypes[] | void; - abstract isSupportedMetadataType(action: string, metadataType: string): boolean | void; - - executeCommand(command: string, path: string, showOnTerminal: boolean): Promise{ - log("info", `Running DevTools Command: ${command}`); - return new Promise(async resolve => { - terminal.executeTerminalCommand({ - command: command, - args: [], - cwd: path, - handleResult(error: string | null, output: string | null, code: number | null) { - if(code !== null){ - log("debug", `[DevToolsCommands_executeCommand] Exit Code: '${code}'`); - resolve(code); - } - if(error){ - log("error", `[DevToolsCommands_executeCommand] Exit Code: ${error}`); - } - if(output){ - showOnTerminal ? log("info", output) : resolve(output); - } - }, - }); - }); - } - - async configureCommandWithParameters( - config: DevToolsCommandSetting, - args: {[key: string]: string | string[] | boolean }, - mdTypes: SupportedMetadataTypes[]): Promise { - - log("debug", `ConfigureCommandWithParameters: ${JSON.stringify(config)}`); - let { command } = config; - // Configured required Params - 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] as string); - }else{ - // Requests user - if(param.toLowerCase() === "mdtypes" && mdTypes.length){ - const userSelecteMDTypes: string | undefined = - await this.handleMetadataTypeRequest(mdTypes); - if(userSelecteMDTypes){ - command = command.replace(`{{${param}}}`, `"${userSelecteMDTypes}"`); - } - } - } - } - } - // Configured optional Params - if("optionalParams" in config && config.optionalParams.length){ - config.optionalParams.forEach((param: string) => { - if(typeof args[param] === "boolean"){ - // if args[paran] is true it puts in the command the format --param (eg --json --fromRetrieve) - args[param] = args[param] - ? `--${param}` - : ""; - } - command = command.replace(`{{${param}}}`, param in args ? args[param] as string : ""); - }); - } - return command; - } - - async handleMetadataTypeRequest(mdTypes: SupportedMetadataTypes[]): Promise { - const mdTypeInputOptions: InputOptionsSettings[] = - mdTypes.map((mdType: SupportedMetadataTypes) => ({ - id: mdType.apiName, - label: mdType.name, - detail: "" - })); - const userResponse: InputOptionsSettings | InputOptionsSettings[] | undefined = - await editorInput.handleQuickPickSelection( - mdTypeInputOptions, - "Please select one or multiple metadata types...", - true - ); - if(userResponse && Array.isArray(userResponse)){ - const mdTypes: string = `${userResponse.map((response: InputOptionsSettings) => response.id)}`; - log("debug", - `User selected metadata types: "${mdTypes}"` - ); - return mdTypes; - } - return; - } - - hasPlaceholders(command: string): boolean { - const pattern: RegExp = /{{.*?}}/g; - return pattern.test(command); - } - - async handleUserInputBox(placeholderText: string): Promise { - const response: string | undefined = await editorInput.handleShowInputBox(placeholderText); - return response; - } - - static init(){ - log("info", "Initializing DevTools Commands..."); - if(!this.commandMap){ - const commandTypes: {id: string}[] = this.getAllCommandTypes(); - this.commandMap = commandTypes.reduce((previous: {}, { id }: { id: string }) => { - try{ - // Instanciates a new type with all the commands by type configured - const devToolsClass: DevToolsCommands = - new (require(`./DevTools${lib.capitalizeFirstLetter(id)}Commands`)).default(); - - return { ...previous, [id.toLowerCase()]: devToolsClass }; - }catch(error){ - log("error", - `DevToolsCommands_init: Command type '${lib.capitalizeFirstLetter(id)}' doesn't have a class configured: ${error}` - ); - return { ...previous }; - } - }, {}); - } - - // Sends the supported mtdata types to each DevTools Command - Object.keys(this.commandMap).forEach((key: string) => { - const devToolCommand: DevToolsCommands = - this.commandMap[key]; - const sortedSuppMdtByName: SupportedMetadataTypes[] = - metadatatypes.sort((a, b) => a.name.localeCompare(b.name)); - devToolCommand.setMetadataTypes(sortedSuppMdtByName); - }); - } - - static async runCommand( - typeId: string | null, - commandId: string, - commandPath: string, - 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 }[] = - this.getAllCommandTypes().filter(({ id }: {id: string}) => - this.getCommandsListByType(id) - .filter(({ id }) => id === commandId).length > 0 - ); - - if(id !== undefined){ - typeId = id; - }else{ - log("error", - `DevToolsCommands_runCommand: Failed to retrieve the command type for command id '${commandId}'.` - ); - return; - } - } - - if(this.commandMap){ - if(typeId && typeId in this.commandMap){ - const [ commandConfig ]: DevToolsCommandSetting[] = - this.getCommandsListByType(typeId) - .filter((commandSetting: DevToolsCommandSetting) => commandSetting.id === commandId); - - if(commandConfig && Object.keys(commandConfig).length){ - const devToolsCommandClass: DevToolsCommands = this.commandMap[typeId]; - devToolsCommandClass.run({ - commandId, - commandConfig, - commandArgs: args, - commandPath, - commandHandlers: commandHandlers - }); - return; - } - log("error", - `DevToolsCommands_runCommand: Command with Id '${commandId}' doesn't have a DevTools command configured.` - ); - return; - } - log("error", - `DevToolsCommands_runCommand: Command type Id '${typeId}' doesn't have a DevTools Command Class configured.` - ); - return; - } - log("error", - `[DevToolsCommands_runCommand] Error: Command Map is not configured.` - ); - } - - static getAllCommandTypes(): Array<{id: string, title: string}>{ - return Object.keys(commandsConfig) - .filter((cmd: string) => commandsConfig[cmd as keyof typeof commandsConfig].isAvailable) - .map((cmd: string) => ({ - id: commandsConfig[cmd as keyof typeof commandsConfig].id, - title: commandsConfig[cmd as keyof typeof commandsConfig].title - })); - } - - static getCommandsListByType(type: string): DevToolsCommandSetting[]{ - const { commands } = commandsConfig[type.toLowerCase() as keyof typeof commandsConfig]; - return commands ? - commands.filter((command: DevToolsCommandSetting) => command.isAvailable) : []; - } - - static requiresCredentials(id: string): boolean { - if(id in commandsConfig){ - return commandsConfig[id as keyof typeof commandsConfig].requireCredentials; - } - log("error", `[DevToolsCommands_runCommand] Error: Failed to retrieve ${id} from commands configuration.`); - return false; - } - - static isSupportedMetadataType(action: string, metadataType: string){ - if("standard" in this.commandMap){ - const devToolsCommand: DevToolsCommands = this.commandMap["standard"]; - return devToolsCommand.isSupportedMetadataType(action, metadataType); - } - log("error", - `[DevToolsCommands_isSupportedMetadataType] Error: Failed to retrieve DevTools Standard Commands from commands configuration.` - ); - return false; - } + static readonly commandPrefix: string = "mcdev"; + static commandMap: { [key: string]: DevToolsCommands }; + + abstract run(commandRunner: DevToolsCommandRunner): void; + abstract setMetadataTypes(mdTypes: SupportedMetadataTypes[]): void; + abstract getMetadataTypes(): SupportedMetadataTypes[] | void; + abstract isSupportedMetadataType(action: string, metadataType: string): boolean | void; + + executeCommand(command: string, path: string, showOnTerminal: boolean): Promise { + log("info", `Running DevTools Command: ${command}`); + return new Promise(async resolve => { + terminal.executeTerminalCommand({ + command: command, + args: [], + cwd: path, + handleResult(error: string | null, output: string | null, code: number | null) { + if (code !== null) { + log("debug", `[DevToolsCommands_executeCommand] Exit Code: '${code}'`); + resolve(code); + } + if (error) { + log("error", `[DevToolsCommands_executeCommand] Exit Code: ${error}`); + } + if (output) { + showOnTerminal ? log("info", output) : resolve(output); + } + } + }); + }); + } + + async configureCommandWithParameters( + config: DevToolsCommandSetting, + args: { [key: string]: string | string[] | boolean }, + mdTypes: SupportedMetadataTypes[] + ): Promise { + log("debug", `ConfigureCommandWithParameters: ${JSON.stringify(config)}`); + let { command } = config; + // Configured required Params + 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] as string); + } else { + // Requests user + if (param.toLowerCase() === "mdtypes" && mdTypes.length) { + const userSelecteMDTypes: string | undefined = await this.handleMetadataTypeRequest(mdTypes); + if (userSelecteMDTypes) { + command = command.replace(`{{${param}}}`, `"${userSelecteMDTypes}"`); + } + } + } + } + } + // Configured optional Params + if ("optionalParams" in config && config.optionalParams.length) { + config.optionalParams.forEach((param: string) => { + if (typeof args[param] === "boolean") { + // if args[paran] is true it puts in the command the format --param (eg --json --fromRetrieve) + args[param] = args[param] ? `--${param}` : ""; + } + command = command.replace(`{{${param}}}`, param in args ? (args[param] as string) : ""); + }); + } + return command; + } + + async handleMetadataTypeRequest(mdTypes: SupportedMetadataTypes[]): Promise { + const mdTypeInputOptions: InputOptionsSettings[] = mdTypes.map((mdType: SupportedMetadataTypes) => ({ + id: mdType.apiName, + label: mdType.name, + detail: "" + })); + const userResponse: InputOptionsSettings | InputOptionsSettings[] | undefined = + await editorInput.handleQuickPickSelection( + mdTypeInputOptions, + "Please select one or multiple metadata types...", + true + ); + if (userResponse && Array.isArray(userResponse)) { + const mdTypes: string = `${userResponse.map((response: InputOptionsSettings) => response.id)}`; + log("debug", `User selected metadata types: "${mdTypes}"`); + return mdTypes; + } + return; + } + + hasPlaceholders(command: string): boolean { + const pattern: RegExp = /{{.*?}}/g; + return pattern.test(command); + } + + async handleUserInputBox(placeholderText: string): Promise { + const response: string | undefined = await editorInput.handleShowInputBox(placeholderText); + return response; + } + + static init() { + log("info", "Initializing DevTools Commands..."); + if (!this.commandMap) { + const commandTypes: { id: string }[] = this.getAllCommandTypes(); + this.commandMap = commandTypes.reduce((previous: {}, { id }: { id: string }) => { + try { + // Instanciates a new type with all the commands by type configured + const devToolsClass: DevToolsCommands = new (require( + `./DevTools${lib.capitalizeFirstLetter(id)}Commands` + ).default)(); + + return { ...previous, [id.toLowerCase()]: devToolsClass }; + } catch (error) { + log( + "error", + `DevToolsCommands_init: Command type '${lib.capitalizeFirstLetter(id)}' doesn't have a class configured: ${error}` + ); + return { ...previous }; + } + }, {}); + } + + // Sends the supported mtdata types to each DevTools Command + Object.keys(this.commandMap).forEach((key: string) => { + const devToolCommand: DevToolsCommands = this.commandMap[key]; + const sortedSuppMdtByName: SupportedMetadataTypes[] = metadatatypes.sort((a, b) => + a.name.localeCompare(b.name) + ); + devToolCommand.setMetadataTypes(sortedSuppMdtByName); + }); + } + + static async runCommand( + typeId: string | null, + commandId: string, + commandPath: string, + 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 }[] = this.getAllCommandTypes().filter( + ({ id }: { id: string }) => + this.getCommandsListByType(id).filter(({ id }) => id === commandId).length > 0 + ); + + if (id !== undefined) { + typeId = id; + } else { + log( + "error", + `DevToolsCommands_runCommand: Failed to retrieve the command type for command id '${commandId}'.` + ); + return; + } + } + + if (this.commandMap) { + if (typeId && typeId in this.commandMap) { + const [commandConfig]: DevToolsCommandSetting[] = this.getCommandsListByType(typeId).filter( + (commandSetting: DevToolsCommandSetting) => commandSetting.id === commandId + ); + + if (commandConfig && Object.keys(commandConfig).length) { + const devToolsCommandClass: DevToolsCommands = this.commandMap[typeId]; + devToolsCommandClass.run({ + commandId, + commandConfig, + commandArgs: args, + commandPath, + commandHandlers: commandHandlers + }); + return; + } + log( + "error", + `DevToolsCommands_runCommand: Command with Id '${commandId}' doesn't have a DevTools command configured.` + ); + return; + } + log( + "error", + `DevToolsCommands_runCommand: Command type Id '${typeId}' doesn't have a DevTools Command Class configured.` + ); + return; + } + log("error", `[DevToolsCommands_runCommand] Error: Command Map is not configured.`); + } + + static getAllCommandTypes(): Array<{ id: string; title: string }> { + return Object.keys(commandsConfig) + .filter((cmd: string) => commandsConfig[cmd as keyof typeof commandsConfig].isAvailable) + .map((cmd: string) => ({ + id: commandsConfig[cmd as keyof typeof commandsConfig].id, + title: commandsConfig[cmd as keyof typeof commandsConfig].title + })); + } + + static getCommandsListByType(type: string): DevToolsCommandSetting[] { + const { commands } = commandsConfig[type.toLowerCase() as keyof typeof commandsConfig]; + return commands ? commands.filter((command: DevToolsCommandSetting) => command.isAvailable) : []; + } + + static requiresCredentials(id: string): boolean { + if (id in commandsConfig) { + return commandsConfig[id as keyof typeof commandsConfig].requireCredentials; + } + log("error", `[DevToolsCommands_runCommand] Error: Failed to retrieve ${id} from commands configuration.`); + return false; + } + + static isSupportedMetadataType(action: string, metadataType: string) { + if ("standard" in this.commandMap) { + const devToolsCommand: DevToolsCommands = this.commandMap["standard"]; + return devToolsCommand.isSupportedMetadataType(action, metadataType); + } + log( + "error", + `[DevToolsCommands_isSupportedMetadataType] Error: Failed to retrieve DevTools Standard Commands from commands configuration.` + ); + return false; + } } -export default DevToolsCommands; \ No newline at end of file +export default DevToolsCommands; diff --git a/src/devtools/commands/DevToolsStandardCommands.ts b/src/devtools/commands/DevToolsStandardCommands.ts index b815b77..7df04cc 100644 --- a/src/devtools/commands/DevToolsStandardCommands.ts +++ b/src/devtools/commands/DevToolsStandardCommands.ts @@ -5,145 +5,139 @@ import DevToolsCommandRunner from "../../shared/interfaces/devToolsCommandRunner import { log } from "../../editor/output"; class DevToolsStandardCommands extends DevToolsCommands { - - private commandMethods: { - [key: string]: ( - config: DevToolsCommandSetting, - args: {[key: string]: string | string[] | boolean }, - path: string, - commandHandlers: { [key: string]: (args?: any) => void } - ) => void - } = {}; - private metadataTypes: SupportedMetadataTypes[] = []; - constructor(){ - super(); - log("debug", "DevToolsStandardCommands Class created"); - this.commandMethods = { - retrieve: this.retrieve.bind(this), - deploy: this.deploy.bind(this) - }; - } - - run(commandRunner: DevToolsCommandRunner): void { - const { - commandId, - commandConfig, - commandArgs, - commandPath, - commandHandlers - }: DevToolsCommandRunner = commandRunner; - - log("debug", `Running DevTools Standard Command for id '${commandId}'.`); - if(commandId in this.commandMethods){ - this.commandMethods[commandId](commandConfig, commandArgs, commandPath, commandHandlers); - }else{ - log("error", `DevTools Standard Command method for id '${commandId}' is not implemented.`); - } - } - - setMetadataTypes(mdTypes: SupportedMetadataTypes[]): void { - this.metadataTypes = mdTypes; - } - - getMetadataTypes(): SupportedMetadataTypes[]{ - return this.metadataTypes; - } - - getSupportedMetadataTypeByAction(action: string){ - const supportedActions: {[key: string]: () => SupportedMetadataTypes[]} = { - "retrieve": () => this.getMetadataTypes() - .filter((mdType: SupportedMetadataTypes) => mdType.supports.retrieve), - "deploy": () => this.getMetadataTypes() - .filter((mdType: SupportedMetadataTypes) => mdType.supports.create || mdType.supports.update) - }; - if(action in supportedActions){ - return supportedActions[action](); - } - log( - "error", - `DevToolsStandardCommand_getSupportedMetadataTypeByAction: Failed to retrieve supported Metadata Types for action ${action}.` - ); - return []; - } - - 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 - const supportedMdTypes: SupportedMetadataTypes[] = this.getSupportedMetadataTypeByAction("retrieve"); - - // Configures the command to replace all the parameters with the values - const commandConfigured: string | undefined = - await this.configureCommandWithParameters( - config, - args, - supportedMdTypes - ); - - // 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}`); - 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]: 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 - const supportedMdTypes: SupportedMetadataTypes[] = this.getSupportedMetadataTypeByAction("deploy"); - - // Configures the command to replace all the parameters with the values - const commandConfigured: string | undefined = - await this.configureCommandWithParameters( - config, - args, - supportedMdTypes - ); - - // 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}`); - 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."); - } - } - - isSupportedMetadataType(action: string, metadataType: string){ - const filteredMdtTypeByAction = this.getSupportedMetadataTypeByAction(action); - return filteredMdtTypeByAction.some((mdtType: SupportedMetadataTypes) => mdtType.apiName === metadataType); - } + private commandMethods: { + [key: string]: ( + config: DevToolsCommandSetting, + args: { [key: string]: string | string[] | boolean }, + path: string, + commandHandlers: { [key: string]: (args?: any) => void } + ) => void; + } = {}; + private metadataTypes: SupportedMetadataTypes[] = []; + constructor() { + super(); + log("debug", "DevToolsStandardCommands Class created"); + this.commandMethods = { + retrieve: this.retrieve.bind(this), + deploy: this.deploy.bind(this) + }; + } + + run(commandRunner: DevToolsCommandRunner): void { + const { commandId, commandConfig, commandArgs, commandPath, commandHandlers }: DevToolsCommandRunner = + commandRunner; + + log("debug", `Running DevTools Standard Command for id '${commandId}'.`); + if (commandId in this.commandMethods) { + this.commandMethods[commandId](commandConfig, commandArgs, commandPath, commandHandlers); + } else { + log("error", `DevTools Standard Command method for id '${commandId}' is not implemented.`); + } + } + + setMetadataTypes(mdTypes: SupportedMetadataTypes[]): void { + this.metadataTypes = mdTypes; + } + + getMetadataTypes(): SupportedMetadataTypes[] { + return this.metadataTypes; + } + + getSupportedMetadataTypeByAction(action: string) { + const supportedActions: { [key: string]: () => SupportedMetadataTypes[] } = { + retrieve: () => + this.getMetadataTypes().filter((mdType: SupportedMetadataTypes) => mdType.supports.retrieve), + deploy: () => + this.getMetadataTypes().filter( + (mdType: SupportedMetadataTypes) => mdType.supports.create || mdType.supports.update + ) + }; + if (action in supportedActions) { + return supportedActions[action](); + } + log( + "error", + `DevToolsStandardCommand_getSupportedMetadataTypeByAction: Failed to retrieve supported Metadata Types for action ${action}.` + ); + return []; + } + + 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 + const supportedMdTypes: SupportedMetadataTypes[] = this.getSupportedMetadataTypeByAction("retrieve"); + + // Configures the command to replace all the parameters with the values + const commandConfigured: string | undefined = await this.configureCommandWithParameters( + config, + args, + supportedMdTypes + ); + + // 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}`); + 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]: 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 + const supportedMdTypes: SupportedMetadataTypes[] = this.getSupportedMetadataTypeByAction("deploy"); + + // Configures the command to replace all the parameters with the values + const commandConfigured: string | undefined = await this.configureCommandWithParameters( + config, + args, + supportedMdTypes + ); + + // 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}`); + 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."); + } + } + + isSupportedMetadataType(action: string, metadataType: string) { + const filteredMdtTypeByAction = this.getSupportedMetadataTypeByAction(action); + return filteredMdtTypeByAction.some((mdtType: SupportedMetadataTypes) => mdtType.apiName === metadataType); + } } -export default DevToolsStandardCommands; \ No newline at end of file +export default DevToolsStandardCommands; diff --git a/src/devtools/containers.ts b/src/devtools/containers.ts index 0b26fda..6ec5bcb 100644 --- a/src/devtools/containers.ts +++ b/src/devtools/containers.ts @@ -7,51 +7,51 @@ import { editorWorkspace } from "../editor/workspace"; import { log } from "../editor/output"; enum StatusBarIcon { - success = "check-all", - retrieve = "cloud-download", - deploy = "cloud-upload", - error = "warning", - copy_to_folder = "file-symlink-directory", - info = "extensions-info-message" + success = "check-all", + retrieve = "cloud-download", + deploy = "cloud-upload", + error = "warning", + copy_to_folder = "file-symlink-directory", + info = "extensions-info-message" } // Contains all the status bars that are displayed in the extension let statusBarContainer: StatusBarItem | StatusBarItem[]; function activateStatusBar(/*isDevtoolsProject: boolean, commandPrefix: string*/): void { - log("debug", "Activating Status Bar Options..."); - const { subscriptions }: ExtensionContext = editorContext.get(); - - // Gets the command prefix for - let statusBarCommand: string | string[]; - - statusBarContainer = editorContainers.displayStatusBarItem([ - editorContainers.createStatusBarItem( - containersConfig.statusBarDevToolsCommand, - `$(${StatusBarIcon.success}) ${containersConfig.statusBarDevToolsTitle}`, - containersConfig.statusBarDevToolsName - ) - ]); - - statusBarCommand = [ - containersConfig.statusBarDevToolsCommand - ]; - - subscriptions.push(...[statusBarContainer].flat()); - - // Register the commands - [statusBarCommand].flat().forEach((command: string) => editorCommands.registerCommand({ - command, - callbackAction: () => { - const [ _, key ]: string[] = command.split(".devtools"); - return devtoolsMain.handleStatusBarActions(key); - } - })); - - // Check which status bar should be displayed - // if .mcdevrc.json AND .mcdev-auth.json in folder then mcdev:Credential/BU && mcdev:Command - // else mcdev: Initialize - /* + log("debug", "Activating Status Bar Options..."); + const { subscriptions }: ExtensionContext = editorContext.get(); + + // Gets the command prefix for + let statusBarCommand: string | string[]; + + statusBarContainer = editorContainers.displayStatusBarItem([ + editorContainers.createStatusBarItem( + containersConfig.statusBarDevToolsCommand, + `$(${StatusBarIcon.success}) ${containersConfig.statusBarDevToolsTitle}`, + containersConfig.statusBarDevToolsName + ) + ]); + + statusBarCommand = [containersConfig.statusBarDevToolsCommand]; + + subscriptions.push(...[statusBarContainer].flat()); + + // Register the commands + [statusBarCommand].flat().forEach((command: string) => + editorCommands.registerCommand({ + command, + callbackAction: () => { + const [_, key]: string[] = command.split(".devtools"); + return devtoolsMain.handleStatusBarActions(key); + } + }) + ); + + // Check which status bar should be displayed + // if .mcdevrc.json AND .mcdev-auth.json in folder then mcdev:Credential/BU && mcdev:Command + // else mcdev: Initialize + /* if(isDevtoolsProject){ // Status Bar mcdev: initialize must be removed if the user initialized devtools in a folder. @@ -118,20 +118,16 @@ 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?.toLowerCase() === `${statusBarId.toLowerCase()}` - ); - - if(statusBar){ - statusBar.text = `$(${StatusBarIcon[action]}) ${containersConfig.statusBarDevToolsTitle}`; - statusBar.backgroundColor = editorContainers.getBackgroundColor( - action === "error" - ? "error" : - "" - ); - } - } + if (statusBarContainer && Array.isArray(statusBarContainer)) { + const [statusBar] = statusBarContainer.filter( + (sb: StatusBarItem) => sb.name?.toLowerCase() === `${statusBarId.toLowerCase()}` + ); + + if (statusBar) { + statusBar.text = `$(${StatusBarIcon[action]}) ${containersConfig.statusBarDevToolsTitle}`; + statusBar.backgroundColor = editorContainers.getBackgroundColor(action === "error" ? "error" : ""); + } + } } // function modifyStatusBar(statusBarId: string, commandPrefix: string, statusBarText: string): void { @@ -146,56 +142,61 @@ function modifyStatusBar(statusBarId: string, action: keyof typeof StatusBarIcon // } function isCredentialBUSelected(): boolean { - return statusBarContainer && - Array.isArray(statusBarContainer) && - statusBarContainer.filter( - (sb: StatusBarItem) => - sb.name === containersConfig.statusBarDevToolsCredentialBUName && - !sb.text.includes(`${containersConfig.statusBarDevToolsCredentialBUTitle}`) - ).length > 0; + return ( + statusBarContainer && + Array.isArray(statusBarContainer) && + statusBarContainer.filter( + (sb: StatusBarItem) => + sb.name === containersConfig.statusBarDevToolsCredentialBUName && + !sb.text.includes(`${containersConfig.statusBarDevToolsCredentialBUTitle}`) + ).length > 0 + ); } function getCredentialsBUName(commandPrefix: string): string | undefined { - if(statusBarContainer && Array.isArray(statusBarContainer)){ - const [ { text } ] = statusBarContainer.filter( - (sb: StatusBarItem) => - sb.name === containersConfig.statusBarDevToolsCredentialBUName && - !sb.text.includes(`${containersConfig.statusBarDevToolsCredentialBUTitle}`) - ); - const [ _, credentialbu ] = text.split(`${commandPrefix}:`); - return credentialbu.trim(); - } - return; + if (statusBarContainer && Array.isArray(statusBarContainer)) { + const [{ text }] = statusBarContainer.filter( + (sb: StatusBarItem) => + sb.name === containersConfig.statusBarDevToolsCredentialBUName && + !sb.text.includes(`${containersConfig.statusBarDevToolsCredentialBUTitle}`) + ); + const [_, credentialbu] = text.split(`${commandPrefix}:`); + return credentialbu.trim(); + } + return; } -function activateContextMenuCommands(){ - [ - containersConfig.contextMenuRetrieveCommand, - containersConfig.contextMenuDeployCommand, - containersConfig.contextMenuCopyToBUCommand - ].forEach((command: string) => editorCommands.registerCommand({ - command, - callbackAction: (file: Uri, multipleFiles: Uri[]) => { - const files: Uri[] = !Array.isArray(multipleFiles) ? [file] : multipleFiles; - if(files.length){ - const filesPath: string[] = editorWorkspace.getFilesURIPath(files); - const [ __, key ]: string[] = command.split(".devtools"); - return devtoolsMain.handleContextMenuActions(key, filesPath); - }else{ - log("error", - "[container_activateContextMenuCommands] Error: Context Menu Callback didn't return any selected files." - ); - } - } - })); +function activateContextMenuCommands() { + [ + containersConfig.contextMenuRetrieveCommand, + containersConfig.contextMenuDeployCommand, + containersConfig.contextMenuCopyToBUCommand + ].forEach((command: string) => + editorCommands.registerCommand({ + command, + callbackAction: (file: Uri, multipleFiles: Uri[]) => { + const files: Uri[] = !Array.isArray(multipleFiles) ? [file] : multipleFiles; + if (files.length) { + const filesPath: string[] = editorWorkspace.getFilesURIPath(files); + const [__, key]: string[] = command.split(".devtools"); + return devtoolsMain.handleContextMenuActions(key, filesPath); + } else { + log( + "error", + "[container_activateContextMenuCommands] Error: Context Menu Callback didn't return any selected files." + ); + } + } + }) + ); } const devtoolsContainers = { - activateStatusBar, - modifyStatusBar, - isCredentialBUSelected, - getCredentialsBUName, - activateContextMenuCommands + activateStatusBar, + modifyStatusBar, + isCredentialBUSelected, + getCredentialsBUName, + activateContextMenuCommands }; -export { StatusBarIcon, devtoolsContainers }; \ No newline at end of file +export { StatusBarIcon, devtoolsContainers }; diff --git a/src/devtools/installer.ts b/src/devtools/installer.ts index 1a3c183..ed86e2c 100644 --- a/src/devtools/installer.ts +++ b/src/devtools/installer.ts @@ -6,89 +6,84 @@ import { terminal } from "../shared/utils/terminal"; import { lib } from "../shared/utils/lib"; async function isDevToolsInstalled(): Promise { - return new Promise(resolve => { - terminal.executeTerminalCommand({ - command: installerConfig.package.mcdev.version, - args: [], - cwd: editorWorkspace.getWorkspaceURIPath(), - handleResult(error: string | null, output: string | null, code: number | null) { - if(code !== null){ - log("debug", `[installer_isDevToolsInstalled] Exit Code: '${code}'`); - } - if(output){ - log("debug", `[installer_isDevToolsInstalled] Output: '${output}'`); - resolve(output.length > 0); - } - if(error){ - log("error", `[installer_isDevToolsInstalled] Error: '${error}'`); - resolve(false); - } - }, - }); - }); + return new Promise(resolve => { + terminal.executeTerminalCommand({ + command: installerConfig.package.mcdev.version, + args: [], + cwd: editorWorkspace.getWorkspaceURIPath(), + handleResult(error: string | null, output: string | null, code: number | null) { + if (code !== null) { + log("debug", `[installer_isDevToolsInstalled] Exit Code: '${code}'`); + } + if (output) { + log("debug", `[installer_isDevToolsInstalled] Output: '${output}'`); + resolve(output.length > 0); + } + if (error) { + log("error", `[installer_isDevToolsInstalled] Error: '${error}'`); + resolve(false); + } + } + }); + }); } -async function installDevTools(): Promise{ - try{ - log("info", "Installing SFMC DevTools..."); - await editorInput.handleInProgressMessage( - "Notification", - (progress) => { - progress.report({message: installerConfig.messages.installingDevToolsProgress}); - return new Promise(resolve => { - terminal.executeTerminalCommand({ - command: installerConfig.package.mcdev.install, - args: [], - cwd: editorWorkspace.getWorkspaceURIPath(), - handleResult: (error: string | null, output: string | null, code: number | null) => { - if(output){ - log("info", output); - } - if(error){ - log("error", `[installer_installDevTools] Exit Code ${error}`); - } - if(code !== null){ - log("debug", `[installer_installDevTools] Exit Code ${code}`); - resolve(); - } - }, - }); - }); - } - ); - log("info", "Reloading VSCode workspace window..."); - lib.waitTime(5000, () => { - // Reloads the workspace after DevTools installation - editorWorkspace.reloadWorkspace(); - }); - }catch(error){ - log("warning", "Something went wrong! SFMC DevTools installation failed."); - log("error", `[installer_installDevTools] Failed to install DevTools: ${error}`); - } +async function installDevTools(): Promise { + try { + log("info", "Installing SFMC DevTools..."); + await editorInput.handleInProgressMessage("Notification", progress => { + progress.report({ message: installerConfig.messages.installingDevToolsProgress }); + return new Promise(resolve => { + terminal.executeTerminalCommand({ + command: installerConfig.package.mcdev.install, + args: [], + cwd: editorWorkspace.getWorkspaceURIPath(), + handleResult: (error: string | null, output: string | null, code: number | null) => { + if (output) { + log("info", output); + } + if (error) { + log("error", `[installer_installDevTools] Exit Code ${error}`); + } + if (code !== null) { + log("debug", `[installer_installDevTools] Exit Code ${code}`); + resolve(); + } + } + }); + }); + }); + log("info", "Reloading VSCode workspace window..."); + lib.waitTime(5000, () => { + // Reloads the workspace after DevTools installation + editorWorkspace.reloadWorkspace(); + }); + } catch (error) { + log("warning", "Something went wrong! SFMC DevTools installation failed."); + log("error", `[installer_installDevTools] Failed to install DevTools: ${error}`); + } } -async function noDevToolsHandler(){ +async function noDevToolsHandler() { + const message: string = `${installerConfig.messages.noDevToolsInstalled} ${installerConfig.messages.askUserToInstallDevTools}`; - const message: string = `${installerConfig.messages.noDevToolsInstalled} ${installerConfig.messages.askUserToInstallDevTools}`; + log("warning", installerConfig.messages.noDevToolsInstalled); - log("warning", installerConfig.messages.noDevToolsInstalled); + // Asks if user wishes to install DevTools + const userResponse: string | undefined = await editorInput.handleShowOptionsMessage( + message, + Object.keys(InstallDevToolsResponseOptions).filter(v => isNaN(Number(v))) + ); - // Asks if user wishes to install DevTools - const userResponse: string | undefined = await editorInput.handleShowOptionsMessage( - message, - Object.keys(InstallDevToolsResponseOptions).filter((v) => isNaN(Number(v))) - ); + log("debug", `[installer_noDevToolsHandler] User Response = ${userResponse}`); - log("debug", `[installer_noDevToolsHandler] User Response = ${userResponse}`); - - if(userResponse && - InstallDevToolsResponseOptions[userResponse as keyof typeof InstallDevToolsResponseOptions]){ - installDevTools(); - } + if (userResponse && InstallDevToolsResponseOptions[userResponse as keyof typeof InstallDevToolsResponseOptions]) { + installDevTools(); + } } export const devtoolsInstaller = { - isDevToolsInstalled, - installDevTools, - noDevToolsHandler -}; \ No newline at end of file + isDevToolsInstalled, + installDevTools, + noDevToolsHandler +}; diff --git a/src/devtools/main.ts b/src/devtools/main.ts index 345d8f7..9279cf2 100644 --- a/src/devtools/main.ts +++ b/src/devtools/main.ts @@ -16,745 +16,761 @@ import { lib } from "../shared/utils/lib"; import { file } from "../shared/utils/file"; import { editorCommands } from "../editor/commands"; - -async function initDevToolsExtension(): Promise{ - - try{ - log("info", "Running SFMC DevTools extension..."); - - const anyDevToolsProject: boolean = await isDevToolsProject() || await anySubFolderIsDevToolsProject(); - - if(anyDevToolsProject){ - await handleDevToolsRequirements(); - activateDependencies(); - activateContainers(); - } - }catch(error){ - log("error", `[main_initDevToolsExtension] Error: ${error}`); - } +async function initDevToolsExtension(): Promise { + try { + log("info", "Running SFMC DevTools extension..."); + + const anyDevToolsProject: boolean = (await isDevToolsProject()) || (await anySubFolderIsDevToolsProject()); + + if (anyDevToolsProject) { + await handleDevToolsRequirements(); + activateDependencies(); + activateContainers(); + } + } catch (error) { + log("error", `[main_initDevToolsExtension] Error: ${error}`); + } } async function isDevToolsProject(projectName?: string): Promise { - 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("debug", - `Folder ${findMcdevFiles.every((result: boolean) => result === true) ? 'is' : 'is not'} a SFMC DevTools project.` - ); - return findMcdevFiles.every((result: boolean) => result === true); + 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( + "debug", + `Folder ${findMcdevFiles.every((result: boolean) => result === true) ? "is" : "is not"} a SFMC DevTools project.` + ); + return findMcdevFiles.every((result: boolean) => result === true); } -async function handleDevToolsRequirements(/*isDevToolsProject: boolean*/): Promise{ - log("info", "Checking SFMC DevTools requirements..."); - const prerequisites: PrerequisitesInstalledReturn = await devtoolsPrerequisites.arePrerequisitesInstalled(); - log("info", `SFMC Pre-Requisites ${ - prerequisites.prerequisitesInstalled ? 'are' : 'are not' - } installed.`); - if(prerequisites.prerequisitesInstalled){ - const isDevToolsInstalled: boolean = await devtoolsInstaller.isDevToolsInstalled(); - if(!isDevToolsInstalled){ - await devtoolsInstaller.noDevToolsHandler(); - return; - } - log("info", "SFMC DevTools is installed."); - - // Deactivates Compact folders for command right execution - editorDependencies.deactivateCompactFolders(); - // init DevTools Commands - DevToolsCommands.init(); - return; - } - log("debug", `Missing Pre-requisites: [${prerequisites.missingPrerequisites}]`); - devtoolsPrerequisites.noPrerequisitesHandler( - editorContext.get().extensionPath, - prerequisites.missingPrerequisites - ); +async function handleDevToolsRequirements(/*isDevToolsProject: boolean*/): Promise { + log("info", "Checking SFMC DevTools requirements..."); + const prerequisites: PrerequisitesInstalledReturn = await devtoolsPrerequisites.arePrerequisitesInstalled(); + log("info", `SFMC Pre-Requisites ${prerequisites.prerequisitesInstalled ? "are" : "are not"} installed.`); + if (prerequisites.prerequisitesInstalled) { + const isDevToolsInstalled: boolean = await devtoolsInstaller.isDevToolsInstalled(); + if (!isDevToolsInstalled) { + await devtoolsInstaller.noDevToolsHandler(); + return; + } + log("info", "SFMC DevTools is installed."); + + // Deactivates Compact folders for command right execution + editorDependencies.deactivateCompactFolders(); + // init DevTools Commands + DevToolsCommands.init(); + return; + } + log("debug", `Missing Pre-requisites: [${prerequisites.missingPrerequisites}]`); + devtoolsPrerequisites.noPrerequisitesHandler(editorContext.get().extensionPath, prerequisites.missingPrerequisites); } async function anySubFolderIsDevToolsProject(): Promise { - const subFolders: string[] = await editorWorkspace.getWorkspaceSubFolders(); - if(subFolders.length){ - const subFolderProjects: boolean[] = - await Promise.all(subFolders.map(async (sf: string) => await isDevToolsProject(sf + "/"))); - return subFolderProjects.some((sfResult: boolean) => sfResult); - }else{ - log("debug", "Workspace doesn't contain any sub folders."); - } - return false; + const subFolders: string[] = await editorWorkspace.getWorkspaceSubFolders(); + if (subFolders.length) { + const subFolderProjects: boolean[] = await Promise.all( + subFolders.map(async (sf: string) => await isDevToolsProject(sf + "/")) + ); + return subFolderProjects.some((sfResult: boolean) => sfResult); + } else { + log("debug", "Workspace doesn't contain any sub folders."); + } + return false; } -function activateDependencies(){ - editorDependencies.activateExtensionDependencies(mainConfig.extensionsDependencies); - editorCommands.setCommandContext("sfmc-devtools-vscode.isDevToolsProject", true); +function activateDependencies() { + editorDependencies.activateExtensionDependencies(mainConfig.extensionsDependencies); + editorCommands.setCommandContext("sfmc-devtools-vscode.isDevToolsProject", true); } -function activateContainers(){ - // activate the status bar - devtoolsContainers.activateStatusBar(); +function activateContainers() { + // activate the status bar + devtoolsContainers.activateStatusBar(); - // activate the context menus options - devtoolsContainers.activateContextMenuCommands(); + // activate the context menus options + devtoolsContainers.activateContextMenuCommands(); } function handleStatusBarActions(action: string): void { - log("debug", "Setting Status Bar Actions..."); - log("debug", `Action: ${action}`); - switch(action.toLowerCase()){ - case "sbcredentialbu": - changeCredentialsBU(); - break; - case "sbcommand": - handleDevToolsSBCommand(); - break; - case "sbinitialize": - initialize(); - break; - case "sbmcdev": - handleMCDevSBCommand(); - break; - default: - log("error", `main_handleStatusBarActions: Invalid Status Bar Action '${action}'`); - } + log("debug", "Setting Status Bar Actions..."); + log("debug", `Action: ${action}`); + switch (action.toLowerCase()) { + case "sbcredentialbu": + changeCredentialsBU(); + break; + case "sbcommand": + handleDevToolsSBCommand(); + break; + case "sbinitialize": + initialize(); + break; + case "sbmcdev": + handleMCDevSBCommand(); + break; + default: + log("error", `main_handleStatusBarActions: Invalid Status Bar Action '${action}'`); + } } function handleContextMenuActions(action: string, selectedFiles: string[]): void { - - devtoolsContainers.modifyStatusBar( "mcdev", "success"); - - log("debug", "Setting Context Menu Actions..."); - log("debug", `Action: ${action} Number of Selected Files: ${selectedFiles.length}`); - switch(action.toLowerCase()){ - case "cmretrieve": - handleDevToolsCMCommand("retrieve", selectedFiles); - break; - case "cmdeploy": - handleDevToolsCMCommand("deploy", selectedFiles); - break; - case "cmcopytobu": - handleCopyToBuCMCommand(selectedFiles); - break; - default: - log("error", `main_handleContextMenuActions: Invalid Context Menu Action '${action}'`); - } + devtoolsContainers.modifyStatusBar("mcdev", "success"); + + log("debug", "Setting Context Menu Actions..."); + log("debug", `Action: ${action} Number of Selected Files: ${selectedFiles.length}`); + switch (action.toLowerCase()) { + case "cmretrieve": + handleDevToolsCMCommand("retrieve", selectedFiles); + break; + case "cmdeploy": + handleDevToolsCMCommand("deploy", selectedFiles); + break; + case "cmcopytobu": + handleCopyToBuCMCommand(selectedFiles); + break; + default: + log("error", `main_handleContextMenuActions: Invalid Context Menu Action '${action}'`); + } } -async function getCredentialsBU(): Promise<{[key: string]: string[] } | undefined >{ - try{ - // gets the project workspace uri path - const folderPath: string = editorWorkspace.getWorkspaceURIPath(); - - // retrieves all the content inside the file that contains the mcdev credentials - const credBUContent: string = - await editorWorkspace.readFile(`${folderPath}/${mainConfig.credentialsFilename}`); - - // parses the content from text to JSON - const parsedCredBUContent: any = JSON.parse(credBUContent); - - // return a json with each credential associated with a list of its business units - if(parsedCredBUContent && "credentials" in parsedCredBUContent){ - return Object.keys(parsedCredBUContent.credentials) - .reduce((prev: {}, credential: string) => { - const { businessUnits } = parsedCredBUContent.credentials[credential]; - if(businessUnits && Object.keys(businessUnits).length){ - return { ...prev, [credential]: Object.keys(businessUnits) }; - }else{ - log("error", `Could not find any business units for the credential '${credential}'`); - return {...prev }; - } - }, {}); - } - log("error", - `[main_getCredentialsBU] Error: Could not find any credentials in the '${mainConfig.credentialsFilename}' file.` - ); - }catch(error){ - log("error", `[main_getCredentialsBU] Error: ${error}`); - } - return; +async function getCredentialsBU(): Promise<{ [key: string]: string[] } | undefined> { + try { + // gets the project workspace uri path + const folderPath: string = editorWorkspace.getWorkspaceURIPath(); + + // retrieves all the content inside the file that contains the mcdev credentials + const credBUContent: string = await editorWorkspace.readFile(`${folderPath}/${mainConfig.credentialsFilename}`); + + // parses the content from text to JSON + const parsedCredBUContent: any = JSON.parse(credBUContent); + + // return a json with each credential associated with a list of its business units + if (parsedCredBUContent && "credentials" in parsedCredBUContent) { + return Object.keys(parsedCredBUContent.credentials).reduce((prev: {}, credential: string) => { + const { businessUnits } = parsedCredBUContent.credentials[credential]; + if (businessUnits && Object.keys(businessUnits).length) { + return { ...prev, [credential]: Object.keys(businessUnits) }; + } else { + log("error", `Could not find any business units for the credential '${credential}'`); + return { ...prev }; + } + }, {}); + } + log( + "error", + `[main_getCredentialsBU] Error: Could not find any credentials in the '${mainConfig.credentialsFilename}' file.` + ); + } catch (error) { + log("error", `[main_getCredentialsBU] Error: ${error}`); + } + return; } -async function changeCredentialsBU(): Promise{ - log("info", "Changing SFMC DevTools credententials/bu..."); - const credentialsBUList: {[key: string]: string[]} | undefined = - await getCredentialsBU(); - - if(credentialsBUList){ - // Configures all placeholder as an selectable option - const allPlaceholderOption: InputOptionsSettings = { - id: mainConfig.allPlaceholder.toLowerCase(), - label: mainConfig.allPlaceholder, - detail: "" - }; - // Configures all credential names as selectable options - const credentialsOptions: InputOptionsSettings[] = Object.keys(credentialsBUList) - .map((credential: string) => ({ - id: credential.toLowerCase(), - label: credential, - detail: "" - })); - - // Requests user to select one credential option - const selectedCredential: InputOptionsSettings | InputOptionsSettings[] | undefined = - await editorInput.handleQuickPickSelection( - [allPlaceholderOption, ...credentialsOptions], - mainConfig.messages.selectCredential, - false - ); - - if(selectedCredential && !Array.isArray(selectedCredential)){ - log("debug", `User selected '${selectedCredential.label}' credential.`); - if(selectedCredential.id === mainConfig.allPlaceholder.toLowerCase()){ - // if user selects *All* then status bar should be replaced with it - // devtoolsContainers.modifyStatusBar( - // "credentialbu", - // DevToolsCommands.commandPrefix, - // selectedCredential.label - // ); - }else{ - const businessUnitsList: string[] = credentialsBUList[selectedCredential.label]; - - // Configures all business units names as selectable options - const businessUnitOptions: InputOptionsSettings[] = businessUnitsList - .map((businessUnit: string) => ({ - id: businessUnit.toLowerCase(), - label: businessUnit, - detail: "" - })); - - // Requests user to select all or one Business Unit - const selectedBU: InputOptionsSettings | InputOptionsSettings[] | undefined = - await editorInput.handleQuickPickSelection( - [allPlaceholderOption, ...businessUnitOptions], - mainConfig.messages.selectBusinessUnit, - false - ); - - if(selectedBU && !Array.isArray(selectedBU)){ - log("debug", `User selected '${selectedBU.label}' business unit.`); - - // Modify the credential status bar icon to contain the - // selected Credential + selected Business Unit - // devtoolsContainers.modifyStatusBar( - // "credentialbu", - // DevToolsCommands.commandPrefix, - // `${selectedCredential.label}/${selectedBU.label}` - // ); - } - } - } - }else{ - log("error", "[main_changeCredentialsBU] Error: CredentialBU List is undefined."); - } +async function changeCredentialsBU(): Promise { + log("info", "Changing SFMC DevTools credententials/bu..."); + const credentialsBUList: { [key: string]: string[] } | undefined = await getCredentialsBU(); + + if (credentialsBUList) { + // Configures all placeholder as an selectable option + const allPlaceholderOption: InputOptionsSettings = { + id: mainConfig.allPlaceholder.toLowerCase(), + label: mainConfig.allPlaceholder, + detail: "" + }; + // Configures all credential names as selectable options + const credentialsOptions: InputOptionsSettings[] = Object.keys(credentialsBUList).map((credential: string) => ({ + id: credential.toLowerCase(), + label: credential, + detail: "" + })); + + // Requests user to select one credential option + const selectedCredential: InputOptionsSettings | InputOptionsSettings[] | undefined = + await editorInput.handleQuickPickSelection( + [allPlaceholderOption, ...credentialsOptions], + mainConfig.messages.selectCredential, + false + ); + + if (selectedCredential && !Array.isArray(selectedCredential)) { + log("debug", `User selected '${selectedCredential.label}' credential.`); + if (selectedCredential.id === mainConfig.allPlaceholder.toLowerCase()) { + // if user selects *All* then status bar should be replaced with it + // devtoolsContainers.modifyStatusBar( + // "credentialbu", + // DevToolsCommands.commandPrefix, + // selectedCredential.label + // ); + } else { + const businessUnitsList: string[] = credentialsBUList[selectedCredential.label]; + + // Configures all business units names as selectable options + const businessUnitOptions: InputOptionsSettings[] = businessUnitsList.map((businessUnit: string) => ({ + id: businessUnit.toLowerCase(), + label: businessUnit, + detail: "" + })); + + // Requests user to select all or one Business Unit + const selectedBU: InputOptionsSettings | InputOptionsSettings[] | undefined = + await editorInput.handleQuickPickSelection( + [allPlaceholderOption, ...businessUnitOptions], + mainConfig.messages.selectBusinessUnit, + false + ); + + if (selectedBU && !Array.isArray(selectedBU)) { + log("debug", `User selected '${selectedBU.label}' business unit.`); + + // Modify the credential status bar icon to contain the + // selected Credential + selected Business Unit + // devtoolsContainers.modifyStatusBar( + // "credentialbu", + // DevToolsCommands.commandPrefix, + // `${selectedCredential.label}/${selectedBU.label}` + // ); + } + } + } + } else { + log("error", "[main_changeCredentialsBU] Error: CredentialBU List is undefined."); + } } -async function handleDevToolsSBCommand(): Promise{ - log("debug", "Selecting SB SFMC DevTools command..."); - const devToolsCommandTypes: {id: string, title: string}[] = DevToolsCommands.getAllCommandTypes(); - - if(devToolsCommandTypes){ - // Configures all commandTypes names as selectable options - const commandTypesOptions: InputOptionsSettings[] = devToolsCommandTypes - .map(({ id, title }: {id: string, title: string}) => ({ - id: id.toLowerCase(), - label: title, - detail: "" - })); - - // Requests user to select one DevTools Command Type - const selectedCommandType: InputOptionsSettings | InputOptionsSettings[] | undefined = - await editorInput.handleQuickPickSelection( - commandTypesOptions, - mainConfig.messages.selectCommandType, - false - ); - - if(selectedCommandType && !Array.isArray(selectedCommandType)){ - log("debug", `User selected in ${selectedCommandType.label} DevTools Command type.`); - const commands: DevToolsCommandSetting[] = - DevToolsCommands.getCommandsListByType(selectedCommandType.id); - - // Configures all devtools commands as selectable options - const commandsOptions: InputOptionsSettings[] = commands - .map((command: DevToolsCommandSetting) => ({ - id: command.id.toLowerCase(), - label: command.title, - detail: command.description - })); - // Requests user to select one DevTools Command Type - const selectedCommandOption: InputOptionsSettings | InputOptionsSettings[] | undefined = - await editorInput.handleQuickPickSelection( - commandsOptions, - mainConfig.messages.selectCommand, - false - ); - - if(selectedCommandOption && !Array.isArray(selectedCommandOption)){ - log("debug", `User selected in ${selectedCommandOption.label} DevTools Command.`); - if(devtoolsContainers.isCredentialBUSelected()){ - log("info", "Credential/BU is selected..."); - const selectedCredentialBU: string | undefined = - devtoolsContainers.getCredentialsBUName(DevToolsCommands.commandPrefix); - if(selectedCredentialBU){ - // execute DevTools Command - DevToolsCommands.runCommand( - selectedCommandType.id, - selectedCommandOption.id, - editorWorkspace.getWorkspaceURIPath(), - { bu: selectedCredentialBU.replace(mainConfig.allPlaceholder, "'*'") }, - { handleCommandResult: (result: any) => log("info", result) } - ); - }else{ - log("error", - `[main_handleDevToolsCommandSelection] Error: Failed to retrieve Credential/BU.` - ); - } - }else{ - if(DevToolsCommands.requiresCredentials(selectedCommandType.id)){ - log("debug", - `Crendentials are required to be selected first for type '${selectedCommandType.id}'` - ); - editorInput.handleShowNotificationMessage("warning", - `${mainConfig.messages.selectedCredentialsBU} '${ - lib.capitalizeFirstLetter(selectedCommandOption.id) - }'...`, - [] - ); - lib.waitTime(1000, () => changeCredentialsBU()); - }else{ - // execute DevTools Command - DevToolsCommands.runCommand( - selectedCommandType.id, - selectedCommandOption.id, - editorWorkspace.getWorkspaceURIPath(), - {}, - { handleCommandResult: (result: any) => log("info", result) } - ); - } - } - } - } - } +async function handleDevToolsSBCommand(): Promise { + log("debug", "Selecting SB SFMC DevTools command..."); + const devToolsCommandTypes: { id: string; title: string }[] = DevToolsCommands.getAllCommandTypes(); + + if (devToolsCommandTypes) { + // Configures all commandTypes names as selectable options + const commandTypesOptions: InputOptionsSettings[] = devToolsCommandTypes.map( + ({ id, title }: { id: string; title: string }) => ({ + id: id.toLowerCase(), + label: title, + detail: "" + }) + ); + + // Requests user to select one DevTools Command Type + const selectedCommandType: InputOptionsSettings | InputOptionsSettings[] | undefined = + await editorInput.handleQuickPickSelection( + commandTypesOptions, + mainConfig.messages.selectCommandType, + false + ); + + if (selectedCommandType && !Array.isArray(selectedCommandType)) { + log("debug", `User selected in ${selectedCommandType.label} DevTools Command type.`); + const commands: DevToolsCommandSetting[] = DevToolsCommands.getCommandsListByType(selectedCommandType.id); + + // Configures all devtools commands as selectable options + const commandsOptions: InputOptionsSettings[] = commands.map((command: DevToolsCommandSetting) => ({ + id: command.id.toLowerCase(), + label: command.title, + detail: command.description + })); + // Requests user to select one DevTools Command Type + const selectedCommandOption: InputOptionsSettings | InputOptionsSettings[] | undefined = + await editorInput.handleQuickPickSelection(commandsOptions, mainConfig.messages.selectCommand, false); + + if (selectedCommandOption && !Array.isArray(selectedCommandOption)) { + log("debug", `User selected in ${selectedCommandOption.label} DevTools Command.`); + if (devtoolsContainers.isCredentialBUSelected()) { + log("info", "Credential/BU is selected..."); + const selectedCredentialBU: string | undefined = devtoolsContainers.getCredentialsBUName( + DevToolsCommands.commandPrefix + ); + if (selectedCredentialBU) { + // execute DevTools Command + DevToolsCommands.runCommand( + selectedCommandType.id, + selectedCommandOption.id, + editorWorkspace.getWorkspaceURIPath(), + { bu: selectedCredentialBU.replace(mainConfig.allPlaceholder, "'*'") }, + { handleCommandResult: (result: any) => log("info", result) } + ); + } else { + log("error", `[main_handleDevToolsCommandSelection] Error: Failed to retrieve Credential/BU.`); + } + } else { + if (DevToolsCommands.requiresCredentials(selectedCommandType.id)) { + log( + "debug", + `Crendentials are required to be selected first for type '${selectedCommandType.id}'` + ); + editorInput.handleShowNotificationMessage( + "warning", + `${mainConfig.messages.selectedCredentialsBU} '${lib.capitalizeFirstLetter( + selectedCommandOption.id + )}'...`, + [] + ); + lib.waitTime(1000, () => changeCredentialsBU()); + } else { + // execute DevTools Command + DevToolsCommands.runCommand( + selectedCommandType.id, + selectedCommandOption.id, + editorWorkspace.getWorkspaceURIPath(), + {}, + { handleCommandResult: (result: any) => log("info", result) } + ); + } + } + } + } + } } -async function initialize(): Promise{ - await handleDevToolsRequirements(); - - const userResponse: string | undefined = await editorInput.handleShowOptionsMessage( - mainConfig.messages.initDevTools, - Object.keys(InstallDevToolsResponseOptions).filter((v) => isNaN(Number(v))) - ); - - if(userResponse && - InstallDevToolsResponseOptions[userResponse as keyof typeof InstallDevToolsResponseOptions]){ - log("info", "Initializing SFMC DevTools project..."); - DevToolsCommands.runCommand( - null, - "init", - editorWorkspace.getWorkspaceURIPath(), - {}, - { - handleCommandResult: () => { - log("info", "Reloading VSCode workspace window..."); - lib.waitTime(5000, () => editorWorkspace.reloadWorkspace()); - } - } - ); - } +async function initialize(): Promise { + await handleDevToolsRequirements(); + + const userResponse: string | undefined = await editorInput.handleShowOptionsMessage( + mainConfig.messages.initDevTools, + Object.keys(InstallDevToolsResponseOptions).filter(v => isNaN(Number(v))) + ); + + if (userResponse && InstallDevToolsResponseOptions[userResponse as keyof typeof InstallDevToolsResponseOptions]) { + log("info", "Initializing SFMC DevTools project..."); + DevToolsCommands.runCommand( + null, + "init", + editorWorkspace.getWorkspaceURIPath(), + {}, + { + handleCommandResult: () => { + log("info", "Reloading VSCode workspace window..."); + lib.waitTime(5000, () => editorWorkspace.reloadWorkspace()); + } + } + ); + } } -function handleMCDevSBCommand(){ - editorOutput.showOuputChannel(); +function handleMCDevSBCommand() { + editorOutput.showOuputChannel(); } function getMCDevRelativePathComponents(relativePath: string): DevToolsPathComponents { - const [ - credentialName, - businessUnit, - metadataType, - ...keys - ]: string[] = relativePath.split("/"); - return { credentialName, businessUnit, metadataType, keys }; + const [credentialName, businessUnit, metadataType, ...keys]: string[] = relativePath.split("/"); + return { credentialName, businessUnit, metadataType, keys }; } -function logUnsupportedMtdtTypeNotification(action: string, unsupportedMtdtTypes: string | string[]){ - [unsupportedMtdtTypes] - .flat() - .forEach((metadataType: string) => { - log( - "error", - `Error: SFMC DevTools currently does not support ${action} for the metadata type: '${metadataType}'` - ); - }); - devtoolsContainers.modifyStatusBar("mcdev", "error"); - editorInput.handleShowNotificationMessage("error", mainConfig.messages.unsupportedMetadataType, []); -}; +function logUnsupportedMtdtTypeNotification(action: string, unsupportedMtdtTypes: string | string[]) { + [unsupportedMtdtTypes].flat().forEach((metadataType: string) => { + log( + "error", + `Error: SFMC DevTools currently does not support ${action} for the metadata type: '${metadataType}'` + ); + }); + devtoolsContainers.modifyStatusBar("mcdev", "error"); + editorInput.handleShowNotificationMessage("error", mainConfig.messages.unsupportedMetadataType, []); +} -async function handleDevToolsCMCommand(action: string, selectedPaths: string[]): Promise{ - log("debug", "Selecting CM SFMC DevTools command..."); - try{ - type ArgsConfig = { bu: string, mdtypes: string | string[], key: string | string[], fromRetrieve: boolean}; - type ProjectConfig = { path: string, args: ArgsConfig[] }; - let filesType: string[] = [], folderType: string[] = []; - - // Separates files and folders into different arrays - for(const path of selectedPaths){ - await editorWorkspace.isFile(path) ? - filesType.push(path) : - folderType.push(path); - } - - // Removes duplicate files (eg. some files have the same name with md and json) - if(filesType.length){ - filesType = lib.removeDuplicates( - lib.removeExtensionFromFile(filesType, mainConfig.fileExtensions) - ) as string[]; - } - - const configureArgsProject = async (action: string, selectedPaths: string[]): Promise<{[key: string]: ProjectConfig}> => { - - const projectArgsMap: {[key: string]: ProjectConfig} = {}; - - // gets workspace directory - const workspaceFolderPath: string = editorWorkspace.getWorkspaceURIPath(); - - for(const filePath of selectedPaths){ - - let projectName: string = ""; - let [ projectPath, cmPath ]: string[] = []; - let args: ArgsConfig[] = []; - let fromRetrieve: boolean = false; - - if(filePath.includes(action)){ - // Action Retrieve or Deploy were triggered from their folder - [ projectPath, cmPath ] = filePath.split(`/${action}`); - }else{ - if(action === "deploy"){ - log("debug", "Context Menu Command Deploy From Retrieve folder..."); - // Action Deploy from Retrieve was triggered (fromRetrieve) - [ projectPath, cmPath ] = filePath.split(`/retrieve`); - fromRetrieve = true; - }else{ - // error - } - } - - // Gets the project folder name - projectName = lib.getProjectNameFromPath(projectPath); - - log("debug", `Current workspace folder path: ${workspaceFolderPath}`); - log("debug", `Project Name: ${projectName}`); - log("debug", `Project path: ${projectPath}`); - log("debug", `Context Menu path: ${cmPath}`); - - 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 isDevToolsProject( projectName + "/" ); - log("debug", - `SubFolder project '${projectPath}' ${ isSubFolderDevToolsProject ? 'is': 'is not'} a DevTools Project.` - ); - if(!isSubFolderDevToolsProject){ - editorInput.handleShowNotificationMessage("error",`Folder '${projectName}' is not a SFMC DevTools Project.`, []); - return {}; - } - } - - // Checks if the project name is already in the map - if(!(projectName in projectArgsMap)){ - projectArgsMap[projectName] = { - path: projectPath, - args: [] - }; - } - - args = projectArgsMap[projectName].args; - - // When user only clicks on retrieve or deploy folder - if(projectPath && !cmPath){ - let filteredByBU: ArgsConfig[] = - args.filter(({ bu }: ArgsConfig) => bu !== undefined && bu === `"*"`); - if(!filteredByBU.length){ - args = [...args, { bu: `"*"`, mdtypes: [], key: [], fromRetrieve}]; - } - log("debug", `Updated project path for '${action} "*"': ${projectPath}.`); - } - - // When user clicks inside a retrieve or deploy folder - if(cmPath){ - let { credentialName, businessUnit, metadataType, keys } = getMCDevRelativePathComponents(cmPath.substring(1)); - let key: string = ""; - - if(metadataType && !DevToolsCommands.isSupportedMetadataType(action, metadataType)){ - logUnsupportedMtdtTypeNotification(action, metadataType); - continue; - } - - // If user selected to retrieve/deploy a subfolder/file inside metadata type asset folder - if(metadataType === "asset" && keys.length){ - // Gets the asset subfolder and asset key - const [ assetFolder, assetKey ] = keys; - if(!assetKey){ - // if user only selected an asset subfolder - // type will be changed to "asset-[name of the asset subfolder]" - metadataType = `${metadataType}-${assetFolder}`; - } - // if user selects a file inside a subfolder of asset - // the key will be the name of the file - keys = assetKey ? [ assetKey ] : []; - }else if(metadataType === "folder" && keys.length){ - // Nested folders are not supported as keys for the metadata type folder - keys = []; - } - - key = keys.length ? keys[0] : ""; - - let filteredByBU: ArgsConfig[] = - args.filter(({ bu }: ArgsConfig) => bu !== undefined && bu === `${credentialName}/${businessUnit ? businessUnit : '*'}`); - - if(filteredByBU.length){ - let newArgs: ArgsConfig = { - bu: filteredByBU[0].bu, - mdtypes: lib.removeNonValues( - (lib.removeDuplicates([...filteredByBU[0]['mdtypes'], metadataType]) as string[]) - ) as string[], - key: lib.removeNonValues( - (lib.removeDuplicates([...filteredByBU[0]['key'], key]) as string[]) - ) as string[], - fromRetrieve: filteredByBU[0].fromRetrieve - }; - args = [ - ...args.filter(({ bu }: ArgsConfig) => bu !== `${credentialName}/${businessUnit ? businessUnit : '*'}`), - newArgs - ]; - }else{ - args = [ - ...args, - { - bu: `${credentialName}/${businessUnit ? businessUnit : '*'}`, - mdtypes: lib.removeNonValues([metadataType]) as string[], - key: lib.removeNonValues([key]) as string[], - fromRetrieve - } - ]; - } - } - projectArgsMap[projectName].args = args; - } - return projectArgsMap; - }; - - for(const optionType of [filesType, folderType]){ - if(optionType.length){ - 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}`); - let { path, args }: ProjectConfig = projectMap[projName]; - args = args.map((arg: ArgsConfig) => ({ - ...arg, - mdtypes: arg.mdtypes.length - ? `"${(arg.mdtypes as string[]).join(",")}"` - : "", - key: arg.key.length ? `"${(arg.key as string[]).join(",")}"`: "" - })); - - for(const dtArgs of args){ - log("debug", `Action: ${action} Args: ${JSON.stringify(dtArgs)}`); - devtoolsContainers.modifyStatusBar( - "mcdev", - action.toLowerCase() as keyof typeof StatusBarIcon - ); - await editorInput.handleInProgressMessage( - "Notification", - (progress) => { - return new Promise(resolve => DevToolsCommands.runCommand( - null, - action, - path, - dtArgs, - { - 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(); - } - } - } - )); - } - ); - } - })); - } - } - }catch(error){ - log("error", `[main_handleDevToolsCMCommand] Error: ${error}`); - } +async function handleDevToolsCMCommand(action: string, selectedPaths: string[]): Promise { + log("debug", "Selecting CM SFMC DevTools command..."); + try { + type ArgsConfig = { bu: string; mdtypes: string | string[]; key: string | string[]; fromRetrieve: boolean }; + type ProjectConfig = { path: string; args: ArgsConfig[] }; + let filesType: string[] = [], + folderType: string[] = []; + + // Separates files and folders into different arrays + for (const path of selectedPaths) { + (await editorWorkspace.isFile(path)) ? filesType.push(path) : folderType.push(path); + } + + // Removes duplicate files (eg. some files have the same name with md and json) + if (filesType.length) { + filesType = lib.removeDuplicates( + lib.removeExtensionFromFile(filesType, mainConfig.fileExtensions) + ) as string[]; + } + + const configureArgsProject = async ( + action: string, + selectedPaths: string[] + ): Promise<{ [key: string]: ProjectConfig }> => { + const projectArgsMap: { [key: string]: ProjectConfig } = {}; + + // gets workspace directory + const workspaceFolderPath: string = editorWorkspace.getWorkspaceURIPath(); + + for (const filePath of selectedPaths) { + let projectName: string = ""; + let [projectPath, cmPath]: string[] = []; + let args: ArgsConfig[] = []; + let fromRetrieve: boolean = false; + + if (filePath.includes(action)) { + // Action Retrieve or Deploy were triggered from their folder + [projectPath, cmPath] = filePath.split(`/${action}`); + } else { + if (action === "deploy") { + log("debug", "Context Menu Command Deploy From Retrieve folder..."); + // Action Deploy from Retrieve was triggered (fromRetrieve) + [projectPath, cmPath] = filePath.split(`/retrieve`); + fromRetrieve = true; + } else { + // error + } + } + + // Gets the project folder name + projectName = lib.getProjectNameFromPath(projectPath); + + log("debug", `Current workspace folder path: ${workspaceFolderPath}`); + log("debug", `Project Name: ${projectName}`); + log("debug", `Project path: ${projectPath}`); + log("debug", `Context Menu path: ${cmPath}`); + + 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 isDevToolsProject(projectName + "/"); + log( + "debug", + `SubFolder project '${projectPath}' ${isSubFolderDevToolsProject ? "is" : "is not"} a DevTools Project.` + ); + if (!isSubFolderDevToolsProject) { + editorInput.handleShowNotificationMessage( + "error", + `Folder '${projectName}' is not a SFMC DevTools Project.`, + [] + ); + return {}; + } + } + + // Checks if the project name is already in the map + if (!(projectName in projectArgsMap)) { + projectArgsMap[projectName] = { + path: projectPath, + args: [] + }; + } + + args = projectArgsMap[projectName].args; + + // When user only clicks on retrieve or deploy folder + if (projectPath && !cmPath) { + let filteredByBU: ArgsConfig[] = args.filter( + ({ bu }: ArgsConfig) => bu !== undefined && bu === `"*"` + ); + if (!filteredByBU.length) { + args = [...args, { bu: `"*"`, mdtypes: [], key: [], fromRetrieve }]; + } + log("debug", `Updated project path for '${action} "*"': ${projectPath}.`); + } + + // When user clicks inside a retrieve or deploy folder + if (cmPath) { + let { credentialName, businessUnit, metadataType, keys } = getMCDevRelativePathComponents( + cmPath.substring(1) + ); + let key: string = ""; + + if (metadataType && !DevToolsCommands.isSupportedMetadataType(action, metadataType)) { + logUnsupportedMtdtTypeNotification(action, metadataType); + continue; + } + + // If user selected to retrieve/deploy a subfolder/file inside metadata type asset folder + if (metadataType === "asset" && keys.length) { + // Gets the asset subfolder and asset key + const [assetFolder, assetKey] = keys; + if (!assetKey) { + // if user only selected an asset subfolder + // type will be changed to "asset-[name of the asset subfolder]" + metadataType = `${metadataType}-${assetFolder}`; + } + // if user selects a file inside a subfolder of asset + // the key will be the name of the file + keys = assetKey ? [assetKey] : []; + } else if (metadataType === "folder" && keys.length) { + // Nested folders are not supported as keys for the metadata type folder + keys = []; + } + + key = keys.length ? keys[0] : ""; + + let filteredByBU: ArgsConfig[] = args.filter( + ({ bu }: ArgsConfig) => + bu !== undefined && bu === `${credentialName}/${businessUnit ? businessUnit : "*"}` + ); + + if (filteredByBU.length) { + let newArgs: ArgsConfig = { + bu: filteredByBU[0].bu, + mdtypes: lib.removeNonValues( + lib.removeDuplicates([...filteredByBU[0]["mdtypes"], metadataType]) as string[] + ) as string[], + key: lib.removeNonValues( + lib.removeDuplicates([...filteredByBU[0]["key"], key]) as string[] + ) as string[], + fromRetrieve: filteredByBU[0].fromRetrieve + }; + args = [ + ...args.filter( + ({ bu }: ArgsConfig) => bu !== `${credentialName}/${businessUnit ? businessUnit : "*"}` + ), + newArgs + ]; + } else { + args = [ + ...args, + { + bu: `${credentialName}/${businessUnit ? businessUnit : "*"}`, + mdtypes: lib.removeNonValues([metadataType]) as string[], + key: lib.removeNonValues([key]) as string[], + fromRetrieve + } + ]; + } + } + projectArgsMap[projectName].args = args; + } + return projectArgsMap; + }; + + for (const optionType of [filesType, folderType]) { + if (optionType.length) { + 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}`); + let { path, args }: ProjectConfig = projectMap[projName]; + args = args.map((arg: ArgsConfig) => ({ + ...arg, + mdtypes: arg.mdtypes.length ? `"${(arg.mdtypes as string[]).join(",")}"` : "", + key: arg.key.length ? `"${(arg.key as string[]).join(",")}"` : "" + })); + + for (const dtArgs of args) { + log("debug", `Action: ${action} Args: ${JSON.stringify(dtArgs)}`); + devtoolsContainers.modifyStatusBar( + "mcdev", + action.toLowerCase() as keyof typeof StatusBarIcon + ); + await editorInput.handleInProgressMessage("Notification", progress => { + return new Promise(resolve => + DevToolsCommands.runCommand(null, action, path, dtArgs, { + 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(); + } + } + }) + ); + }); + } + }) + ); + } + } + } catch (error) { + log("error", `[main_handleDevToolsCMCommand] Error: ${error}`); + } } -async function handleCopyToBuCMCommand(selectedPaths: string[]){ - try{ - enum CopyToBUInputOptions { - COPY = `$(${StatusBarIcon.copy_to_folder}) Copy`, - COPY_AND_DEPLOY = `$(${StatusBarIcon.deploy}) Copy & Deploy` - } - - type DevToolsPathConfiguration = DevToolsPathComponents & { absolutePath: string }; - type SupportedMetadataTypeConfiguration = { supportedMetadataTypes: DevToolsPathConfiguration[], unsupportedMetadataTypes: string[] }; - - const actionOptionList: InputOptionsSettings[] = - Object.values(CopyToBUInputOptions).map((action: string) => ({ id: action, label: action, detail: "" })); - - const configuredSelectedPaths: DevToolsPathConfiguration[] = selectedPaths.map((path: string) => { - const [ _ , relativeDevToolsPath ]: string[] = path.split(/\/retrieve\/|\/deploy\//); - return { ...getMCDevRelativePathComponents(relativeDevToolsPath), absolutePath: path }; - }); - - const { supportedMetadataTypes, unsupportedMetadataTypes }: SupportedMetadataTypeConfiguration = - configuredSelectedPaths.reduce((accObj: SupportedMetadataTypeConfiguration, configPath: DevToolsPathConfiguration) => { - - if(DevToolsCommands.isSupportedMetadataType("deploy", configPath.metadataType)){ - accObj.supportedMetadataTypes.push(configPath); - }else{ - accObj.unsupportedMetadataTypes = lib.removeDuplicates([...accObj.unsupportedMetadataTypes, configPath.metadataType]) as string[]; - } - return accObj; - - }, { supportedMetadataTypes: [], unsupportedMetadataTypes: [] }); - - - if(unsupportedMetadataTypes.length && !supportedMetadataTypes.length){ - logUnsupportedMtdtTypeNotification("deploy", unsupportedMetadataTypes); - return; - } - - const selectedAction: InputOptionsSettings | undefined = await editorInput.handleQuickPickSelection( - actionOptionList, - mainConfig.messages.copyToBUInput, - false - ) as InputOptionsSettings; - - if(selectedAction){ - const credentials: {[key: string]: string[]} | undefined = await getCredentialsBU(); - if(credentials){ - const instances: string[] = Object.keys(credentials); - const singleInstance: boolean = instances.length === 1; - let selectedInstance: string = singleInstance ? instances[0] : ""; - - if(!singleInstance){ - const instanceOptions: InputOptionsSettings[] = - instances.map((instance: string) => ({ id: instance, label: instance, detail: "" })); - const instanceResponse: InputOptionsSettings | undefined = - await editorInput.handleQuickPickSelection( - instanceOptions, - mainConfig.messages.selectCredential, - false - ) as InputOptionsSettings; - if(instanceResponse){ - selectedInstance = instanceResponse.id; - } - } - - if(selectedInstance){ - const buOptionsList: InputOptionsSettings[] = - credentials[selectedInstance].map((businessUnit: string) => ({ id: businessUnit, label: businessUnit, detail: "" })); - const buOptions: InputOptionsSettings[] | undefined = - await editorInput.handleQuickPickSelection(buOptionsList, mainConfig.messages.selectBusinessUnit, true) as InputOptionsSettings[]; - - if(buOptions){ - - type FileCopyConfig = { sourceFilePath: string; targetFilePath: string; }; - const buSelected: string[] = buOptions.map((bu: InputOptionsSettings) => bu.id); - - const filePathsConfigured: FileCopyConfig[] = - supportedMetadataTypes.map((configPath: DevToolsPathConfiguration) => { - - const { absolutePath, businessUnit } = configPath; - - if(businessUnit){ - let paths: string[] = []; - - if(file.isPathADirectory(absolutePath)){ - paths = [...paths, absolutePath]; - }else{ - const [ currentFileExt ]: string[] = - mainConfig.fileExtensions.filter((fileExt: string) => absolutePath.endsWith(fileExt)); - if(currentFileExt){ - paths = [ - ...paths, - ...file.fileExists( - mainConfig.fileExtensions - .filter((fileExtension: string) => !mainConfig.noCopyFileExtensions.includes(fileExtension)) - .map((fileExtension: string) => absolutePath.replace(currentFileExt, fileExtension)) - ) - ]; - } - } - - return buSelected - .filter((buSelected: string) => buSelected !== businessUnit) - .map((buSelected: string) => - paths.map((keyFilePath: string) => - ({ - sourceFilePath: keyFilePath, - targetFilePath: keyFilePath - .replace(/\/retrieve\//, "/deploy/") - .replace(businessUnit, buSelected) - })) - ) - .flat(); - } - return; - }) - .filter((filePath: FileCopyConfig[] | undefined) => filePath !== undefined) - .flat() as FileCopyConfig[]; - - const targetFilePaths: string[] = await file.copyFile(filePathsConfigured as FileCopyConfig[], (error: any) => { - if(error !== null){ - log("error", `[main_handleCopyToBuCMCommand] Failed to copy file: ${error}`); - } - }); - - if(selectedAction.label === CopyToBUInputOptions.COPY_AND_DEPLOY){ - handleDevToolsCMCommand("deploy", targetFilePaths); - } - - if(unsupportedMetadataTypes.length){ - logUnsupportedMtdtTypeNotification("deploy", unsupportedMetadataTypes); - } - - log("info", - `Copying to the deploy folder`+ - `${selectedAction.label === CopyToBUInputOptions.COPY_AND_DEPLOY ? " and Deploying " : " "}`+ - `the following selected files:\n` + - editorWorkspace.getFileSystemPaths(targetFilePaths).join("\n") - ); - } - } - }else{ - log("error", `[main_handleCopyToBuCMCommand] Failed to retrieve DevTools credentials.`); - } - } - }catch(error){ - log("error", `[main_handleCopyToBuCMCommand] Error: ${error}`); - } +async function handleCopyToBuCMCommand(selectedPaths: string[]) { + try { + enum CopyToBUInputOptions { + COPY = `$(${StatusBarIcon.copy_to_folder}) Copy`, + COPY_AND_DEPLOY = `$(${StatusBarIcon.deploy}) Copy & Deploy` + } + + type DevToolsPathConfiguration = DevToolsPathComponents & { absolutePath: string }; + type SupportedMetadataTypeConfiguration = { + supportedMetadataTypes: DevToolsPathConfiguration[]; + unsupportedMetadataTypes: string[]; + }; + + const actionOptionList: InputOptionsSettings[] = Object.values(CopyToBUInputOptions).map((action: string) => ({ + id: action, + label: action, + detail: "" + })); + + const configuredSelectedPaths: DevToolsPathConfiguration[] = selectedPaths.map((path: string) => { + const [_, relativeDevToolsPath]: string[] = path.split(/\/retrieve\/|\/deploy\//); + return { ...getMCDevRelativePathComponents(relativeDevToolsPath), absolutePath: path }; + }); + + const { supportedMetadataTypes, unsupportedMetadataTypes }: SupportedMetadataTypeConfiguration = + configuredSelectedPaths.reduce( + (accObj: SupportedMetadataTypeConfiguration, configPath: DevToolsPathConfiguration) => { + if (DevToolsCommands.isSupportedMetadataType("deploy", configPath.metadataType)) { + accObj.supportedMetadataTypes.push(configPath); + } else { + accObj.unsupportedMetadataTypes = lib.removeDuplicates([ + ...accObj.unsupportedMetadataTypes, + configPath.metadataType + ]) as string[]; + } + return accObj; + }, + { supportedMetadataTypes: [], unsupportedMetadataTypes: [] } + ); + + if (unsupportedMetadataTypes.length && !supportedMetadataTypes.length) { + logUnsupportedMtdtTypeNotification("deploy", unsupportedMetadataTypes); + return; + } + + const selectedAction: InputOptionsSettings | undefined = (await editorInput.handleQuickPickSelection( + actionOptionList, + mainConfig.messages.copyToBUInput, + false + )) as InputOptionsSettings; + + if (selectedAction) { + const credentials: { [key: string]: string[] } | undefined = await getCredentialsBU(); + if (credentials) { + const instances: string[] = Object.keys(credentials); + const singleInstance: boolean = instances.length === 1; + let selectedInstance: string = singleInstance ? instances[0] : ""; + + if (!singleInstance) { + const instanceOptions: InputOptionsSettings[] = instances.map((instance: string) => ({ + id: instance, + label: instance, + detail: "" + })); + const instanceResponse: InputOptionsSettings | undefined = + (await editorInput.handleQuickPickSelection( + instanceOptions, + mainConfig.messages.selectCredential, + false + )) as InputOptionsSettings; + if (instanceResponse) { + selectedInstance = instanceResponse.id; + } + } + + if (selectedInstance) { + const buOptionsList: InputOptionsSettings[] = credentials[selectedInstance].map( + (businessUnit: string) => ({ id: businessUnit, label: businessUnit, detail: "" }) + ); + const buOptions: InputOptionsSettings[] | undefined = (await editorInput.handleQuickPickSelection( + buOptionsList, + mainConfig.messages.selectBusinessUnit, + true + )) as InputOptionsSettings[]; + + if (buOptions) { + type FileCopyConfig = { sourceFilePath: string; targetFilePath: string }; + const buSelected: string[] = buOptions.map((bu: InputOptionsSettings) => bu.id); + + const filePathsConfigured: FileCopyConfig[] = supportedMetadataTypes + .map((configPath: DevToolsPathConfiguration) => { + const { absolutePath, businessUnit } = configPath; + + if (businessUnit) { + let paths: string[] = []; + + if (file.isPathADirectory(absolutePath)) { + paths = [...paths, absolutePath]; + } else { + const [currentFileExt]: string[] = mainConfig.fileExtensions.filter( + (fileExt: string) => absolutePath.endsWith(fileExt) + ); + if (currentFileExt) { + paths = [ + ...paths, + ...file.fileExists( + mainConfig.fileExtensions + .filter( + (fileExtension: string) => + !mainConfig.noCopyFileExtensions.includes(fileExtension) + ) + .map((fileExtension: string) => + absolutePath.replace(currentFileExt, fileExtension) + ) + ) + ]; + } + } + + return buSelected + .filter((buSelected: string) => buSelected !== businessUnit) + .map((buSelected: string) => + paths.map((keyFilePath: string) => ({ + sourceFilePath: keyFilePath, + targetFilePath: keyFilePath + .replace(/\/retrieve\//, "/deploy/") + .replace(businessUnit, buSelected) + })) + ) + .flat(); + } + return; + }) + .filter((filePath: FileCopyConfig[] | undefined) => filePath !== undefined) + .flat() as FileCopyConfig[]; + + const targetFilePaths: string[] = await file.copyFile( + filePathsConfigured as FileCopyConfig[], + (error: any) => { + if (error !== null) { + log("error", `[main_handleCopyToBuCMCommand] Failed to copy file: ${error}`); + } + } + ); + + if (selectedAction.label === CopyToBUInputOptions.COPY_AND_DEPLOY) { + handleDevToolsCMCommand("deploy", targetFilePaths); + } + + if (unsupportedMetadataTypes.length) { + logUnsupportedMtdtTypeNotification("deploy", unsupportedMetadataTypes); + } + + log( + "info", + `Copying to the deploy folder` + + `${selectedAction.label === CopyToBUInputOptions.COPY_AND_DEPLOY ? " and Deploying " : " "}` + + `the following selected files:\n` + + editorWorkspace.getFileSystemPaths(targetFilePaths).join("\n") + ); + } + } + } else { + log("error", `[main_handleCopyToBuCMCommand] Failed to retrieve DevTools credentials.`); + } + } + } catch (error) { + log("error", `[main_handleCopyToBuCMCommand] Error: ${error}`); + } } export const devtoolsMain = { - initDevToolsExtension, - handleStatusBarActions, - handleContextMenuActions -}; \ No newline at end of file + initDevToolsExtension, + handleStatusBarActions, + handleContextMenuActions +}; diff --git a/src/devtools/prerequisites.ts b/src/devtools/prerequisites.ts index 9ce8ccb..e10203e 100644 --- a/src/devtools/prerequisites.ts +++ b/src/devtools/prerequisites.ts @@ -6,92 +6,88 @@ import { editorWebview } from "../editor/webview"; import { editorWorkspace } from "../editor/workspace"; import { log } from "../editor/output"; -interface PrerequisitesInstalledReturn { - prerequisitesInstalled: boolean, - missingPrerequisites: string[] -}; +interface PrerequisitesInstalledReturn { + prerequisitesInstalled: boolean; + missingPrerequisites: string[]; +} async function arePrerequisitesInstalled(): Promise { - let prerequisiteResult: PrerequisitesInstalledReturn = { prerequisitesInstalled: true, missingPrerequisites: []}; - for (const [prerequisite, command] of Object.entries(prerequisitesConfig.packages)){ - await new Promise(resolve => { - terminal.executeTerminalCommand({ - command, - args: [], - cwd: editorWorkspace.getWorkspaceURIPath(), - handleResult: (error: string | null, output: string | null, code: number | null) => { - if (error) { - log("error", - `[prerequisites_arePrerequisitesInstalled] Missing Pre-Requisite '${prerequisite}': ${error}` - ); - prerequisiteResult = { - prerequisitesInstalled: false, - missingPrerequisites: [ - ...prerequisiteResult.missingPrerequisites, - prerequisite - ] - }; - } - if (output) { - log("debug", - `[prerequisites_arePrerequisitesInstalled] '${prerequisite}': ${output}` - ); - } - if (code !== null) { - log("debug", - `[prerequisites_arePrerequisitesInstalled] Exit Code: '${code}'` - ); - resolve(); - } - } - }); - }); - } - return prerequisiteResult; -}; + let prerequisiteResult: PrerequisitesInstalledReturn = { prerequisitesInstalled: true, missingPrerequisites: [] }; + for (const [prerequisite, command] of Object.entries(prerequisitesConfig.packages)) { + await new Promise(resolve => { + terminal.executeTerminalCommand({ + command, + args: [], + cwd: editorWorkspace.getWorkspaceURIPath(), + handleResult: (error: string | null, output: string | null, code: number | null) => { + if (error) { + log( + "error", + `[prerequisites_arePrerequisitesInstalled] Missing Pre-Requisite '${prerequisite}': ${error}` + ); + prerequisiteResult = { + prerequisitesInstalled: false, + missingPrerequisites: [...prerequisiteResult.missingPrerequisites, prerequisite] + }; + } + if (output) { + log("debug", `[prerequisites_arePrerequisitesInstalled] '${prerequisite}': ${output}`); + } + if (code !== null) { + log("debug", `[prerequisites_arePrerequisitesInstalled] Exit Code: '${code}'`); + resolve(); + } + } + }); + }); + } + return prerequisiteResult; +} async function noPrerequisitesHandler(extensionPath: string, missingPrerequisites: string[]): Promise { - // checks if the one or more prerequisites are missing to show the correct message. - const missingPrerequisitesMessage: string = missingPrerequisites.length === 1 ? - prerequisitesConfig.messages["onePrerequisiteMissing"].replace("{{prerequisites}}", missingPrerequisites[0]) : - prerequisitesConfig.messages["multiplePrerequisitesMissing"].replace("{{prerequisites}}", missingPrerequisites.join(" and ")); - - const message: string = `${missingPrerequisitesMessage} ${prerequisitesConfig.messages.askPrerequisitesToUser}`; + // checks if the one or more prerequisites are missing to show the correct message. + const missingPrerequisitesMessage: string = + missingPrerequisites.length === 1 + ? prerequisitesConfig.messages["onePrerequisiteMissing"].replace( + "{{prerequisites}}", + missingPrerequisites[0] + ) + : prerequisitesConfig.messages["multiplePrerequisitesMissing"].replace( + "{{prerequisites}}", + missingPrerequisites.join(" and ") + ); + + const message: string = `${missingPrerequisitesMessage} ${prerequisitesConfig.messages.askPrerequisitesToUser}`; - // Asks if user wishes to follow the guide of how to install the prerequisites - const userResponse: string | undefined = await editorInput.handleShowOptionsMessage( - message, - Object.keys(NoPrerequisitesResponseOptions).filter((v) => isNaN(Number(v))) - ); + // Asks if user wishes to follow the guide of how to install the prerequisites + const userResponse: string | undefined = await editorInput.handleShowOptionsMessage( + message, + Object.keys(NoPrerequisitesResponseOptions).filter(v => isNaN(Number(v))) + ); - log("debug", - `[prerequisites_noPrerequisitesHandler] User Response = ${userResponse}.` - ); + log("debug", `[prerequisites_noPrerequisitesHandler] User Response = ${userResponse}.`); - // If yes creates an webview in vscode with a installation guide - if(userResponse && NoPrerequisitesResponseOptions[userResponse as keyof typeof NoPrerequisitesResponseOptions]){ - editorWebview.create({ - id: prerequisitesConfig.webview.id, - title: prerequisitesConfig.webview.title, - extensionPath: extensionPath, - filename: prerequisitesConfig.webview.filename, - handler: ({ command }: { command: string }) => { - if(command === "install"){ - devtoolsInstaller.installDevTools(); - return { dispose: true }; - } - return { dispose: false }; - } - }); - } + // If yes creates an webview in vscode with a installation guide + if (userResponse && NoPrerequisitesResponseOptions[userResponse as keyof typeof NoPrerequisitesResponseOptions]) { + editorWebview.create({ + id: prerequisitesConfig.webview.id, + title: prerequisitesConfig.webview.title, + extensionPath: extensionPath, + filename: prerequisitesConfig.webview.filename, + handler: ({ command }: { command: string }) => { + if (command === "install") { + devtoolsInstaller.installDevTools(); + return { dispose: true }; + } + return { dispose: false }; + } + }); + } } const devtoolsPrerequisites = { - arePrerequisitesInstalled, - noPrerequisitesHandler + arePrerequisitesInstalled, + noPrerequisitesHandler }; -export { - PrerequisitesInstalledReturn, - devtoolsPrerequisites -}; \ No newline at end of file +export { PrerequisitesInstalledReturn, devtoolsPrerequisites }; diff --git a/src/editor/commands.ts b/src/editor/commands.ts index 33c8e19..864ce73 100644 --- a/src/editor/commands.ts +++ b/src/editor/commands.ts @@ -1,37 +1,24 @@ import { commands, Uri } from "vscode"; interface CommandRegister { - command: string, - callbackAction: (file: Uri, files: Uri[]) => void + command: string; + callbackAction: (file: Uri, files: Uri[]) => void; } function registerCommand(register: CommandRegister | CommandRegister[]): void { - [register] - .flat() - .forEach( - (registry) => commands.registerCommand(registry.command, registry.callbackAction) - ); + [register].flat().forEach(registry => commands.registerCommand(registry.command, registry.callbackAction)); } -function executeCommand(command: string | string[], args: (string | boolean | string[])[]){ - [command] - .flat() - .forEach( - async (command: string) => await commands.executeCommand(command, ...args) - ); +function executeCommand(command: string | string[], args: (string | boolean | string[])[]) { + [command].flat().forEach(async (command: string) => await commands.executeCommand(command, ...args)); } -function setCommandContext(command: string | string[], args: string | boolean | number){ - [command] - .flat() - .forEach( - (command: string) => commands.executeCommand('setContext', command, args) - ); +function setCommandContext(command: string | string[], args: string | boolean | number) { + [command].flat().forEach((command: string) => commands.executeCommand("setContext", command, args)); } const editorCommands = { - registerCommand, - executeCommand, - setCommandContext + registerCommand, + executeCommand, + setCommandContext }; export { Uri, editorCommands }; - diff --git a/src/editor/containers.ts b/src/editor/containers.ts index 0c7a59e..958c5be 100644 --- a/src/editor/containers.ts +++ b/src/editor/containers.ts @@ -1,28 +1,26 @@ import { window, StatusBarItem, StatusBarAlignment, ThemeColor } from "vscode"; function createStatusBarItem(command: string, title: string, name: string): StatusBarItem { - let statusBar: StatusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, 110); - statusBar.name = name; - statusBar.command = command; - statusBar.text = title; - return statusBar; + let statusBar: StatusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, 110); + statusBar.name = name; + statusBar.command = command; + statusBar.text = title; + return statusBar; } function displayStatusBarItem(statusBar: StatusBarItem | StatusBarItem[]): StatusBarItem | StatusBarItem[] { - [statusBar] - .flat() - .forEach((sbi: StatusBarItem) => sbi.show()); - return statusBar; + [statusBar].flat().forEach((sbi: StatusBarItem) => sbi.show()); + return statusBar; } -function getBackgroundColor(status: string){ - return new ThemeColor(`statusBarItem.${status}Background`); +function getBackgroundColor(status: string) { + return new ThemeColor(`statusBarItem.${status}Background`); } const editorContainers = { - createStatusBarItem, - displayStatusBarItem, - getBackgroundColor + createStatusBarItem, + displayStatusBarItem, + getBackgroundColor }; -export { StatusBarItem, editorContainers }; \ No newline at end of file +export { StatusBarItem, editorContainers }; diff --git a/src/editor/context.ts b/src/editor/context.ts index 3b841bc..2b631e3 100644 --- a/src/editor/context.ts +++ b/src/editor/context.ts @@ -1,10 +1,10 @@ import { ExtensionContext } from "vscode"; -type EditorContext = { set: (context: ExtensionContext) => void, get: () => ExtensionContext }; +type EditorContext = { set: (context: ExtensionContext) => void; get: () => ExtensionContext }; let contextInstance: ExtensionContext; const editorContext: EditorContext = { - set: (context: ExtensionContext) => contextInstance = context, - get: () => contextInstance + set: (context: ExtensionContext) => (contextInstance = context), + get: () => contextInstance }; -export { ExtensionContext, editorContext }; \ No newline at end of file +export { ExtensionContext, editorContext }; diff --git a/src/editor/dependencies.ts b/src/editor/dependencies.ts index 4651e1b..e2ad812 100644 --- a/src/editor/dependencies.ts +++ b/src/editor/dependencies.ts @@ -4,56 +4,53 @@ import { editorInput } from "./input"; import { editorCommands } from "./commands"; enum RecommendedExtensionsInputOptions { - INSTALL = "Install", - NOT_NOW = "Not Now", - DONT_ASK_AGAIN = "Do not show again" -}; - -async function activateExtensionDependencies(dependencies: string | string[]){ - - const missingExtDependencies: string[] = - [dependencies] - .flat() - .filter((dependencyName: string) => !extensions.getExtension(dependencyName)); - - if(missingExtDependencies.length){ - - const workspaceConfiguration = editorWorkspace.handleWorkspaceConfiguration("sfmc-devtools-vscode", "Global"); - const suggestRecommendedExtensions: boolean = - Boolean(workspaceConfiguration.get("recommendExtensions", true)); + INSTALL = "Install", + NOT_NOW = "Not Now", + DONT_ASK_AGAIN = "Do not show again" +} - if(suggestRecommendedExtensions){ - const message: string = "There are some recommended extensions that can enhance your usage of SFMC DevTools. Would you like to install them?"; - const options: string[] = Object.values(RecommendedExtensionsInputOptions); - const selectedOption: string | undefined = await editorInput.handleShowOptionsMessage(message, options); - if(selectedOption){ - if(selectedOption === RecommendedExtensionsInputOptions.INSTALL){ - missingExtDependencies.forEach((extDependency: string) => { - editorCommands.executeCommand( - ["extension.open", "workbench.extensions.installExtension"], - [extDependency] - ); - }); - }else if(selectedOption === RecommendedExtensionsInputOptions.DONT_ASK_AGAIN){ - workspaceConfiguration.set("recommendExtensions", false); - } - } - } - } +async function activateExtensionDependencies(dependencies: string | string[]) { + const missingExtDependencies: string[] = [dependencies] + .flat() + .filter((dependencyName: string) => !extensions.getExtension(dependencyName)); + + if (missingExtDependencies.length) { + const workspaceConfiguration = editorWorkspace.handleWorkspaceConfiguration("sfmc-devtools-vscode", "Global"); + const suggestRecommendedExtensions: boolean = Boolean(workspaceConfiguration.get("recommendExtensions", true)); + + if (suggestRecommendedExtensions) { + const message: string = + "There are some recommended extensions that can enhance your usage of SFMC DevTools. Would you like to install them?"; + const options: string[] = Object.values(RecommendedExtensionsInputOptions); + const selectedOption: string | undefined = await editorInput.handleShowOptionsMessage(message, options); + if (selectedOption) { + if (selectedOption === RecommendedExtensionsInputOptions.INSTALL) { + missingExtDependencies.forEach((extDependency: string) => { + editorCommands.executeCommand( + ["extension.open", "workbench.extensions.installExtension"], + [extDependency] + ); + }); + } else if (selectedOption === RecommendedExtensionsInputOptions.DONT_ASK_AGAIN) { + workspaceConfiguration.set("recommendExtensions", false); + } + } + } + } } -function deactivateCompactFolders(){ - const workspaceConfiguration = editorWorkspace.handleWorkspaceConfiguration("explorer", "Workspace"); - const isCompactFoldersEnabled: boolean = Boolean(workspaceConfiguration.get("compactFolders", true)); - if(isCompactFoldersEnabled){ - // Disable Compact Folders - workspaceConfiguration.set("compactFolders", false); - } +function deactivateCompactFolders() { + const workspaceConfiguration = editorWorkspace.handleWorkspaceConfiguration("explorer", "Workspace"); + const isCompactFoldersEnabled: boolean = Boolean(workspaceConfiguration.get("compactFolders", true)); + if (isCompactFoldersEnabled) { + // Disable Compact Folders + workspaceConfiguration.set("compactFolders", false); + } } const editorDependencies = { - activateExtensionDependencies, - deactivateCompactFolders + activateExtensionDependencies, + deactivateCompactFolders }; -export { editorDependencies }; \ No newline at end of file +export { editorDependencies }; diff --git a/src/editor/input.ts b/src/editor/input.ts index 52c7874..4facab4 100644 --- a/src/editor/input.ts +++ b/src/editor/input.ts @@ -3,65 +3,70 @@ import { editorOutput } from "./output"; import InputOptionsSettings from "../shared/interfaces/inputOptionsSettings"; enum NotificationMessage { - info, - warning, - error -}; + info, + warning, + error +} async function handleQuickPickSelection( - optionsList: InputOptionsSettings[], - placeHolder: string, - canPickMany: boolean): Promise { - const selectedOption: InputOptionsSettings | InputOptionsSettings[] | undefined = await window.showQuickPick( - optionsList, - { placeHolder: placeHolder, canPickMany: canPickMany, ignoreFocusOut: true } - ); - return selectedOption; + optionsList: InputOptionsSettings[], + placeHolder: string, + canPickMany: boolean +): Promise { + const selectedOption: InputOptionsSettings | InputOptionsSettings[] | undefined = await window.showQuickPick( + optionsList, + { placeHolder: placeHolder, canPickMany: canPickMany, ignoreFocusOut: true } + ); + return selectedOption; } async function handleShowOptionsMessage(message: string, actions: string[]): Promise { - const response: string | undefined = await window.showInformationMessage(message, ...actions); - return response; + const response: string | undefined = await window.showInformationMessage(message, ...actions); + return response; } async function handleShowInputBox(placeHolder: string): Promise { - const response: string | undefined = await window.showInputBox({ placeHolder, ignoreFocusOut: true}); - return response; + const response: string | undefined = await window.showInputBox({ placeHolder, ignoreFocusOut: true }); + return response; } -async function handleInProgressMessage(local: string, callbackFn: (progress: Progress<{message: string, increment?: number}>) => Promise ){ - await window.withProgress({ location: ProgressLocation[local as keyof typeof ProgressLocation]}, - async(progress) => callbackFn(progress) - ); +async function handleInProgressMessage( + local: string, + callbackFn: (progress: Progress<{ message: string; increment?: number }>) => Promise +) { + await window.withProgress({ location: ProgressLocation[local as keyof typeof ProgressLocation] }, async progress => + callbackFn(progress) + ); } -function handleShowNotificationMessage(level: keyof typeof NotificationMessage, message: string, actions: string[]){ - type NotificationLevelFunctions = { - [key in keyof typeof NotificationMessage]: (message: string, actions: string[]) => Thenable; - }; +function handleShowNotificationMessage(level: keyof typeof NotificationMessage, message: string, actions: string[]) { + type NotificationLevelFunctions = { + [key in keyof typeof NotificationMessage]: (message: string, actions: string[]) => Thenable; + }; + + const defaultActions: string[] = ["More Details", "Close"]; - const defaultActions: string[] = ["More Details", "Close"]; + const notificationLevelFunctions: NotificationLevelFunctions = { + info: (message: string, actions: string[]) => + window.showInformationMessage(message, ...actions, ...defaultActions), + warning: (message: string, actions: string[]) => + window.showWarningMessage(message, ...actions, ...defaultActions), + error: (message: string, actions: string[]) => window.showErrorMessage(message, ...actions, ...defaultActions) + }; - const notificationLevelFunctions: NotificationLevelFunctions = { - info: (message: string, actions: string[]) => window.showInformationMessage(message, ...actions, ...defaultActions), - warning: (message: string, actions: string[]) => window.showWarningMessage(message, ...actions, ...defaultActions), - error: (message: string, actions: string[]) => window.showErrorMessage(message, ...actions, ...defaultActions), - }; - - const callNotificationFunction = notificationLevelFunctions[level]; - - callNotificationFunction(message, actions) - .then((response: string | undefined) => { - if(response === "More Details"){ - editorOutput.showOuputChannel(); - } - }); + const callNotificationFunction = notificationLevelFunctions[level]; + + callNotificationFunction(message, actions).then((response: string | undefined) => { + if (response === "More Details") { + editorOutput.showOuputChannel(); + } + }); } export const editorInput = { - handleQuickPickSelection, - handleShowOptionsMessage, - handleInProgressMessage, - handleShowInputBox, - handleShowNotificationMessage -}; \ No newline at end of file + handleQuickPickSelection, + handleShowOptionsMessage, + handleInProgressMessage, + handleShowInputBox, + handleShowNotificationMessage +}; diff --git a/src/editor/output.ts b/src/editor/output.ts index 341ba3e..4518d33 100644 --- a/src/editor/output.ts +++ b/src/editor/output.ts @@ -3,10 +3,10 @@ import { lib } from "../shared/utils/lib"; import { fileLogger, FileLogger } from "../shared/utils/fileLogger"; enum LogLevel { - debug = "DEBUG", - info = "INFO", - warning = "WARNING", - error = "ERROR" + debug = "DEBUG", + info = "INFO", + warning = "WARNING", + error = "ERROR" } // DEBUG, WARNING AND ERROR => File logger @@ -15,46 +15,43 @@ enum LogLevel { let outputChannel: OutputChannel; let fileLoggerMap: { [key: string]: FileLogger } = {}; -function initFileLogger(logPath: string | string[]){ - fileLoggerMap = [logPath] - .flat() - .reduce((prev: {}, path: string) => { - const projectName: string = lib.getProjectNameFromPath(path); - return { - ...prev, - [projectName]: fileLogger.createFileLogger(path.replace('/c:', '')) - }; - },{}); +function initFileLogger(logPath: string | string[]) { + fileLoggerMap = [logPath].flat().reduce((prev: {}, path: string) => { + const projectName: string = lib.getProjectNameFromPath(path); + return { + ...prev, + [projectName]: fileLogger.createFileLogger(path.replace("/c:", "")) + }; + }, {}); } -function showOuputChannel(){ - if(outputChannel){ - outputChannel.show(); - } +function showOuputChannel() { + if (outputChannel) { + outputChannel.show(); + } } -function log(level: keyof typeof LogLevel, output: string | number | object, logProject?: string){ - - const outputStr: string = lib.mapObject(output); - // creates an output channel - if(!outputChannel){ - outputChannel = window.createOutputChannel("SFMC Devtools"); - outputChannel.hide(); - } - - if(LogLevel[level] === LogLevel.info || LogLevel[level] === LogLevel.error){ - outputChannel.appendLine(`${outputStr}`); - } - - if(logProject && logProject in fileLoggerMap){ - const logger: FileLogger = fileLoggerMap[logProject]; - logger[level](outputStr); - } +function log(level: keyof typeof LogLevel, output: string | number | object, logProject?: string) { + const outputStr: string = lib.mapObject(output); + // creates an output channel + if (!outputChannel) { + outputChannel = window.createOutputChannel("SFMC Devtools"); + outputChannel.hide(); + } + + if (LogLevel[level] === LogLevel.info || LogLevel[level] === LogLevel.error) { + outputChannel.appendLine(`${outputStr}`); + } + + if (logProject && logProject in fileLoggerMap) { + const logger: FileLogger = fileLoggerMap[logProject]; + logger[level](outputStr); + } } const editorOutput = { - initFileLogger, - showOuputChannel + initFileLogger, + showOuputChannel }; -export { log, editorOutput }; \ No newline at end of file +export { log, editorOutput }; diff --git a/src/editor/webview.ts b/src/editor/webview.ts index efcd0be..0b45302 100644 --- a/src/editor/webview.ts +++ b/src/editor/webview.ts @@ -3,61 +3,50 @@ import { editorWorkspace } from "./workspace"; import { lib } from "../shared/utils/lib"; interface WebviewConfig { - id: string, - title: string, - extensionPath: string, - filename: string, - handler: (data: any) => { dispose: boolean } -}; + id: string; + title: string; + extensionPath: string; + filename: string; + handler: (data: any) => { dispose: boolean }; +} -async function create(config: WebviewConfig){ - try { - const panel: WebviewPanel = window.createWebviewPanel( - config.id, - config.title, - ViewColumn.One, - { enableScripts: true } - ); - - let html: string = await editorWorkspace.readFile(lib.createFilePath([ - config.extensionPath, - "src", - "html", - `${config.filename}.html` - ])); - - const sldsPath: string = lib.createFilePath([ - config.extensionPath, - "src", - "css", - "salesforce-lightning-design-system.min.css" - ]); - - const jsPath: string = lib.createFilePath([ - config.extensionPath, - "src", - "js", - `${config.filename}.js` - ]); - - const sldsUriPath: Uri = panel.webview.asWebviewUri(Uri.file(sldsPath)); - const jsUriPath: Uri = panel.webview.asWebviewUri(Uri.file(jsPath)); - - html = html.replace("{{styleUri}}", sldsUriPath.toString()); - html = html.replace("{{jsUri}}", jsUriPath.toString()); - - panel.webview.onDidReceiveMessage((data: any) => { - const { dispose }: { dispose: boolean } = config.handler(data); - if(dispose){ - panel.dispose(); - } - }); - panel.webview.html = html; - }catch(error){ - throw new Error(`Webview creation error: ${error}`); - } +async function create(config: WebviewConfig) { + try { + const panel: WebviewPanel = window.createWebviewPanel(config.id, config.title, ViewColumn.One, { + enableScripts: true + }); + + let html: string = await editorWorkspace.readFile( + lib.createFilePath([config.extensionPath, "src", "html", `${config.filename}.html`]) + ); + + const sldsPath: string = lib.createFilePath([ + config.extensionPath, + "src", + "css", + "salesforce-lightning-design-system.min.css" + ]); + + const jsPath: string = lib.createFilePath([config.extensionPath, "src", "js", `${config.filename}.js`]); + + const sldsUriPath: Uri = panel.webview.asWebviewUri(Uri.file(sldsPath)); + const jsUriPath: Uri = panel.webview.asWebviewUri(Uri.file(jsPath)); + + html = html.replace("{{styleUri}}", sldsUriPath.toString()); + html = html.replace("{{jsUri}}", jsUriPath.toString()); + + panel.webview.onDidReceiveMessage((data: any) => { + const { dispose }: { dispose: boolean } = config.handler(data); + if (dispose) { + panel.dispose(); + } + }); + panel.webview.html = html; + } catch (error) { + throw new Error(`Webview creation error: ${error}`); + } } export const editorWebview = { - create -}; \ No newline at end of file + create +}; diff --git a/src/editor/workspace.ts b/src/editor/workspace.ts index 8165357..2c3465e 100644 --- a/src/editor/workspace.ts +++ b/src/editor/workspace.ts @@ -1,85 +1,91 @@ -import { workspace, Uri, TextDocument, WorkspaceFolder, FileType, FileStat, WorkspaceConfiguration, ConfigurationTarget } from "vscode"; +import { + workspace, + Uri, + TextDocument, + WorkspaceFolder, + FileType, + FileStat, + WorkspaceConfiguration, + ConfigurationTarget +} from "vscode"; import { editorCommands } from "./commands"; type WorkspaceConfigurationTarget = "Global" | "Workspace" | "WorkspaceFolder"; function getWorkspaceURI(): Uri { - const wsFolder: readonly WorkspaceFolder[] | undefined = workspace.workspaceFolders; - if(wsFolder){ - const [{ uri }] = wsFolder; - return uri; - }else{ - throw new Error("Could not get Workspace Folder."); - } + const wsFolder: readonly WorkspaceFolder[] | undefined = workspace.workspaceFolders; + if (wsFolder) { + const [{ uri }] = wsFolder; + return uri; + } else { + throw new Error("Could not get Workspace Folder."); + } } function getWorkspaceURIPath(): string { - const wsURI: Uri = getWorkspaceURI(); - if(wsURI && "path" in wsURI){ - return wsURI.path; - } - throw new Error("Failed to find Worspace Uri PATH."); + const wsURI: Uri = getWorkspaceURI(); + if (wsURI && "path" in wsURI) { + return wsURI.path; + } + throw new Error("Failed to find Worspace Uri PATH."); } -async function getWorkspaceSubFolders(): Promise{ - const wsURI: Uri = getWorkspaceURI(); - const subFolders = await workspace.fs.readDirectory(wsURI); - return subFolders.map(([folderName]: [string, FileType]) => folderName); +async function getWorkspaceSubFolders(): Promise { + const wsURI: Uri = getWorkspaceURI(); + const subFolders = await workspace.fs.readDirectory(wsURI); + return subFolders.map(([folderName]: [string, FileType]) => folderName); } async function isFileInFolder(filename: string): Promise { - const fileArray: Uri[] = await workspace.findFiles(filename); - return fileArray.length > 0; + const fileArray: Uri[] = await workspace.findFiles(filename); + return fileArray.length > 0; } -async function readFile(path: string): Promise{ - const document: TextDocument = await workspace.openTextDocument(path); - return document.getText(); +async function readFile(path: string): Promise { + const document: TextDocument = await workspace.openTextDocument(path); + return document.getText(); } -function reloadWorkspace(){ - editorCommands.executeCommand("workbench.action.reloadWindow", []); +function reloadWorkspace() { + editorCommands.executeCommand("workbench.action.reloadWindow", []); } -function getFilesURIPath(files: Uri | Uri[]){ - return [files] - .flat() - .map((file: Uri) => file.path); +function getFilesURIPath(files: Uri | Uri[]) { + return [files].flat().map((file: Uri) => file.path); } -async function isFile(file: string | Uri){ - if(typeof file === "string"){ - file = Uri.file(file); - } - const { type }: FileStat = await workspace.fs.stat(file); - const fileType: string = FileType[type]; - return fileType.toLowerCase() === "file"; +async function isFile(file: string | Uri) { + if (typeof file === "string") { + file = Uri.file(file); + } + const { type }: FileStat = await workspace.fs.stat(file); + const fileType: string = FileType[type]; + return fileType.toLowerCase() === "file"; } -function handleWorkspaceConfiguration(section: string, target: WorkspaceConfigurationTarget){ - const workspaceConfiguration: WorkspaceConfiguration = workspace.getConfiguration(section); - if(workspaceConfiguration){ - return { - get: (key: string, value: string | boolean) => workspaceConfiguration.get(key, value), - set: (key: string, value: string | boolean) => workspaceConfiguration.update(key, value, ConfigurationTarget[target]) - }; - } - throw new Error("Failed to handle Workspace Configuration."); +function handleWorkspaceConfiguration(section: string, target: WorkspaceConfigurationTarget) { + const workspaceConfiguration: WorkspaceConfiguration = workspace.getConfiguration(section); + if (workspaceConfiguration) { + return { + get: (key: string, value: string | boolean) => workspaceConfiguration.get(key, value), + set: (key: string, value: string | boolean) => + workspaceConfiguration.update(key, value, ConfigurationTarget[target]) + }; + } + throw new Error("Failed to handle Workspace Configuration."); } -function getFileSystemPaths(paths: string | string[]){ - return [paths] - .flat() - .map((path: string) => Uri.file(path).fsPath); +function getFileSystemPaths(paths: string | string[]) { + return [paths].flat().map((path: string) => Uri.file(path).fsPath); } export const editorWorkspace = { - isFileInFolder, - readFile, - reloadWorkspace, - getWorkspaceURIPath, - getWorkspaceSubFolders, - handleWorkspaceConfiguration, - getFilesURIPath, - isFile, - getFileSystemPaths -}; \ No newline at end of file + isFileInFolder, + readFile, + reloadWorkspace, + getWorkspaceURIPath, + getWorkspaceSubFolders, + handleWorkspaceConfiguration, + getFilesURIPath, + isFile, + getFileSystemPaths +}; diff --git a/src/extension.ts b/src/extension.ts index 482a5fb..f51fe40 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,5 +1,5 @@ -import { editorContext, ExtensionContext } from './editor/context'; -import { devtoolsMain } from './devtools/main'; +import { editorContext, ExtensionContext } from "./editor/context"; +import { devtoolsMain } from "./devtools/main"; export async function activate(context: ExtensionContext) { editorContext.set(context); devtoolsMain.initDevToolsExtension(); diff --git a/src/shared/interfaces/devToolsCommandRunner.ts b/src/shared/interfaces/devToolsCommandRunner.ts index f1f76bb..3e06c7a 100644 --- a/src/shared/interfaces/devToolsCommandRunner.ts +++ b/src/shared/interfaces/devToolsCommandRunner.ts @@ -1,11 +1,11 @@ import DevToolsCommandSetting from "./devToolsCommandSetting"; -interface DevToolsCommandRunner{ - commandId: string, - commandConfig: DevToolsCommandSetting, - commandArgs: { [key: string]: string | string[] | boolean }, - commandPath: string, - commandHandlers: { [key: string]: (args?: any) => void } +interface DevToolsCommandRunner { + commandId: string; + commandConfig: DevToolsCommandSetting; + commandArgs: { [key: string]: string | string[] | boolean }; + commandPath: string; + commandHandlers: { [key: string]: (args?: any) => void }; } -export default DevToolsCommandRunner; \ No newline at end of file +export default DevToolsCommandRunner; diff --git a/src/shared/interfaces/devToolsCommandSetting.ts b/src/shared/interfaces/devToolsCommandSetting.ts index 4711aec..17e034a 100644 --- a/src/shared/interfaces/devToolsCommandSetting.ts +++ b/src/shared/interfaces/devToolsCommandSetting.ts @@ -1,11 +1,11 @@ interface DevToolsCommandSetting { - id: string, - title: string, - description: string, - command: string, - requiredParams: Array, - optionalParams: Array, - isAvailable: boolean + id: string; + title: string; + description: string; + command: string; + requiredParams: Array; + optionalParams: Array; + isAvailable: boolean; } -export default DevToolsCommandSetting; \ No newline at end of file +export default DevToolsCommandSetting; diff --git a/src/shared/interfaces/devToolsPathComponents.ts b/src/shared/interfaces/devToolsPathComponents.ts index fcb94a1..46437dc 100644 --- a/src/shared/interfaces/devToolsPathComponents.ts +++ b/src/shared/interfaces/devToolsPathComponents.ts @@ -1,8 +1,8 @@ interface DevToolsPathComponents { - credentialName: string, - businessUnit: string, - metadataType: string, - keys: string[] + credentialName: string; + businessUnit: string; + metadataType: string; + keys: string[]; } -export default DevToolsPathComponents; \ No newline at end of file +export default DevToolsPathComponents; diff --git a/src/shared/interfaces/inputOptionsSettings.ts b/src/shared/interfaces/inputOptionsSettings.ts index c85eff0..2f75308 100644 --- a/src/shared/interfaces/inputOptionsSettings.ts +++ b/src/shared/interfaces/inputOptionsSettings.ts @@ -1,6 +1,6 @@ interface InputOptionsSettings { - id: string, - label: string, - detail: string -}; -export default InputOptionsSettings; \ No newline at end of file + id: string; + label: string; + detail: string; +} +export default InputOptionsSettings; diff --git a/src/shared/interfaces/supportedMetadataTypes.ts b/src/shared/interfaces/supportedMetadataTypes.ts index ca3715b..a7e4b05 100644 --- a/src/shared/interfaces/supportedMetadataTypes.ts +++ b/src/shared/interfaces/supportedMetadataTypes.ts @@ -1,9 +1,9 @@ interface SupportedMetadataTypes { - name: string, - apiName: string, - retrieveByDefault: boolean | string[], - supports: {[key: string]: boolean | null }, - description: string + name: string; + apiName: string; + retrieveByDefault: boolean | string[]; + supports: { [key: string]: boolean | null }; + description: string; } -export default SupportedMetadataTypes; \ No newline at end of file +export default SupportedMetadataTypes; diff --git a/src/shared/interfaces/terminalCommandRunner.ts b/src/shared/interfaces/terminalCommandRunner.ts index bd912f6..8c54ac5 100644 --- a/src/shared/interfaces/terminalCommandRunner.ts +++ b/src/shared/interfaces/terminalCommandRunner.ts @@ -1,8 +1,8 @@ -interface TerminalCommandRunner{ - command: string, - args: string[], - cwd: string, - handleResult: (error: string | null, output: string | null, code: number | null) => void +interface TerminalCommandRunner { + command: string; + args: string[]; + cwd: string; + handleResult: (error: string | null, output: string | null, code: number | null) => void; } -export default TerminalCommandRunner; \ No newline at end of file +export default TerminalCommandRunner; diff --git a/src/shared/utils/file.ts b/src/shared/utils/file.ts index 5ef6f12..ae2a5e0 100644 --- a/src/shared/utils/file.ts +++ b/src/shared/utils/file.ts @@ -1,58 +1,60 @@ import * as fs from "fs"; -import * as path from 'path'; - +import * as path from "path"; + function readFileSync(path: string): string { - try{ - return fs.readFileSync(path, "utf-8"); - }catch(error){ - throw error; - } + try { + return fs.readFileSync(path, "utf-8"); + } catch (error) { + throw error; + } } function fileExists(path: string | string[]): string[] { - try{ - return [path] - .flat() - .filter((path: string) => fs.existsSync(path.replace(/^\/[a-zA-Z]:/g, ""))); - }catch(error){ - throw error; - } + try { + return [path].flat().filter((path: string) => fs.existsSync(path.replace(/^\/[a-zA-Z]:/g, ""))); + } catch (error) { + throw error; + } } function isPathADirectory(path: string): boolean { - try{ - return fs.lstatSync(path.replace(/^\/[a-zA-Z]:/g, "")).isDirectory(); - }catch(error){ - throw error; - } + try { + return fs.lstatSync(path.replace(/^\/[a-zA-Z]:/g, "")).isDirectory(); + } catch (error) { + throw error; + } } function createFilePath(pathArray: string[]): string { - return path.join(...pathArray); + return path.join(...pathArray); } -async function copyFile(files: {sourceFilePath: string, targetFilePath: string}[], handleCopyFileError: (error: any) => void): Promise{ - try{ - const copiedFiles: Promise[] = files.map(async ({sourceFilePath, targetFilePath}: {sourceFilePath: string, targetFilePath: string}) => { - const noDriveLetterSourceFilePath: string = sourceFilePath.replace(/^\/[a-zA-Z]:/g, ""); - const noDriveLetterTargetFilePath: string = targetFilePath.replace(/^\/[a-zA-Z]:/g, ""); - return new Promise(resolve => fs.cp( - noDriveLetterSourceFilePath, - noDriveLetterTargetFilePath, - {recursive: true}, - (err) => err ? handleCopyFileError(err) : resolve(targetFilePath) - )); - }); - return await Promise.all(copiedFiles); - }catch(error){ - throw error; - } +async function copyFile( + files: { sourceFilePath: string; targetFilePath: string }[], + handleCopyFileError: (error: any) => void +): Promise { + try { + const copiedFiles: Promise[] = files.map( + async ({ sourceFilePath, targetFilePath }: { sourceFilePath: string; targetFilePath: string }) => { + const noDriveLetterSourceFilePath: string = sourceFilePath.replace(/^\/[a-zA-Z]:/g, ""); + const noDriveLetterTargetFilePath: string = targetFilePath.replace(/^\/[a-zA-Z]:/g, ""); + return new Promise(resolve => + fs.cp(noDriveLetterSourceFilePath, noDriveLetterTargetFilePath, { recursive: true }, err => + err ? handleCopyFileError(err) : resolve(targetFilePath) + ) + ); + } + ); + return await Promise.all(copiedFiles); + } catch (error) { + throw error; + } } export const file = { - createFilePath, - readFileSync, - copyFile, - fileExists, - isPathADirectory -}; \ No newline at end of file + createFilePath, + readFileSync, + copyFile, + fileExists, + isPathADirectory +}; diff --git a/src/shared/utils/fileLogger.ts b/src/shared/utils/fileLogger.ts index 2d5d1df..1d199d8 100644 --- a/src/shared/utils/fileLogger.ts +++ b/src/shared/utils/fileLogger.ts @@ -1,24 +1,25 @@ import winston from "winston"; const vscodeFileLogsLevel: string[] = ["info", "error"]; -const vscodeLogsPath: string = 'logs/vscode-logs'; +const vscodeLogsPath: string = "logs/vscode-logs"; function createFileLogger(logPath: string): winston.Logger { - const logFileName: string = new Date().toISOString().split(':').join('.'); - const transports: winston.transports.FileTransportInstance[] = - vscodeFileLogsLevel - .map((level: string) => new winston.transports.File({ - filename: `${logPath}/${vscodeLogsPath}/${logFileName}${level === "error" ? "-error" : ""}.log`, - level: level, - format: winston.format.combine( - winston.format.timestamp({ format: 'HH:mm:ss.SSS' }), - winston.format.simple(), - winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`) - ), - })); - return winston.createLogger({transports}); + const logFileName: string = new Date().toISOString().split(":").join("."); + const transports: winston.transports.FileTransportInstance[] = vscodeFileLogsLevel.map( + (level: string) => + new winston.transports.File({ + filename: `${logPath}/${vscodeLogsPath}/${logFileName}${level === "error" ? "-error" : ""}.log`, + level: level, + format: winston.format.combine( + winston.format.timestamp({ format: "HH:mm:ss.SSS" }), + winston.format.simple(), + winston.format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`) + ) + }) + ); + return winston.createLogger({ transports }); } export type FileLogger = winston.Logger; export const fileLogger = { - createFileLogger -}; \ No newline at end of file + createFileLogger +}; diff --git a/src/shared/utils/lib.ts b/src/shared/utils/lib.ts index d3d47cc..d94f726 100644 --- a/src/shared/utils/lib.ts +++ b/src/shared/utils/lib.ts @@ -1,88 +1,84 @@ import path from "path"; -function parseArrayJsonStringToArray(jsonStr: string): - {[key: string]: string | string[] | {[key: string]: string}} { - return JSON.parse(jsonStr); +function parseArrayJsonStringToArray(jsonStr: string): { + [key: string]: string | string[] | { [key: string]: string }; +} { + return JSON.parse(jsonStr); } function mapObject(object: string | number | object): string { - switch(typeof object){ - case "string": - return object; - case "number": - return object.toString(); - case "object": - let ret: string = ''; - for (const [key, value] of Object.entries(object)) { - ret += (`${key}: ${value}\n`); - } - return ret; - default: - return object; - } + switch (typeof object) { + case "string": + return object; + case "number": + return object.toString(); + case "object": + let ret: string = ""; + for (const [key, value] of Object.entries(object)) { + ret += `${key}: ${value}\n`; + } + return ret; + default: + return object; + } } function createFilePath(pathArray: string[]): string { - return path.join(...pathArray); + return path.join(...pathArray); } function capitalizeFirstLetter(text: string): string { - return text.charAt(0).toUpperCase() + text.slice(1); + return text.charAt(0).toUpperCase() + text.slice(1); } -function waitTime(timeInMs: number, handleFn: () => void){ - setTimeout(() => handleFn(), timeInMs); +function waitTime(timeInMs: number, handleFn: () => void) { + setTimeout(() => handleFn(), timeInMs); } function getProjectNameFromPath(projectPath: string): string { - const projectName : string | undefined = projectPath.split("/").pop(); - if(!projectName){ - throw new Error(`[lib_getProjectNameFromPath]: Failed to retrieve last folder name from path: ${projectPath}`); - } - return projectName; + const projectName: string | undefined = projectPath.split("/").pop(); + if (!projectName) { + throw new Error(`[lib_getProjectNameFromPath]: Failed to retrieve last folder name from path: ${projectPath}`); + } + return projectName; } function removeDuplicates(array: (string | number)[]): (string | number)[] { - return [...new Set(array)]; + return [...new Set(array)]; } -function removeNonValues(array: (string | number)[]): (string | number)[]{ - return array - .filter( - (value: string | number) => (value !== undefined && value !== null && value !== "") - ); +function removeNonValues(array: (string | number)[]): (string | number)[] { + return array.filter((value: string | number) => value !== undefined && value !== null && value !== ""); } 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){ - 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}`); - } - return filePathSplit.join("/"); - }); + return [files].flat().map((file: string) => { + const filePathSplit: string[] = file.split("/"); + let fileName: string | undefined = filePathSplit.pop(); + if (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}`); + } + return filePathSplit.join("/"); + }); } export const lib = { - parseArrayJsonStringToArray, - mapObject, - createFilePath, - capitalizeFirstLetter, - waitTime, - getProjectNameFromPath, - removeDuplicates, - removeNonValues, - removeExtensionFromFile -}; \ No newline at end of file + parseArrayJsonStringToArray, + mapObject, + createFilePath, + capitalizeFirstLetter, + waitTime, + getProjectNameFromPath, + removeDuplicates, + removeNonValues, + removeExtensionFromFile +}; diff --git a/src/shared/utils/terminal.ts b/src/shared/utils/terminal.ts index b7959d3..1edf3b2 100644 --- a/src/shared/utils/terminal.ts +++ b/src/shared/utils/terminal.ts @@ -1,27 +1,27 @@ -import { ChildProcess, spawn } from 'child_process'; -import TerminalCommandRunner from '../interfaces/terminalCommandRunner'; +import { ChildProcess, spawn } from "child_process"; +import TerminalCommandRunner from "../interfaces/terminalCommandRunner"; function executeTerminalCommand(commandRunner: TerminalCommandRunner): void { - const childProcess: ChildProcess = spawn( - commandRunner.command, - commandRunner.args, - { - shell: true, - cwd: commandRunner.cwd.replace(/^\/[a-zA-Z]:/g, "") - } - ); + const childProcess: ChildProcess = spawn(commandRunner.command, commandRunner.args, { + shell: true, + cwd: commandRunner.cwd.replace(/^\/[a-zA-Z]:/g, "") + }); - if(childProcess.stdout){ - childProcess.stdout.on('data', (data: Buffer) => commandRunner.handleResult(null, data.toString().trim(), null)); - } + if (childProcess.stdout) { + childProcess.stdout.on("data", (data: Buffer) => + commandRunner.handleResult(null, data.toString().trim(), null) + ); + } - if(childProcess.stderr){ - childProcess.stderr.on('data', (data: Buffer) => commandRunner.handleResult(data.toString().trim(), null, null)); - } + if (childProcess.stderr) { + childProcess.stderr.on("data", (data: Buffer) => + commandRunner.handleResult(data.toString().trim(), null, null) + ); + } - childProcess.on("exit", (code: number) => commandRunner.handleResult(null, null, code)); + childProcess.on("exit", (code: number) => commandRunner.handleResult(null, null, code)); } export const terminal = { - executeTerminalCommand -}; \ No newline at end of file + executeTerminalCommand +}; From 1b9b05c4e6342c53ee12befa9337a5453f6529cd Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Wed, 5 Jun 2024 16:40:25 +0200 Subject: [PATCH 02/14] added command palette logic --- package.json | 6 +++--- src/devtools/containers.ts | 25 +++++++++++++++++-------- src/devtools/main.ts | 1 - src/editor/containers.ts | 12 +++++++++++- src/editor/output.ts | 6 +++++- 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index e03df30..159bcc0 100644 --- a/package.json +++ b/package.json @@ -79,15 +79,15 @@ "commandPalette": [ { "command": "sfmc-devtools-vscext.devtoolsCMRetrieve", - "when": "false" + "when": "sfmc-devtools-vscode.isDevToolsProject" }, { "command": "sfmc-devtools-vscext.devtoolsCMDeploy", - "when": "false" + "when": "sfmc-devtools-vscode.isDevToolsProject" }, { "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", - "when": "false" + "when": "sfmc-devtools-vscode.isDevToolsProject" } ], "editor/title/context": [ diff --git a/src/devtools/containers.ts b/src/devtools/containers.ts index 6ec5bcb..7eff925 100644 --- a/src/devtools/containers.ts +++ b/src/devtools/containers.ts @@ -175,17 +175,26 @@ function activateContextMenuCommands() { editorCommands.registerCommand({ command, callbackAction: (file: Uri, multipleFiles: Uri[]) => { - const files: Uri[] = !Array.isArray(multipleFiles) ? [file] : multipleFiles; - if (files.length) { - const filesPath: string[] = editorWorkspace.getFilesURIPath(files); + let filesUri = []; + + // If file is undefined it could be that the command is being called from the commands palette + // else it should be the menu command + if (!file) { + // Gets the file uri that is currently open in the editor + const fileURI: Uri | undefined = editorContainers.getActiveTabFileURI(); + filesUri = fileURI ? [fileURI] : []; + } else { + filesUri = !Array.isArray(multipleFiles) ? [file] : multipleFiles; + } + + if (filesUri.length) { + // Gets the file path from the URI + const filesPath: string[] = editorWorkspace.getFilesURIPath(filesUri); const [__, key]: string[] = command.split(".devtools"); + // Executes the command return devtoolsMain.handleContextMenuActions(key, filesPath); - } else { - log( - "error", - "[container_activateContextMenuCommands] Error: Context Menu Callback didn't return any selected files." - ); } + log("warning", "Warn: No file was selected or is currently open in the editor."); } }) ); diff --git a/src/devtools/main.ts b/src/devtools/main.ts index 9279cf2..7097bab 100644 --- a/src/devtools/main.ts +++ b/src/devtools/main.ts @@ -119,7 +119,6 @@ function handleStatusBarActions(action: string): void { function handleContextMenuActions(action: string, selectedFiles: string[]): void { devtoolsContainers.modifyStatusBar("mcdev", "success"); - log("debug", "Setting Context Menu Actions..."); log("debug", `Action: ${action} Number of Selected Files: ${selectedFiles.length}`); switch (action.toLowerCase()) { diff --git a/src/editor/containers.ts b/src/editor/containers.ts index 958c5be..af6f213 100644 --- a/src/editor/containers.ts +++ b/src/editor/containers.ts @@ -1,4 +1,4 @@ -import { window, StatusBarItem, StatusBarAlignment, ThemeColor } from "vscode"; +import { window, StatusBarItem, StatusBarAlignment, ThemeColor, Uri, Tab, TabInputText } from "vscode"; function createStatusBarItem(command: string, title: string, name: string): StatusBarItem { let statusBar: StatusBarItem = window.createStatusBarItem(StatusBarAlignment.Right, 110); @@ -17,9 +17,19 @@ function getBackgroundColor(status: string) { return new ThemeColor(`statusBarItem.${status}Background`); } +function getActiveTabFileURI(): Uri | undefined { + const activeTab: Tab | undefined = window.tabGroups.activeTabGroup.activeTab; + if (activeTab && activeTab.input) { + const activeTabInput: TabInputText = activeTab.input as TabInputText; + return activeTabInput.uri; + } + return; +} + const editorContainers = { createStatusBarItem, displayStatusBarItem, + getActiveTabFileURI, getBackgroundColor }; diff --git a/src/editor/output.ts b/src/editor/output.ts index 4518d33..59bca0d 100644 --- a/src/editor/output.ts +++ b/src/editor/output.ts @@ -39,7 +39,11 @@ function log(level: keyof typeof LogLevel, output: string | number | object, log outputChannel.hide(); } - if (LogLevel[level] === LogLevel.info || LogLevel[level] === LogLevel.error) { + if ( + LogLevel[level] === LogLevel.info || + LogLevel[level] === LogLevel.error || + LogLevel[level] === LogLevel.warning + ) { outputChannel.appendLine(`${outputStr}`); } From d905487f509d65245808995c92390e02e65caf5b Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Wed, 5 Jun 2024 16:55:41 +0200 Subject: [PATCH 03/14] code improvement defining activeTab file URI --- .eslintrc.json | 2 +- src/devtools/containers.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 27f0647..45bb6b2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,7 +12,7 @@ "@typescript-eslint/naming-convention": "off", "@typescript-eslint/semi": "warn", "@typescript-eslint/camelcase": "off", - "curly": "warn", + "curly": "off", "eqeqeq": "warn", "no-throw-literal": "warn", "semi": "off" diff --git a/src/devtools/containers.ts b/src/devtools/containers.ts index 7eff925..714d5b4 100644 --- a/src/devtools/containers.ts +++ b/src/devtools/containers.ts @@ -175,21 +175,21 @@ function activateContextMenuCommands() { editorCommands.registerCommand({ command, callbackAction: (file: Uri, multipleFiles: Uri[]) => { - let filesUri = []; + let filesURI: Uri[] = []; // If file is undefined it could be that the command is being called from the commands palette // else it should be the menu command if (!file) { // Gets the file uri that is currently open in the editor const fileURI: Uri | undefined = editorContainers.getActiveTabFileURI(); - filesUri = fileURI ? [fileURI] : []; + if (fileURI) filesURI.push(fileURI); } else { - filesUri = !Array.isArray(multipleFiles) ? [file] : multipleFiles; + filesURI = !Array.isArray(multipleFiles) ? [file] : multipleFiles; } - if (filesUri.length) { + if (filesURI.length) { // Gets the file path from the URI - const filesPath: string[] = editorWorkspace.getFilesURIPath(filesUri); + const filesPath: string[] = editorWorkspace.getFilesURIPath(filesURI); const [__, key]: string[] = command.split(".devtools"); // Executes the command return devtoolsMain.handleContextMenuActions(key, filesPath); From d745d664e9ac5a8aee76cd323117b261da42df57 Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Wed, 5 Jun 2024 17:46:52 +0200 Subject: [PATCH 04/14] add configuration to display commands when right click on the file content --- package.json | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e03df30..633b6af 100644 --- a/package.json +++ b/package.json @@ -76,18 +76,21 @@ "group": "devtools" } ], - "commandPalette": [ + "editor/context": [ { + "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/", "command": "sfmc-devtools-vscext.devtoolsCMRetrieve", - "when": "false" + "group": "devtools" }, { + "when": "sfmc-devtools-vscode.isDevToolsProject && (resourcePath =~ /deploy/ || (resourcePath =~ /retrieve/ && (resourceExtname == '.json' || resourceExtname == '.html' || resourceExtname == '.sql' || resourceExtname == '.ssjs' || resourceLangId == 'markdown' || resourceLangId == 'AMPscript' || resourceLangId == 'ampscript' || resourceDirname =~ /asset\\\\[a-zA-Z]*/)))", "command": "sfmc-devtools-vscext.devtoolsCMDeploy", - "when": "false" + "group": "devtools" }, { + "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/ && resourceFilename != 'retrieve' && resourceFilename != 'deploy'", "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", - "when": "false" + "group": "devtools" } ], "editor/title/context": [ @@ -106,6 +109,20 @@ "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", "group": "devtools" } + ], + "commandPalette": [ + { + "command": "sfmc-devtools-vscext.devtoolsCMRetrieve", + "when": "false" + }, + { + "command": "sfmc-devtools-vscext.devtoolsCMDeploy", + "when": "false" + }, + { + "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", + "when": "false" + } ] }, "configuration": { From 5889ebff4cfee98a29a2f5f3137d472ced5e7021 Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Thu, 6 Jun 2024 12:38:53 +0200 Subject: [PATCH 05/14] added editorIsOpen variable to detect when a file is open in terminal --- .eslintrc.json | 2 +- package.json | 6 +++--- src/devtools/containers.ts | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 45bb6b2..27f0647 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,7 +12,7 @@ "@typescript-eslint/naming-convention": "off", "@typescript-eslint/semi": "warn", "@typescript-eslint/camelcase": "off", - "curly": "off", + "curly": "warn", "eqeqeq": "warn", "no-throw-literal": "warn", "semi": "off" diff --git a/package.json b/package.json index 159bcc0..064a74e 100644 --- a/package.json +++ b/package.json @@ -79,15 +79,15 @@ "commandPalette": [ { "command": "sfmc-devtools-vscext.devtoolsCMRetrieve", - "when": "sfmc-devtools-vscode.isDevToolsProject" + "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && resourcePath =~ /retrieve/" }, { "command": "sfmc-devtools-vscext.devtoolsCMDeploy", - "when": "sfmc-devtools-vscode.isDevToolsProject" + "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && (resourcePath =~ /deploy/ || (resourcePath =~ /retrieve/ && (resourceExtname == '.json' || resourceExtname == '.html' || resourceExtname == '.sql' || resourceExtname == '.ssjs' || resourceLangId == 'markdown' || resourceLangId == 'AMPscript' || resourceLangId == 'ampscript')))" }, { "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", - "when": "sfmc-devtools-vscode.isDevToolsProject" + "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && resourcePath =~ /retrieve/" } ], "editor/title/context": [ diff --git a/src/devtools/containers.ts b/src/devtools/containers.ts index 714d5b4..0dbd0dc 100644 --- a/src/devtools/containers.ts +++ b/src/devtools/containers.ts @@ -177,12 +177,13 @@ function activateContextMenuCommands() { callbackAction: (file: Uri, multipleFiles: Uri[]) => { let filesURI: Uri[] = []; + // Gets the file uri that is currently open in the editor + const fileURI: Uri | undefined = editorContainers.getActiveTabFileURI(); + // If file is undefined it could be that the command is being called from the commands palette // else it should be the menu command - if (!file) { - // Gets the file uri that is currently open in the editor - const fileURI: Uri | undefined = editorContainers.getActiveTabFileURI(); - if (fileURI) filesURI.push(fileURI); + if (!file && fileURI) { + filesURI.push(fileURI); } else { filesURI = !Array.isArray(multipleFiles) ? [file] : multipleFiles; } @@ -194,7 +195,6 @@ function activateContextMenuCommands() { // Executes the command return devtoolsMain.handleContextMenuActions(key, filesPath); } - log("warning", "Warn: No file was selected or is currently open in the editor."); } }) ); From 229c975a38cf983751647b4eefd03775e5963560 Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Thu, 6 Jun 2024 12:50:40 +0200 Subject: [PATCH 06/14] change command palette order in package.json --- package.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 064a74e..702fe67 100644 --- a/package.json +++ b/package.json @@ -59,35 +59,35 @@ } ], "menus": { - "explorer/context": [ + "commandPalette": [ { - "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/", "command": "sfmc-devtools-vscext.devtoolsCMRetrieve", - "group": "devtools" + "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && resourcePath =~ /retrieve/" }, { - "when": "sfmc-devtools-vscode.isDevToolsProject && (resourcePath =~ /deploy/ || (resourcePath =~ /retrieve/ && (resourceExtname == '.json' || resourceExtname == '.html' || resourceExtname == '.sql' || resourceExtname == '.ssjs' || resourceLangId == 'markdown' || resourceLangId == 'AMPscript' || resourceLangId == 'ampscript' || resourceDirname =~ /asset\\\\[a-zA-Z]*/)))", "command": "sfmc-devtools-vscext.devtoolsCMDeploy", - "group": "devtools" + "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && (resourcePath =~ /deploy/ || (resourcePath =~ /retrieve/ && (resourceExtname == '.json' || resourceExtname == '.html' || resourceExtname == '.sql' || resourceExtname == '.ssjs' || resourceLangId == 'markdown' || resourceLangId == 'AMPscript' || resourceLangId == 'ampscript')))" }, { - "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/ && resourceFilename != 'retrieve' && resourceFilename != 'deploy'", "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", - "group": "devtools" + "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && resourcePath =~ /retrieve/" } ], - "commandPalette": [ + "explorer/context": [ { + "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/", "command": "sfmc-devtools-vscext.devtoolsCMRetrieve", - "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && resourcePath =~ /retrieve/" + "group": "devtools" }, { + "when": "sfmc-devtools-vscode.isDevToolsProject && (resourcePath =~ /deploy/ || (resourcePath =~ /retrieve/ && (resourceExtname == '.json' || resourceExtname == '.html' || resourceExtname == '.sql' || resourceExtname == '.ssjs' || resourceLangId == 'markdown' || resourceLangId == 'AMPscript' || resourceLangId == 'ampscript' || resourceDirname =~ /asset\\\\[a-zA-Z]*/)))", "command": "sfmc-devtools-vscext.devtoolsCMDeploy", - "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && (resourcePath =~ /deploy/ || (resourcePath =~ /retrieve/ && (resourceExtname == '.json' || resourceExtname == '.html' || resourceExtname == '.sql' || resourceExtname == '.ssjs' || resourceLangId == 'markdown' || resourceLangId == 'AMPscript' || resourceLangId == 'ampscript')))" + "group": "devtools" }, { + "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/ && resourceFilename != 'retrieve' && resourceFilename != 'deploy'", "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", - "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && resourcePath =~ /retrieve/" + "group": "devtools" } ], "editor/title/context": [ From a8d6612f67e6154ab209a4d845da4914eef8202c Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Thu, 6 Jun 2024 13:32:17 +0200 Subject: [PATCH 07/14] fixed double commandPalette configuration --- package.json | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/package.json b/package.json index 712fa01..30389b0 100644 --- a/package.json +++ b/package.json @@ -123,20 +123,6 @@ "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", "group": "devtools" } - ], - "commandPalette": [ - { - "command": "sfmc-devtools-vscext.devtoolsCMRetrieve", - "when": "false" - }, - { - "command": "sfmc-devtools-vscext.devtoolsCMDeploy", - "when": "false" - }, - { - "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", - "when": "false" - } ] }, "configuration": { From 98bc33a6d0487cf2be967a28e0271c942cceb0ee Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Tue, 13 Aug 2024 16:38:23 +0200 Subject: [PATCH 08/14] Disable compact folder feature removed --- src/devtools/main.ts | 2 -- src/editor/dependencies.ts | 12 +----------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/devtools/main.ts b/src/devtools/main.ts index 7097bab..2e95eb7 100644 --- a/src/devtools/main.ts +++ b/src/devtools/main.ts @@ -60,8 +60,6 @@ async function handleDevToolsRequirements(/*isDevToolsProject: boolean*/): Promi } log("info", "SFMC DevTools is installed."); - // Deactivates Compact folders for command right execution - editorDependencies.deactivateCompactFolders(); // init DevTools Commands DevToolsCommands.init(); return; diff --git a/src/editor/dependencies.ts b/src/editor/dependencies.ts index e2ad812..89c3332 100644 --- a/src/editor/dependencies.ts +++ b/src/editor/dependencies.ts @@ -39,18 +39,8 @@ async function activateExtensionDependencies(dependencies: string | string[]) { } } -function deactivateCompactFolders() { - const workspaceConfiguration = editorWorkspace.handleWorkspaceConfiguration("explorer", "Workspace"); - const isCompactFoldersEnabled: boolean = Boolean(workspaceConfiguration.get("compactFolders", true)); - if (isCompactFoldersEnabled) { - // Disable Compact Folders - workspaceConfiguration.set("compactFolders", false); - } -} - const editorDependencies = { - activateExtensionDependencies, - deactivateCompactFolders + activateExtensionDependencies }; export { editorDependencies }; From fd6422da4c4e31c52e80addbce903b0f3b873fc2 Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Tue, 20 Aug 2024 18:13:59 +0200 Subject: [PATCH 09/14] removed logic of requesting user to select metadatatype for retrieve and deploy --- .../commands/DevToolsAdminCommands.ts | 12 ++----- src/devtools/commands/DevToolsCommands.ts | 31 +------------------ .../commands/DevToolsStandardCommands.ts | 20 +++--------- src/devtools/commands/commands.config.json | 8 ++--- 4 files changed, 11 insertions(+), 60 deletions(-) diff --git a/src/devtools/commands/DevToolsAdminCommands.ts b/src/devtools/commands/DevToolsAdminCommands.ts index 58f2854..1d2c98d 100644 --- a/src/devtools/commands/DevToolsAdminCommands.ts +++ b/src/devtools/commands/DevToolsAdminCommands.ts @@ -59,11 +59,7 @@ class DevToolsAdminCommands extends DevToolsCommands { } log("debug", `Init payload: ${JSON.stringify(initArgs)}`); - const commandConfigured: string | undefined = await this.configureCommandWithParameters( - config, - initArgs, - [] - ); + const commandConfigured: string | undefined = await this.configureCommandWithParameters(config, initArgs); // Checks if the command is still missing so required parameter if (this.hasPlaceholders(commandConfigured)) { log("debug", `Required Parameters missing from Init command: ${commandConfigured}`); @@ -89,11 +85,7 @@ class DevToolsAdminCommands extends DevToolsCommands { try { log("info", `Running DevTools Admin Command: Explain Types...`); if ("command" in config && config.command) { - const commandConfigured: string | undefined = await this.configureCommandWithParameters( - config, - args, - [] - ); + const commandConfigured: string | undefined = await this.configureCommandWithParameters(config, args); log("debug", `Explain types final command: ${commandConfigured}`); const commandResult: string | number = await this.executeCommand( commandConfigured, diff --git a/src/devtools/commands/DevToolsCommands.ts b/src/devtools/commands/DevToolsCommands.ts index 39824f7..a7f1d6a 100644 --- a/src/devtools/commands/DevToolsCommands.ts +++ b/src/devtools/commands/DevToolsCommands.ts @@ -44,8 +44,7 @@ abstract class DevToolsCommands { async configureCommandWithParameters( config: DevToolsCommandSetting, - args: { [key: string]: string | string[] | boolean }, - mdTypes: SupportedMetadataTypes[] + args: { [key: string]: string | string[] | boolean } ): Promise { log("debug", `ConfigureCommandWithParameters: ${JSON.stringify(config)}`); let { command } = config; @@ -54,14 +53,6 @@ abstract class DevToolsCommands { for (const param of config.requiredParams) { if (param in args && args[param]) { command = command.replace(`{{${param}}}`, args[param] as string); - } else { - // Requests user - if (param.toLowerCase() === "mdtypes" && mdTypes.length) { - const userSelecteMDTypes: string | undefined = await this.handleMetadataTypeRequest(mdTypes); - if (userSelecteMDTypes) { - command = command.replace(`{{${param}}}`, `"${userSelecteMDTypes}"`); - } - } } } } @@ -78,26 +69,6 @@ abstract class DevToolsCommands { return command; } - async handleMetadataTypeRequest(mdTypes: SupportedMetadataTypes[]): Promise { - const mdTypeInputOptions: InputOptionsSettings[] = mdTypes.map((mdType: SupportedMetadataTypes) => ({ - id: mdType.apiName, - label: mdType.name, - detail: "" - })); - const userResponse: InputOptionsSettings | InputOptionsSettings[] | undefined = - await editorInput.handleQuickPickSelection( - mdTypeInputOptions, - "Please select one or multiple metadata types...", - true - ); - if (userResponse && Array.isArray(userResponse)) { - const mdTypes: string = `${userResponse.map((response: InputOptionsSettings) => response.id)}`; - log("debug", `User selected metadata types: "${mdTypes}"`); - return mdTypes; - } - return; - } - hasPlaceholders(command: string): boolean { const pattern: RegExp = /{{.*?}}/g; return pattern.test(command); diff --git a/src/devtools/commands/DevToolsStandardCommands.ts b/src/devtools/commands/DevToolsStandardCommands.ts index 7df04cc..ccbc968 100644 --- a/src/devtools/commands/DevToolsStandardCommands.ts +++ b/src/devtools/commands/DevToolsStandardCommands.ts @@ -70,15 +70,8 @@ class DevToolsStandardCommands extends DevToolsCommands { ) { log("info", `Running DevTools Standard Command: Retrieve...`); if ("command" in config && config.command) { - // Gets that metadata types that are supported for retrieve - const supportedMdTypes: SupportedMetadataTypes[] = this.getSupportedMetadataTypeByAction("retrieve"); - // Configures the command to replace all the parameters with the values - const commandConfigured: string | undefined = await this.configureCommandWithParameters( - config, - args, - supportedMdTypes - ); + const commandConfigured: string | undefined = await this.configureCommandWithParameters(config, args); // Checks if the command is still missing so required parameter if (this.hasPlaceholders(commandConfigured)) { @@ -89,6 +82,7 @@ class DevToolsStandardCommands extends DevToolsCommands { log("debug", `Retrieve Command configured: ${commandConfigured}`); loadingNotification(); + const commandResult: string | number = await this.executeCommand(commandConfigured, path, true); if (typeof commandResult === "number") { handleCommandResult({ success: commandResult === 0, cancelled: false }); @@ -106,15 +100,8 @@ class DevToolsStandardCommands extends DevToolsCommands { ) { log("info", `Running DevTools Standard Command: Deploy...`); if ("command" in config && config.command) { - // Gets that metadata types that are supported for deploy - const supportedMdTypes: SupportedMetadataTypes[] = this.getSupportedMetadataTypeByAction("deploy"); - // Configures the command to replace all the parameters with the values - const commandConfigured: string | undefined = await this.configureCommandWithParameters( - config, - args, - supportedMdTypes - ); + const commandConfigured: string | undefined = await this.configureCommandWithParameters(config, args); // Checks if the command is still missing so required parameter if (this.hasPlaceholders(commandConfigured)) { @@ -125,6 +112,7 @@ class DevToolsStandardCommands extends DevToolsCommands { log("debug", `Deploy Command configured: ${commandConfigured}`); loadingNotification(); + const commandResult: string | number = await this.executeCommand(commandConfigured, path, true); if (typeof commandResult === "number") { handleCommandResult({ success: commandResult === 0, cancelled: false }); diff --git a/src/devtools/commands/commands.config.json b/src/devtools/commands/commands.config.json index 39abc9b..041fcee 100644 --- a/src/devtools/commands/commands.config.json +++ b/src/devtools/commands/commands.config.json @@ -41,8 +41,8 @@ "id":"retrieve", "title": "Retrieve", "command": "mcdev retrieve {{bu}} {{mdtypes}} {{key}} --skipInteraction", - "requiredParams": ["bu", "mdtypes"], - "optionalParams": ["key"], + "requiredParams": ["bu"], + "optionalParams": ["key", "mdtypes"], "description": "Retrieves metadata of a business unit.", "isAvailable": true }, @@ -50,8 +50,8 @@ "id":"deploy", "title": "Deploy", "command": "mcdev deploy {{bu}} {{mdtypes}} {{key}} {{fromRetrieve}} --skipInteraction", - "requiredParams": ["bu", "mdtypes"], - "optionalParams": ["key", "fromRetrieve"], + "requiredParams": ["bu"], + "optionalParams": ["key", "mdtypes", "fromRetrieve"], "description": "Deploys local metadata to a business unit.", "isAvailable": true }, From 7b3bf35f838abb26319701f39dae69ca8a7f981f Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Wed, 21 Aug 2024 18:05:16 +0200 Subject: [PATCH 10/14] Asset types folders are fully copied with all the files inside --- src/devtools/main.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/devtools/main.ts b/src/devtools/main.ts index 2e95eb7..fe6521c 100644 --- a/src/devtools/main.ts +++ b/src/devtools/main.ts @@ -685,21 +685,23 @@ async function handleCopyToBuCMCommand(selectedPaths: string[]) { const buSelected: string[] = buOptions.map((bu: InputOptionsSettings) => bu.id); const filePathsConfigured: FileCopyConfig[] = supportedMetadataTypes - .map((configPath: DevToolsPathConfiguration) => { - const { absolutePath, businessUnit } = configPath; - + .map(({ absolutePath, businessUnit, metadataType, keys }: DevToolsPathConfiguration) => { if (businessUnit) { let paths: string[] = []; - - if (file.isPathADirectory(absolutePath)) { - paths = [...paths, absolutePath]; + // When the selected file to copy is inside a folder in asset type + // the whole folder and all files inside should be copied + if (metadataType === "asset" && keys.length > 2) { + const [_, assetKey]: string[] = keys; + paths.push(absolutePath.split(assetKey).shift() + assetKey); + } else if (file.isPathADirectory(absolutePath)) { + paths.push(absolutePath); } else { const [currentFileExt]: string[] = mainConfig.fileExtensions.filter( (fileExt: string) => absolutePath.endsWith(fileExt) ); if (currentFileExt) { - paths = [ - ...paths, + // Copies the same file for multiple extensions, eg sql & json + paths.push( ...file.fileExists( mainConfig.fileExtensions .filter( @@ -710,7 +712,7 @@ async function handleCopyToBuCMCommand(selectedPaths: string[]) { absolutePath.replace(currentFileExt, fileExtension) ) ) - ]; + ); } } From b8aac4b94b960bfc2eb42d1c5e169c9c35644035 Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Tue, 27 Aug 2024 15:38:01 +0200 Subject: [PATCH 11/14] removed "mcdev: Copy to ..." option from menu for the Credential folder --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 30389b0..b9d8e75 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "group": "devtools" }, { - "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/ && resourceFilename != 'retrieve' && resourceFilename != 'deploy'", + "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/ && resourceFilename != 'retrieve' && resourceFilename != 'deploy' && resourcePath =~ /retrieve\\\\.*\\\\.*/", "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", "group": "devtools" } @@ -119,7 +119,7 @@ "group": "devtools" }, { - "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/ && resourceFilename != 'retrieve' && resourceFilename != 'deploy'", + "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/ && resourceFilename != 'retrieve' && resourceFilename != 'deploy' && resourcePath =~ /retrieve\\\\.*\\\\.*/", "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", "group": "devtools" } From 00f98e4433ddcaf2edee16b97fb49ed8b5ded9f8 Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Wed, 28 Aug 2024 13:00:04 +0200 Subject: [PATCH 12/14] Copy action is copying only valid mdtypes when selected on the BU folder. --- src/devtools/commands/DevToolsCommands.ts | 25 +++++++++++-------- .../commands/DevToolsStandardCommands.ts | 15 +++-------- src/devtools/main.ts | 19 +++++++++++++- src/shared/utils/file.ts | 6 +---- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/devtools/commands/DevToolsCommands.ts b/src/devtools/commands/DevToolsCommands.ts index a7f1d6a..65a1221 100644 --- a/src/devtools/commands/DevToolsCommands.ts +++ b/src/devtools/commands/DevToolsCommands.ts @@ -3,7 +3,6 @@ import * as commandsConfig from "./commands.config.json"; import DevToolsCommandSetting from "../../shared/interfaces/devToolsCommandSetting"; import DevToolsCommandRunner from "../../shared/interfaces/devToolsCommandRunner"; import SupportedMetadataTypes from "../../shared/interfaces/supportedMetadataTypes"; -import InputOptionsSettings from "../../shared/interfaces/inputOptionsSettings"; import { editorInput } from "../../editor/input"; import { log } from "../../editor/output"; import { lib } from "../../shared/utils/lib"; @@ -13,10 +12,9 @@ import { metadatatypes } from "../../config/metadatatypes.config"; abstract class DevToolsCommands { static readonly commandPrefix: string = "mcdev"; static commandMap: { [key: string]: DevToolsCommands }; + static metadataTypes: SupportedMetadataTypes[]; abstract run(commandRunner: DevToolsCommandRunner): void; - abstract setMetadataTypes(mdTypes: SupportedMetadataTypes[]): void; - abstract getMetadataTypes(): SupportedMetadataTypes[] | void; abstract isSupportedMetadataType(action: string, metadataType: string): boolean | void; executeCommand(command: string, path: string, showOnTerminal: boolean): Promise { @@ -101,14 +99,11 @@ abstract class DevToolsCommands { }, {}); } - // Sends the supported mtdata types to each DevTools Command - Object.keys(this.commandMap).forEach((key: string) => { - const devToolCommand: DevToolsCommands = this.commandMap[key]; - const sortedSuppMdtByName: SupportedMetadataTypes[] = metadatatypes.sort((a, b) => - a.name.localeCompare(b.name) - ); - devToolCommand.setMetadataTypes(sortedSuppMdtByName); - }); + // Sets the metadata types sorted by name + const sortedSuppMdtByName: SupportedMetadataTypes[] = metadatatypes.sort((a, b) => + a.name.localeCompare(b.name) + ); + this.setMetadataTypes(sortedSuppMdtByName); } static async runCommand( @@ -190,6 +185,14 @@ abstract class DevToolsCommands { return false; } + static setMetadataTypes(mdTypes: SupportedMetadataTypes[]): void { + this.metadataTypes = mdTypes; + } + + static getMetadataTypes(): SupportedMetadataTypes[] { + return this.metadataTypes; + } + static isSupportedMetadataType(action: string, metadataType: string) { if ("standard" in this.commandMap) { const devToolsCommand: DevToolsCommands = this.commandMap["standard"]; diff --git a/src/devtools/commands/DevToolsStandardCommands.ts b/src/devtools/commands/DevToolsStandardCommands.ts index ccbc968..6d40682 100644 --- a/src/devtools/commands/DevToolsStandardCommands.ts +++ b/src/devtools/commands/DevToolsStandardCommands.ts @@ -13,7 +13,6 @@ class DevToolsStandardCommands extends DevToolsCommands { commandHandlers: { [key: string]: (args?: any) => void } ) => void; } = {}; - private metadataTypes: SupportedMetadataTypes[] = []; constructor() { super(); log("debug", "DevToolsStandardCommands Class created"); @@ -35,20 +34,14 @@ class DevToolsStandardCommands extends DevToolsCommands { } } - setMetadataTypes(mdTypes: SupportedMetadataTypes[]): void { - this.metadataTypes = mdTypes; - } - - getMetadataTypes(): SupportedMetadataTypes[] { - return this.metadataTypes; - } - getSupportedMetadataTypeByAction(action: string) { const supportedActions: { [key: string]: () => SupportedMetadataTypes[] } = { retrieve: () => - this.getMetadataTypes().filter((mdType: SupportedMetadataTypes) => mdType.supports.retrieve), + DevToolsCommands.getMetadataTypes().filter( + (mdType: SupportedMetadataTypes) => mdType.supports.retrieve + ), deploy: () => - this.getMetadataTypes().filter( + DevToolsCommands.getMetadataTypes().filter( (mdType: SupportedMetadataTypes) => mdType.supports.create || mdType.supports.update ) }; diff --git a/src/devtools/main.ts b/src/devtools/main.ts index fe6521c..4f2e679 100644 --- a/src/devtools/main.ts +++ b/src/devtools/main.ts @@ -15,6 +15,7 @@ import DevToolsPathComponents from "../shared/interfaces/devToolsPathComponents" import { lib } from "../shared/utils/lib"; import { file } from "../shared/utils/file"; import { editorCommands } from "../editor/commands"; +import SupportedMetadataTypes from "../shared/interfaces/supportedMetadataTypes"; async function initDevToolsExtension(): Promise { try { @@ -622,7 +623,23 @@ async function handleCopyToBuCMCommand(selectedPaths: string[]) { const { supportedMetadataTypes, unsupportedMetadataTypes }: SupportedMetadataTypeConfiguration = configuredSelectedPaths.reduce( (accObj: SupportedMetadataTypeConfiguration, configPath: DevToolsPathConfiguration) => { - if (DevToolsCommands.isSupportedMetadataType("deploy", configPath.metadataType)) { + if (!configPath.metadataType) { + // Gets all the metadata types that are supported for deployment + const allDeployMetadataTypes: SupportedMetadataTypes[] = + DevToolsCommands.getMetadataTypes().filter((mdType: SupportedMetadataTypes) => + DevToolsCommands.isSupportedMetadataType("deploy", mdType.apiName) + ); + // Configures and adds all the metadata types that exist in the BU folder + accObj.supportedMetadataTypes = allDeployMetadataTypes + .map((mdType: SupportedMetadataTypes) => ({ + ...configPath, + absolutePath: `${configPath.absolutePath}/${mdType.apiName}`, + metadataType: mdType.apiName + })) + .filter((pathConfig: DevToolsPathConfiguration) => + file.isPathADirectory(pathConfig.absolutePath) + ); + } else if (DevToolsCommands.isSupportedMetadataType("deploy", configPath.metadataType)) { accObj.supportedMetadataTypes.push(configPath); } else { accObj.unsupportedMetadataTypes = lib.removeDuplicates([ diff --git a/src/shared/utils/file.ts b/src/shared/utils/file.ts index ae2a5e0..0ea1bcb 100644 --- a/src/shared/utils/file.ts +++ b/src/shared/utils/file.ts @@ -18,11 +18,7 @@ function fileExists(path: string | string[]): string[] { } function isPathADirectory(path: string): boolean { - try { - return fs.lstatSync(path.replace(/^\/[a-zA-Z]:/g, "")).isDirectory(); - } catch (error) { - throw error; - } + return fileExists(path).length > 0 && fs.lstatSync(path.replace(/^\/[a-zA-Z]:/g, "")).isDirectory(); } function createFilePath(pathArray: string[]): string { From 8ee8c029516feaa19bac56c08fc4a2096f5ec743 Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Wed, 28 Aug 2024 14:11:15 +0200 Subject: [PATCH 13/14] mcdev: Copy... allows to copy to same business unit --- src/devtools/main.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/devtools/main.ts b/src/devtools/main.ts index 4f2e679..51e1dca 100644 --- a/src/devtools/main.ts +++ b/src/devtools/main.ts @@ -734,7 +734,6 @@ async function handleCopyToBuCMCommand(selectedPaths: string[]) { } return buSelected - .filter((buSelected: string) => buSelected !== businessUnit) .map((buSelected: string) => paths.map((keyFilePath: string) => ({ sourceFilePath: keyFilePath, From 6f0a8196d38207d41738ffeb69d2324addcd755d Mon Sep 17 00:00:00 2001 From: Ana Silva Date: Wed, 28 Aug 2024 16:19:33 +0200 Subject: [PATCH 14/14] Removed ununecessary conditions for mcdev Copy in package.json --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b9d8e75..ad18861 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ }, { "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", - "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && resourcePath =~ /retrieve/" + "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && resourcePath =~ /\\\\retrieve\\\\.*\\\\.*/" } ], "explorer/context": [ @@ -85,7 +85,7 @@ "group": "devtools" }, { - "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/ && resourceFilename != 'retrieve' && resourceFilename != 'deploy' && resourcePath =~ /retrieve\\\\.*\\\\.*/", + "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /\\\\retrieve\\\\.*\\\\.*/", "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", "group": "devtools" } @@ -102,7 +102,7 @@ "group": "devtools" }, { - "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/ && resourceFilename != 'retrieve' && resourceFilename != 'deploy'", + "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /\\\\retrieve\\\\.*\\\\.*/", "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", "group": "devtools" } @@ -119,7 +119,7 @@ "group": "devtools" }, { - "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/ && resourceFilename != 'retrieve' && resourceFilename != 'deploy' && resourcePath =~ /retrieve\\\\.*\\\\.*/", + "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /\\\\retrieve\\\\.*\\\\.*/", "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", "group": "devtools" }