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/package-lock.json b/package-lock.json index 3f3f704..dd095f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,25 +9,25 @@ "version": "1.0.2", "license": "MIT", "dependencies": { - "@salesforce-ux/design-system": "^2.23.2", - "@vscode/codicons": "^0.0.35", + "@salesforce-ux/design-system": "^2.24.5", + "@vscode/codicons": "^0.0.36", "winston": "^3.13.0" }, "devDependencies": { "@types/chai": "^4.3.16", "@types/jest": "^29.5.11", "@types/node": "^20.12.8", - "@types/vscode": "^1.81.1", - "@typescript-eslint/eslint-plugin": "^7.8.0", - "@typescript-eslint/parser": "^7.7.0", - "@vscode/test-electron": "^2.3.9", + "@types/vscode": "^1.92.0", + "@typescript-eslint/eslint-plugin": "^8.3.0", + "@typescript-eslint/parser": "^7.18.0", + "@vscode/test-electron": "^2.4.1", "chai": "^5.1.0", "eslint": "^8.56.0", "jest": "^29.7.0", - "ts-jest": "^29.1.2", + "ts-jest": "^29.2.4", "ts-loader": "^9.5.1", - "typescript": "^5.3.3", - "webpack": "^5.91.0", + "typescript": "^5.5.4", + "webpack": "^5.94.0", "webpack-cli": "^5.1.4" }, "engines": { @@ -1325,9 +1325,9 @@ } }, "node_modules/@salesforce-ux/design-system": { - "version": "2.24.2", - "resolved": "https://registry.npmjs.org/@salesforce-ux/design-system/-/design-system-2.24.2.tgz", - "integrity": "sha512-EcAeoa705VTz6rve5Dm84Oh6ACGL44FUtEj/nhY9EshPmHy4L4I3E05XNG4Y7svOzMn/uV0Vn7qLU4vXaq+2FQ==", + "version": "2.24.5", + "resolved": "https://registry.npmjs.org/@salesforce-ux/design-system/-/design-system-2.24.5.tgz", + "integrity": "sha512-dyZU4BoeDiqBhOmsQnoOJd16eJCjuTlHtDsfciMqt4i9LVgk7bBRuL72M+xZxAvmUA8ZUcQZHHeg2l90wNE+FQ==", "peerDependencies": { "postcss": "^8.3.5" } @@ -1380,15 +1380,6 @@ "node": ">=14.16" } }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1436,26 +1427,6 @@ "integrity": "sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==", "dev": true }, - "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -1538,9 +1509,9 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" }, "node_modules/@types/vscode": { - "version": "1.89.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.89.0.tgz", - "integrity": "sha512-TMfGKLSVxfGfoO8JfIE/neZqv7QLwS4nwPwL/NwMvxtAY2230H2I4Z5xx6836pmJvMAzqooRQ4pmLm7RUicP3A==", + "version": "1.92.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.92.0.tgz", + "integrity": "sha512-DcZoCj17RXlzB4XJ7IfKdPTcTGDLYvTOcTNkvtjXWF+K2TlKzHHkBEXNWQRpBIXixNEUgx39cQeTFunY0E2msw==", "dev": true }, "node_modules/@types/yargs": { @@ -1559,31 +1530,31 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.10.0.tgz", - "integrity": "sha512-PzCr+a/KAef5ZawX7nbyNwBDtM1HdLIT53aSA2DDlxmxMngZ43O8SIePOeX8H5S+FHXeI6t97mTt/dDdzY4Fyw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.3.0.tgz", + "integrity": "sha512-FLAIn63G5KH+adZosDYiutqkOkYEx0nvcwNNfJAf+c7Ae/H35qWwTYvPZUKFj5AS+WfHG/WJJfWnDnyNUlp8UA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.10.0", - "@typescript-eslint/type-utils": "7.10.0", - "@typescript-eslint/utils": "7.10.0", - "@typescript-eslint/visitor-keys": "7.10.0", + "@typescript-eslint/scope-manager": "8.3.0", + "@typescript-eslint/type-utils": "8.3.0", + "@typescript-eslint/utils": "8.3.0", + "@typescript-eslint/visitor-keys": "8.3.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" }, "peerDependenciesMeta": { "typescript": { @@ -1592,15 +1563,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.10.0.tgz", - "integrity": "sha512-2EjZMA0LUW5V5tGQiaa2Gys+nKdfrn2xiTIBLR4fxmPmVSvgPcKNW+AE/ln9k0A4zDUti0J/GZXMDupQoI+e1w==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.10.0", - "@typescript-eslint/types": "7.10.0", - "@typescript-eslint/typescript-estree": "7.10.0", - "@typescript-eslint/visitor-keys": "7.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "engines": { @@ -1619,14 +1590,14 @@ } } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.10.0.tgz", - "integrity": "sha512-7L01/K8W/VGl7noe2mgH0K7BE29Sq6KAbVmxurj8GGaPDZXPr8EEQ2seOeAS+mEV9DnzxBQB6ax6qQQ5C6P4xg==", + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.10.0", - "@typescript-eslint/visitor-keys": "7.10.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1636,15 +1607,32 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.10.0.tgz", - "integrity": "sha512-D7tS4WDkJWrVkuzgm90qYw9RdgBcrWmbbRkrLA4d7Pg3w0ttVGDsvYGV19SH8gPR5L7OtcN5J1hTtyenO9xE9g==", + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.10.0", - "@typescript-eslint/utils": "7.10.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", "ts-api-utils": "^1.3.0" }, "engines": { @@ -1654,8 +1642,63 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "eslint": "^8.56.0" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.3.0.tgz", + "integrity": "sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.3.0", + "@typescript-eslint/visitor-keys": "8.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.3.0.tgz", + "integrity": "sha512-wrV6qh//nLbfXZQoj32EXKmwHf4b7L+xXLrP3FZ0GOUU72gSvLjeWUl5J5Ue5IwRxIV1TfF73j/eaBapxx99Lg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.3.0", + "@typescript-eslint/utils": "8.3.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependenciesMeta": { "typescript": { @@ -1664,12 +1707,12 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.10.0.tgz", - "integrity": "sha512-7fNj+Ya35aNyhuqrA1E/VayQX9Elwr8NKZ4WueClR3KwJ7Xx9jcCdOrLW04h51de/+gNbyFMs+IDxh5xIwfbNg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.3.0.tgz", + "integrity": "sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw==", "dev": true, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -1677,22 +1720,22 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.10.0.tgz", - "integrity": "sha512-LXFnQJjL9XIcxeVfqmNj60YhatpRLt6UhdlFwAkjNc6jSUlK8zQOl1oktAP8PlWFzPQC1jny/8Bai3/HPuvN5g==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.3.0.tgz", + "integrity": "sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.10.0", - "@typescript-eslint/visitor-keys": "7.10.0", + "@typescript-eslint/types": "8.3.0", + "@typescript-eslint/visitor-keys": "8.3.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -1705,38 +1748,38 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.10.0.tgz", - "integrity": "sha512-olzif1Fuo8R8m/qKkzJqT7qwy16CzPRWBvERS0uvyc+DHd8AKbO4Jb7kpAvVzMmZm8TrHnI7hvjN4I05zow+tg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.3.0.tgz", + "integrity": "sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.10.0", - "@typescript-eslint/types": "7.10.0", - "@typescript-eslint/typescript-estree": "7.10.0" + "@typescript-eslint/scope-manager": "8.3.0", + "@typescript-eslint/types": "8.3.0", + "@typescript-eslint/typescript-estree": "8.3.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.10.0.tgz", - "integrity": "sha512-9ntIVgsi6gg6FIq9xjEO4VQJvwOqA3jaBFQJ/6TK5AvEup2+cECI6Fh7QiBxmfMHXU0V0J4RyPeOU1VDNzl9cg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.3.0.tgz", + "integrity": "sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.10.0", + "@typescript-eslint/types": "8.3.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -1750,23 +1793,168 @@ "dev": true }, "node_modules/@vscode/codicons": { - "version": "0.0.35", - "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.35.tgz", - "integrity": "sha512-7iiKdA5wHVYSbO7/Mm0hiHD3i4h+9hKUe1O4hISAe/nHhagMwb2ZbFC8jU6d7Cw+JNT2dWXN2j+WHbkhT5/l2w==" + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.36.tgz", + "integrity": "sha512-wsNOvNMMJ2BY8rC2N2MNBG7yOowV3ov8KlvUE/AiVUlHKTfWsw3OgAOQduX7h0Un6GssKD3aoTVH+TF3DSQwKQ==" }, "node_modules/@vscode/test-electron": { - "version": "2.3.10", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.3.10.tgz", - "integrity": "sha512-FxMqrvUm6a8S5tP4CymNJ40e6kD+wUTWTc6K32U629yrCCa+kl/rmpkC2gKpN4F4zjg1r+0Hnk9sl0+N2atsYA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", + "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==", "dev": true, "dependencies": { - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.5", "jszip": "^3.10.1", - "semver": "^7.5.2" + "ora": "^7.0.1", + "semver": "^7.6.2" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@vscode/test-electron/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@vscode/test-electron/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@vscode/test-electron/node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true + }, + "node_modules/@vscode/test-electron/node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dev": true, + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/ora": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", + "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", + "dev": true, + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.9.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.3.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "string-width": "^6.1.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/string-width": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", + "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^10.2.1", + "strip-ansi": "^7.0.1" }, "engines": { "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@vscode/test-electron/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/@webassemblyjs/ast": { @@ -1983,10 +2171,10 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "peerDependencies": { "acorn": "^8" @@ -2002,15 +2190,15 @@ } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { - "debug": "4" + "debug": "^4.3.4" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { @@ -2393,8 +2581,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "peer": true + ] }, "node_modules/beauty-amp-core2": { "version": "0.4.5", @@ -2904,7 +3091,6 @@ "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "peer": true, "engines": { "node": ">=6" }, @@ -3603,8 +3789,7 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "peer": true + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/easy-table": { "version": "1.1.0", @@ -3615,6 +3800,21 @@ "wcwidth": ">=1.0.1" } }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.777", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.777.tgz", @@ -3644,9 +3844,9 @@ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, "node_modules/enhanced-resolve": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", - "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -4258,6 +4458,27 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4812,17 +5033,16 @@ "peer": true }, "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/http2-wrapper": { @@ -4839,16 +5059,16 @@ } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "dependencies": { - "agent-base": "6", + "agent-base": "^7.0.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-signals": { @@ -4889,8 +5109,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "peer": true + ] }, "node_modules/ignore": { "version": "5.3.1", @@ -5437,7 +5656,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "peer": true, "engines": { "node": ">=12" }, @@ -5581,6 +5799,46 @@ "node": ">=8" } }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -8143,6 +8401,70 @@ "node": ">=8" } }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dev": true, + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stdin-discarder/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/stdin-discarder/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/stdin-discarder/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", @@ -8550,12 +8872,13 @@ } }, "node_modules/ts-jest": { - "version": "29.1.3", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.3.tgz", - "integrity": "sha512-6L9qz3ginTd1NKhOxmkP0qU3FyKjj5CPoY+anszfVn6Pmv/RIKzhiMCsH7Yb7UvJR9I2A64rm4zQl531s2F1iw==", + "version": "29.2.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.4.tgz", + "integrity": "sha512-3d6tgDyhCI29HlpwIq87sNuI+3Q6GLTTCeYRHCs7vDz+/3GCMwEtV9jezLyl4ZtnBgx00I7hm8PCP8cTksMGrw==", "dev": true, "dependencies": { "bs-logger": "0.x", + "ejs": "^3.1.10", "fast-json-stable-stringify": "2.x", "jest-util": "^29.0.0", "json5": "^2.2.3", @@ -8767,9 +9090,9 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -8965,21 +9288,20 @@ } }, "node_modules/webpack": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", diff --git a/package.json b/package.json index e03df30..e9e8286 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sfmc-devtools-vscode", "displayName": "SFMC DevTools", - "description": "Unofficial IDE for Salesforce Marketing Cloud", + "description": "Unofficial IDE for Salesforce Marketing Cloud - Handle and manipulate several SFMC assets (journeys, automations, queries, SSJS, AMPScript, etc..) between your local machine and Salesforce Marketing Cloud (SFMC).", "version": "1.0.2", "license": "MIT", "publisher": "Accenture-oss", @@ -59,6 +59,20 @@ } ], "menus": { + "commandPalette": [ + { + "command": "sfmc-devtools-vscext.devtoolsCMRetrieve", + "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && resourcePath =~ /retrieve/" + }, + { + "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')))" + }, + { + "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", + "when": "sfmc-devtools-vscode.isDevToolsProject && editorIsOpen && resourcePath =~ /\\\\retrieve\\\\.*\\\\.*/" + } + ], "explorer/context": [ { "when": "sfmc-devtools-vscode.isDevToolsProject && resourcePath =~ /retrieve/", @@ -71,23 +85,26 @@ "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" } ], - "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\\\\.*\\\\.*/", "command": "sfmc-devtools-vscext.devtoolsCMCopyToBU", - "when": "false" + "group": "devtools" } ], "editor/title/context": [ @@ -102,7 +119,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" } @@ -133,22 +150,22 @@ "@types/chai": "^4.3.16", "@types/jest": "^29.5.11", "@types/node": "^20.12.8", - "@types/vscode": "^1.81.1", - "@typescript-eslint/eslint-plugin": "^7.8.0", - "@typescript-eslint/parser": "^7.7.0", - "@vscode/test-electron": "^2.3.9", + "@types/vscode": "^1.92.0", + "@typescript-eslint/eslint-plugin": "^8.3.0", + "@typescript-eslint/parser": "^7.18.0", + "@vscode/test-electron": "^2.4.1", "chai": "^5.1.0", "eslint": "^8.56.0", "jest": "^29.7.0", - "ts-jest": "^29.1.2", + "ts-jest": "^29.2.4", "ts-loader": "^9.5.1", - "typescript": "^5.3.3", - "webpack": "^5.91.0", + "typescript": "^5.5.4", + "webpack": "^5.94.0", "webpack-cli": "^5.1.4" }, "dependencies": { - "@salesforce-ux/design-system": "^2.23.2", - "@vscode/codicons": "^0.0.35", + "@salesforce-ux/design-system": "^2.24.5", + "@vscode/codicons": "^0.0.36", "winston": "^3.13.0" } } 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..b38a381 100644 --- a/src/config/metadatatypes.config.ts +++ b/src/config/metadatatypes.config.ts @@ -1,535 +1,534 @@ -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 +import SupportedMetadataTypes from "../shared/interfaces/supportedMetadataTypes"; + +export const metadatatypes: SupportedMetadataTypes[] = [ + { + 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..d482f7c 100644 --- a/src/devtools/commands/DevToolsAdminCommands.ts +++ b/src/devtools/commands/DevToolsAdminCommands.ts @@ -1,123 +1,104 @@ import DevToolsCommands from "./DevToolsCommands"; import DevToolsCommandSetting from "../../shared/interfaces/devToolsCommandSetting"; import DevToolsCommandRunner from "../../shared/interfaces/devToolsCommandRunner"; -import SupportedMetadataTypes from "../../shared/interfaces/supportedMetadataTypes"; 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.`); - } - } + 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..34e9b9c 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"; @@ -11,229 +10,212 @@ 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 }; + static metadataTypes: SupportedMetadataTypes[]; + + abstract run(commandRunner: DevToolsCommandRunner): 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 } + ): 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); + } + } + } + // 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; + } + + 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 }; + } + }, {}); + } + + // sets default metadata type file + this.setMetadataTypes(metadatatypes); + + // Updates metadatypes with the result from explainTypes command, not blocking code execution + this.runCommand( + "admin", + "etypes", + "", + { json: true }, + { + handleCommandResult: ({ success, data }: { success: boolean; data: string }) => { + if (success) { + this.setMetadataTypes(JSON.parse(data) as SupportedMetadataTypes[]); + } + } + } + ); + } + + 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 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"]; + 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..6d40682 100644 --- a/src/devtools/commands/DevToolsStandardCommands.ts +++ b/src/devtools/commands/DevToolsStandardCommands.ts @@ -5,145 +5,120 @@ 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; + } = {}; + 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.`); + } + } + + getSupportedMetadataTypeByAction(action: string) { + const supportedActions: { [key: string]: () => SupportedMetadataTypes[] } = { + retrieve: () => + DevToolsCommands.getMetadataTypes().filter( + (mdType: SupportedMetadataTypes) => mdType.supports.retrieve + ), + deploy: () => + DevToolsCommands.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) { + // Configures the command to replace all the parameters with the values + const commandConfigured: string | undefined = await this.configureCommandWithParameters(config, args); + + // 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) { + // Configures the command to replace all the parameters with the values + const commandConfigured: string | undefined = await this.configureCommandWithParameters(config, args); + + // 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/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 }, diff --git a/src/devtools/containers.ts b/src/devtools/containers.ts index 0b26fda..0dbd0dc 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,70 @@ 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[]) => { + 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 && fileURI) { + filesURI.push(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); + } + } + }) + ); } 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..51e1dca 100644 --- a/src/devtools/main.ts +++ b/src/devtools/main.ts @@ -15,746 +15,777 @@ import DevToolsPathComponents from "../shared/interfaces/devToolsPathComponents" 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}`); - } +import SupportedMetadataTypes from "../shared/interfaces/supportedMetadataTypes"; + +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."); + + // 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 (!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([ + ...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(({ absolutePath, businessUnit, metadataType, keys }: DevToolsPathConfiguration) => { + if (businessUnit) { + let paths: string[] = []; + // 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) { + // Copies the same file for multiple extensions, eg sql & json + paths.push( + ...file.fileExists( + mainConfig.fileExtensions + .filter( + (fileExtension: string) => + !mainConfig.noCopyFileExtensions.includes(fileExtension) + ) + .map((fileExtension: string) => + absolutePath.replace(currentFileExt, fileExtension) + ) + ) + ); + } + } + + return buSelected + .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..af6f213 100644 --- a/src/editor/containers.ts +++ b/src/editor/containers.ts @@ -1,28 +1,36 @@ -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); - 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`); +} + +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, - getBackgroundColor + createStatusBarItem, + displayStatusBarItem, + getActiveTabFileURI, + 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..89c3332 100644 --- a/src/editor/dependencies.ts +++ b/src/editor/dependencies.ts @@ -4,56 +4,43 @@ 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)); - - 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); - } - } - } - } + INSTALL = "Install", + NOT_NOW = "Not Now", + DONT_ASK_AGAIN = "Do not show again" } -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); - } +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); + } + } + } + } } const editorDependencies = { - activateExtensionDependencies, - deactivateCompactFolders + activateExtensionDependencies }; -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..59bca0d 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,47 @@ 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 || + LogLevel[level] === LogLevel.warning + ) { + 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..0ea1bcb 100644 --- a/src/shared/utils/file.ts +++ b/src/shared/utils/file.ts @@ -1,58 +1,56 @@ 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; - } + return fileExists(path).length > 0 && fs.lstatSync(path.replace(/^\/[a-zA-Z]:/g, "")).isDirectory(); } 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 +};