diff --git a/package-lock.json b/package-lock.json index b98674a..9c20c27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "cross-fetch": "^3.1.5", "outscale-api": "^0.11.0", "rxjs": "^7.5.7", + "shelljs": "^0.8.5", "true-myth": "^6.2.0" }, "devDependencies": { @@ -20,6 +21,7 @@ "@types/glob": "^7.2.0", "@types/mocha": "^9.1.1", "@types/node": "14.x", + "@types/shelljs": "^0.8.11", "@types/sinon": "^10.0.13", "@types/vscode": "^1.73.0", "@typescript-eslint/eslint-plugin": "^5.21.0", @@ -303,6 +305,16 @@ "@types/ws": "*" } }, + "node_modules/@types/shelljs": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.15.tgz", + "integrity": "sha512-vzmnCHl6hViPu9GNLQJ+DZFd6BQI2DBTUeOvYHqkWQLMfKAAQYMb/xAmZkTogZI/vqXHCWkqDRymDI5p0QTi5Q==", + "dev": true, + "dependencies": { + "@types/glob": "~7.2.0", + "@types/node": "*" + } + }, "node_modules/@types/sinon": { "version": "10.0.13", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", @@ -901,8 +913,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -993,7 +1004,6 @@ "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" @@ -1394,8 +1404,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/core-util-is": { "version": "1.0.3", @@ -2276,8 +2285,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fstream": { "version": "1.0.12", @@ -2327,10 +2335,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -2566,6 +2576,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -2725,7 +2746,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2734,8 +2754,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "1.3.8", @@ -2743,6 +2762,14 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2767,6 +2794,17 @@ "is-ci": "bin.js" } }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -3191,7 +3229,6 @@ "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" }, @@ -3529,7 +3566,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -3702,7 +3738,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -3716,6 +3751,11 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, "node_modules/path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -4014,6 +4054,17 @@ "node": ">=8.10.0" } }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -4035,6 +4086,22 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -4250,6 +4317,41 @@ "node": ">=8" } }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/shelljs/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -4422,6 +4524,17 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -5059,8 +5172,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/ws": { "version": "8.12.0", @@ -5422,6 +5534,16 @@ "@types/ws": "*" } }, + "@types/shelljs": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.15.tgz", + "integrity": "sha512-vzmnCHl6hViPu9GNLQJ+DZFd6BQI2DBTUeOvYHqkWQLMfKAAQYMb/xAmZkTogZI/vqXHCWkqDRymDI5p0QTi5Q==", + "dev": true, + "requires": { + "@types/glob": "~7.2.0", + "@types/node": "*" + } + }, "@types/sinon": { "version": "10.0.13", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", @@ -5838,8 +5960,7 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base64-js": { "version": "1.5.1", @@ -5909,7 +6030,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6217,8 +6337,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "core-util-is": { "version": "1.0.3", @@ -6880,8 +6999,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fstream": { "version": "1.0.12", @@ -6921,10 +7039,9 @@ } }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -7099,6 +7216,14 @@ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "requires": { + "function-bind": "^1.1.2" + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -7207,7 +7332,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -7216,8 +7340,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", @@ -7225,6 +7348,11 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -7243,6 +7371,14 @@ "ci-info": "^2.0.0" } }, + "is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "requires": { + "hasown": "^2.0.0" + } + }, "is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -7570,7 +7706,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -7829,7 +7964,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -7961,8 +8095,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", @@ -7970,6 +8103,11 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, "path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -8202,6 +8340,14 @@ "picomatch": "^2.2.1" } }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "requires": { + "resolve": "^1.1.6" + } + }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -8214,6 +8360,16 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, "resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -8363,6 +8519,31 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -8481,6 +8662,11 @@ "has-flag": "^4.0.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, "tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -9003,8 +9189,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "ws": { "version": "8.12.0", diff --git a/package.json b/package.json index 60e4ef4..5c9dccb 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,16 @@ "type": "object", "default": {}, "description": "%osc-viewer.filters%" + }, + "osc-viewer.costEstimation.enabled": { + "type": "boolean", + "default": false, + "description": "%osc-viewer.costEstimation.enabled%" + }, + "osc-viewer.costEstimation.oscCostPath": { + "type": "string", + "default": "", + "description": "%osc-viewer.costEstimation.oscCostPath%" } } }, @@ -358,8 +368,8 @@ }, "scripts": { "vscode:prepublish": "npm run ${MOCK}esbuild-base -- --minify", - "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node", - "mock-esbuild-base": "esbuild ./src/mockExtension.ts --bundle --outfile=out/main.js --external:vscode --format=cjs --platform=node", + "esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=out/main.js --external:vscode --external:shelljs --format=cjs --platform=node", + "mock-esbuild-base": "esbuild ./src/mockExtension.ts --bundle --outfile=out/main.js --external:vscode --external:shelljs --format=cjs --platform=node", "esbuild": "npm run esbuild-base -- --sourcemap", "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch", "docker-build-ui-test": "docker build -f src/ui-test/Dockerfile -t vscode-osc-viewer-test:latest .", @@ -379,6 +389,7 @@ "@types/glob": "^7.2.0", "@types/mocha": "^9.1.1", "@types/node": "14.x", + "@types/shelljs": "^0.8.11", "@types/sinon": "^10.0.13", "@types/vscode": "^1.73.0", "@typescript-eslint/eslint-plugin": "^5.21.0", @@ -405,6 +416,7 @@ "cross-fetch": "^3.1.5", "outscale-api": "^0.11.0", "rxjs": "^7.5.7", + "shelljs": "^0.8.5", "true-myth": "^6.2.0" } } \ No newline at end of file diff --git a/src/components/osc_cost.ts b/src/components/osc_cost.ts new file mode 100644 index 0000000..9ce4856 --- /dev/null +++ b/src/components/osc_cost.ts @@ -0,0 +1,313 @@ +import * as vscode from 'vscode'; +import { getConfigurationParameter, OSC_COST_PARAMETER } from "../configuration/utils"; +import { pathExists } from "../config_file/utils"; +import { ACCESSKEY_FOLDER_NAME } from "../flat/folders/simple/node.folder.accesskey"; +import { APIACCESSRULES_FOLDER_NAME } from "../flat/folders/simple/node.folder.apiaccessrule"; +import { CA_FOLDER_NAME } from "../flat/folders/simple/node.folder.ca"; +import { CLIENTGATEWAYS_FOLDER_NAME } from "../flat/folders/simple/node.folder.clientgateway"; +import { DHCPOPTIONS_FOLDER_NAME } from "../flat/folders/simple/node.folder.dhcpoption"; +import { DIRECTLINKS_FOLDER_NAME } from "../flat/folders/simple/node.folder.directlink"; +import { DIRECTLINKINTERFACES_FOLDER_NAME } from "../flat/folders/simple/node.folder.directlinkinterface"; +import { IMAGES_FOLDER_NAME } from "../flat/folders/simple/node.folder.image"; +import { KEYPAIRS_FOLDER_NAME } from "../flat/folders/simple/node.folder.keypair"; +import { LOADBALANCER_FOLDER_NAME } from "../flat/folders/simple/node.folder.loadbalancer"; +import { NATSERVICES_FOLDER_NAME } from "../flat/folders/simple/node.folder.natservice"; +import { NETACCESSPOINTS_FOLDER_NAME } from "../flat/folders/simple/node.folder.netaccesspoint"; +import { NETPEERINGS_FOLDER_NAME } from "../flat/folders/simple/node.folder.netpeering"; +import { SNAPSHOTS_FOLDER_NAME } from "../flat/folders/simple/node.folder.snapshot"; +import { SUBNETS_FOLDER_NAME } from "../flat/folders/simple/node.folder.subnet"; +import { VPNCONNECTIONS_FOLDER_NAME } from "../flat/folders/simple/node.folder.vpnconnection"; +import { FLEXIBLEGPUS_FOLDER_NAME } from "../flat/folders/specific/node.folder.flexiblegpu"; +import { INTERNETSERVICES_FOLDER_NAME } from "../flat/folders/specific/node.folder.internetservice"; +import { NET_FOLDER_NAME } from "../flat/folders/specific/node.folder.net"; +import { NICS_FOLDER_NAME } from "../flat/folders/specific/node.folder.nic"; +import { PUBLICIP_FOLDER_NAME } from "../flat/folders/specific/node.folder.publicip"; +import { ROUTETABLES_FOLDER_NAME } from "../flat/folders/specific/node.folder.routetable"; +import { SECURITYGROUPS_FOLDER_NAME } from "../flat/folders/specific/node.folder.securitygroup"; +import { VIRTUALGATEWAYS_FOLDER_NAME } from "../flat/folders/specific/node.folder.virtualgateway"; +import { VM_FOLDER_NAME } from "../flat/folders/specific/node.folder.vm"; +import { VOLUME_FOLDER_NAME } from "../flat/folders/specific/node.folder.volume"; +import { Profile, ResourceNodeType } from "../flat/node"; +import { OutputChannel } from "../logs/output_channel"; +import { shell } from "./shell"; + +export type ResourceCost = number; +export type ResourcesTypeCost = { globalPrice: number, values: Map }; + +const DEFAULT_OPTIONS_OSC_COST = new Map([ + ['v0.1.0', '--format json'], + ['v0.2.0', '--format json --skip-resource Oos'], + ]); + +export class AccountCost { + accountCost: number; + region: string; + resourcesCost: Map; + + constructor() { + this.resourcesCost = new Map(); + this.accountCost = 0; + this.region = ""; + } + + getResourceType(resourceType: string): ResourcesTypeCost | undefined { + return this.resourcesCost.get(resourceType); + } + + setResourceType(resourceType: string, value: ResourcesTypeCost) { + this.resourcesCost.set(resourceType, value); + } + + getResourceTypeCost(folderName: string): string | undefined { + const resourceType = folderNameToOscCostResourceType(folderName); + if (typeof resourceType === 'undefined') { + return undefined; + } + const resourceTypeCost = this.getResourceType(resourceType); + if (typeof resourceTypeCost === 'undefined') { + return undefined; + } + return formatPrice(resourceTypeCost.globalPrice, getCurrency(this.region)); + } + + getResourceIdCost(resourceNodeType: ResourceNodeType, resourceId: string): string | undefined { + const resourceType = resourceNodeTypeToOscCostResourceType(resourceNodeType); + if (typeof resourceType === 'undefined') { + return undefined; + } + + const resourceTypeCost = this.getResourceType(resourceType); + if (typeof resourceTypeCost === 'undefined') { + return undefined; + } + + const resourceCost = resourceTypeCost.values.get(resourceId); + if (typeof resourceCost === 'undefined') { + return undefined; + } + + return formatPrice(resourceCost, getCurrency(this.region)); + } + + getAccountCost(): string { + return formatPrice(this.accountCost, getCurrency(this.region)); + } + + +} + +function formatPrice(price: number, currency: string): string { + return "~" + price.toFixed(2) + currency; +} + +export function getCurrency(region: string): string { + switch (region) { + case "eu-west-2": + case "cloudgouv-eu-west-1": + return "€"; + case "ap-northeast-1": + return "¥"; + case "us-east-2": + case "us-west-1": + return "$"; + default: + return "€"; + } +} + +function folderNameToOscCostResourceType(folderName: string): string | undefined { + switch (folderName) { + case VM_FOLDER_NAME: + return "Vm"; + case VOLUME_FOLDER_NAME: + return "Volume"; + case PUBLICIP_FOLDER_NAME: + return "PublicIp"; + case SNAPSHOTS_FOLDER_NAME: + return "Snapshot"; + case LOADBALANCER_FOLDER_NAME: + return "LoadBalancer"; + case FLEXIBLEGPUS_FOLDER_NAME: + return "FlexibleGpu"; + case VPNCONNECTIONS_FOLDER_NAME: + return "FlexibleGpu"; + case NATSERVICES_FOLDER_NAME: + return "NatServices"; + case ACCESSKEY_FOLDER_NAME: + case CLIENTGATEWAYS_FOLDER_NAME: + case IMAGES_FOLDER_NAME: + case INTERNETSERVICES_FOLDER_NAME: + case KEYPAIRS_FOLDER_NAME: + case NET_FOLDER_NAME: + case NICS_FOLDER_NAME: + case ROUTETABLES_FOLDER_NAME: + case SECURITYGROUPS_FOLDER_NAME: + case SUBNETS_FOLDER_NAME: + case VIRTUALGATEWAYS_FOLDER_NAME: + case DHCPOPTIONS_FOLDER_NAME: + case DIRECTLINKS_FOLDER_NAME: + case DIRECTLINKINTERFACES_FOLDER_NAME: + case NETPEERINGS_FOLDER_NAME: + case APIACCESSRULES_FOLDER_NAME: + case NETACCESSPOINTS_FOLDER_NAME: + case CA_FOLDER_NAME: + return undefined; + default: + OutputChannel.getInstance().appendLine(`The folder '${folderName}' is not handle for osc-cost conversion. Report it to the developpers`); + return undefined; + } +} + +function resourceNodeTypeToOscCostResourceType(resourceNodeType: ResourceNodeType): string | undefined { + switch (resourceNodeType) { + case 'vms': + return "Vm"; + case 'volumes': + return "Volume"; + case 'eips': + return "PublicIp"; + case 'snapshots': + return "Snapshot"; + case 'loadbalancers': + return "LoadBalancer"; + case 'FlexibleGpu': + return "FlexibleGpu"; + case 'VpnConnection': + return "FlexibleGpu"; + case 'NatService': + return "NatServices"; + case 'AccessKey': + case 'ClientGateway': + case 'omis': + case 'InternetService': + case 'keypairs': + case 'vpc': + case 'Nic': + case 'routetables': + case 'securitygroups': + case 'Subnet': + case 'VirtualGateway': + case 'DhcpOption': + case 'DirectLink': + case 'DirectLinkInterface': + case 'NetPeering': + case 'ApiAccessRule': + case 'NetAccessPoint': + case 'Ca': + return undefined; + default: + OutputChannel.getInstance().appendLine(`The resourceNodeType '${resourceNodeType}' is not handle for osc-cost conversion. Report it to the developpers`); + return undefined; + } +} + + +function jsonToAccountCost(oscCostOutput: string): AccountCost | undefined { + const accountCost = new AccountCost(); + for (const jsonString of oscCostOutput.split('\n')) { + OutputChannel.getInstance().appendLine(`The jsonString is ${jsonString}`); + let json; + try { + json = JSON.parse(jsonString); + } catch { + OutputChannel.getInstance().appendLine(`Could not parse the json string ${jsonString}`); + return undefined; + } + + if (typeof json === "undefined") { + OutputChannel.getInstance().appendLine(`Could not parse the json string ${jsonString}`); + return undefined; + } + + //OutputChannel.getInstance().appendLine(`The json is ${json}`); + + + const resourceType = json.resource_type; + const resourceId = json.resource_id; + const pricePerMonth = json.price_per_month; + const region = json.region; + + if (typeof resourceType !== 'string') { + OutputChannel.getInstance().appendLine(`Could not parse the resource type ${resourceType}`); + return undefined; + } + + if (typeof resourceId !== 'string') { + OutputChannel.getInstance().appendLine(`Could not parse the resource id ${resourceId}`); + return undefined; + } + + if (typeof region !== 'string') { + OutputChannel.getInstance().appendLine(`Could not parse the region ${region}`); + return undefined; + } + + if (typeof pricePerMonth !== 'number') { + OutputChannel.getInstance().appendLine(`Could not parse the price per month ${pricePerMonth}`); + return undefined; + } + + + let resourceElement = accountCost.getResourceType(resourceType); + if (typeof resourceElement === 'undefined') { + resourceElement = { + globalPrice: 0, + values: new Map() + }; + } + + resourceElement.values.set(resourceId, pricePerMonth); + resourceElement.globalPrice += pricePerMonth; + accountCost.accountCost += pricePerMonth; + accountCost.region = region; + accountCost.setResourceType(resourceType, resourceElement); + } + + + return accountCost; +} + +export async function fetchAccountCost(profile: Profile): Promise { + + const oscCostPath = getOscCostPath(); + + if (typeof oscCostPath === 'undefined') { + vscode.window.showErrorMessage("Cannot find osc-cost binary. Please install it."); + return Promise.resolve(undefined); + } + + const res = await shell.exec(`osc-cost --format json --profile ${profile.name}`); + if (typeof res === "undefined") { + return res; + } + + return Promise.resolve(jsonToAccountCost(res.stdout.trim())); +} + + +export function isOscCostEnabled(): boolean { + const isEnabled = getConfigurationParameter(OSC_COST_PARAMETER + ".enabled"); + if (typeof isEnabled === 'undefined') { + return false; + } + OutputChannel.getInstance().appendLine("IsEnabled:" + isEnabled); + return isEnabled; +} + +export function getOscCostPath(): string | undefined { + const userOscCostPath = getConfigurationParameter(OSC_COST_PARAMETER + ".oscCostPath"); + if (typeof userOscCostPath === 'undefined' || userOscCostPath === "") { + const systemOscCostPath = shell.which("osc-cost"); + if (systemOscCostPath === null) { + return undefined; + } + OutputChannel.getInstance().appendLine(systemOscCostPath); + return systemOscCostPath; + } + // Check exist + if (! pathExists(userOscCostPath)) { + return undefined; + } + OutputChannel.getInstance().appendLine(userOscCostPath); + return userOscCostPath; +} \ No newline at end of file diff --git a/src/components/shell.ts b/src/components/shell.ts new file mode 100644 index 0000000..99559c6 --- /dev/null +++ b/src/components/shell.ts @@ -0,0 +1,131 @@ +'use strict'; + +import * as vscode from 'vscode'; +import * as shelljs from 'shelljs'; +import { ChildProcess } from 'child_process'; + +export enum Platform { + // eslint-disable-next-line @typescript-eslint/naming-convention + Windows, + // eslint-disable-next-line @typescript-eslint/naming-convention + MacOS, + // eslint-disable-next-line @typescript-eslint/naming-convention + Linux, + // eslint-disable-next-line @typescript-eslint/naming-convention + Unsupported, // shouldn't happen! +} + +export interface Shell { + isWindows(): boolean; + isUnix(): boolean; + platform(): Platform; + execOpts(): any; + exec(cmd: string, stdin?: string): Promise; + which(bin: string): string | null; +} + +export const shell: Shell = { + isWindows: isWindows, + isUnix: isUnix, + platform: platform, + execOpts: execOpts, + exec: exec, + which: which, +}; + +const WINDOWS = 'win32'; + +export interface ShellResult { + readonly code: number; + readonly stdout: string; + readonly stderr: string; +} + +export type ShellHandler = (code: number, stdout: string, stderr: string) => void; + +function isWindows(): boolean { + return (process.platform === WINDOWS); +} + +function isUnix(): boolean { + return !isWindows(); +} + +function platform(): Platform { + switch (process.platform) { + case 'win32': return Platform.Windows; + case 'darwin': return Platform.MacOS; + case 'linux': return Platform.Linux; + default: return Platform.Unsupported; + } +} + +function concatIfSafe(homeDrive: string | undefined, homePath: string | undefined): string | undefined { + if (homeDrive && homePath) { + const safe = !homePath.toLowerCase().startsWith('\\windows\\system32'); + if (safe) { + return homeDrive.concat(homePath); + } + } + + return undefined; +} + +function home(): string { + return process.env['HOME'] || + concatIfSafe(process.env['HOMEDRIVE'], process.env['HOMEPATH']) || + process.env['USERPROFILE'] || + ''; +} + +function execOpts(): any { + let env = process.env; + if (isWindows()) { + // eslint-disable-next-line @typescript-eslint/naming-convention + env = Object.assign({}, env, { HOME: home() }); + } + env = shellEnvironment(env); + + const opts = { + cwd: typeof vscode.workspace.workspaceFolders === 'undefined' ? undefined : vscode.workspace.workspaceFolders[0], + env: env, + async: true + }; + return opts; +} + +async function exec(cmd: string, stdin?: string): Promise { + try { + return await execCore(cmd, execOpts(), null, stdin); + } catch (ex) { + vscode.window.showErrorMessage(`${ex}`); + return undefined; + } +} + +function execCore(cmd: string, opts: any, callback?: ((proc: ChildProcess) => void) | null, stdin?: string): Promise { + return new Promise((resolve) => { + const proc = shelljs.exec(cmd, opts, (code, stdout, stderr) => resolve({ code: code, stdout: stdout, stderr: stderr })); + if (stdin && proc.stdin !== null) { + proc.stdin.end(stdin); + } + if (callback) { + callback(proc); + } + }); +} + +function which(bin: string): string | null { + return shelljs.which(bin); +} + +export function shellEnvironment(baseEnvironment: any): any { + const env = Object.assign({}, baseEnvironment); + return env; +} + +const SAFE_CHARS_REGEX = /^[-,._+:@%/\w]*$/; + +export function isSafe(s: string): boolean { + return SAFE_CHARS_REGEX.test(s); +} diff --git a/src/configuration/utils.ts b/src/configuration/utils.ts index 932f53c..401887f 100644 --- a/src/configuration/utils.ts +++ b/src/configuration/utils.ts @@ -5,6 +5,7 @@ const CONFIGURATION_NAME = "osc-viewer"; export const FILTERS_PARAMETER = "filters"; export const DISABLE_FOLDER_PARAMETER = "disableFolders"; +export const OSC_COST_PARAMETER = "costEstimation"; export function getConfigurationParameter(parameter: string): T | undefined { const conf = vscode.workspace.getConfiguration(CONFIGURATION_NAME); diff --git a/src/explorer.ts b/src/explorer.ts index c3e66c5..749e4c4 100644 --- a/src/explorer.ts +++ b/src/explorer.ts @@ -1,7 +1,9 @@ import * as vscode from 'vscode'; -import { ExplorerNode } from './flat/node'; +import { ExplorerNode, Profile } from './flat/node'; import { ProfileNode } from './flat/node.profile'; import { createConfigFile, getConfigFile, getDefaultConfigFilePath, jsonToProfile, readConfigFile } from './config_file/utils'; +import { AccountCost, fetchAccountCost, isOscCostEnabled } from './components/osc_cost'; +import { OutputChannel } from './logs/output_channel'; export class OscExplorer implements vscode.TreeDataProvider { @@ -14,16 +16,21 @@ export class OscExplorer implements vscode.TreeDataProvider { this._onDidChangeTreeData.fire(); } - getTreeItem(element: ExplorerNode): vscode.TreeItem { + getTreeItem(element: ExplorerNode): Thenable { return element.getTreeItem(); } - getChildren(element?: ExplorerNode): Thenable { + async getChildren(element?: ExplorerNode): Promise { if (element) { return element.getChildren(); } else { - const toExplorerNode = (profileName: string, definition: any): ProfileNode => { - return new ProfileNode(jsonToProfile(profileName, definition)); + const toExplorerNode = async (profileName: string, definition: any): Promise => { + const profile = jsonToProfile(profileName, definition); + if (isOscCostEnabled()) { + profile.oscCost = await this.retrieveAccountCost(profile); + OutputChannel.getInstance().appendLine(`Retrieve the cost for ${profile.name}: ${profile.oscCost?.accountCost}`); + } + return Promise.resolve(new ProfileNode(profile)); }; const oscConfigObject = readConfigFile(); @@ -31,7 +38,7 @@ export class OscExplorer implements vscode.TreeDataProvider { vscode.window.showErrorMessage(vscode.l10n.t('No config file found')); return Promise.resolve([]); } - const explorerNodes = Object.keys(oscConfigObject).map(dep => toExplorerNode(dep, oscConfigObject[dep])); + const explorerNodes = await Promise.all(Object.keys(oscConfigObject).map(async (dep) => await toExplorerNode(dep, oscConfigObject[dep]))); return Promise.resolve(explorerNodes); } @@ -52,4 +59,9 @@ export class OscExplorer implements vscode.TreeDataProvider { } + async retrieveAccountCost(profile: Profile): Promise { + // TODO: Add user options + return fetchAccountCost(profile); + } + } \ No newline at end of file diff --git a/src/flat/folders/node.folder.ts b/src/flat/folders/node.folder.ts index 507811a..9b10608 100644 --- a/src/flat/folders/node.folder.ts +++ b/src/flat/folders/node.folder.ts @@ -12,6 +12,9 @@ export abstract class FolderNode implements ExplorerFolderNode { getTreeItem(): vscode.TreeItem { const treeItem = new vscode.TreeItem(this.folderName, vscode.TreeItemCollapsibleState.Collapsed); treeItem.contextValue = this.getContextValue(); + if (typeof this.profile.oscCost !== 'undefined') { + treeItem.description = this.profile.oscCost.getResourceTypeCost(this.folderName); + } return treeItem; } diff --git a/src/flat/node.profile.ts b/src/flat/node.profile.ts index 825e05e..556a3f1 100644 --- a/src/flat/node.profile.ts +++ b/src/flat/node.profile.ts @@ -39,12 +39,15 @@ export class ProfileNode implements ExplorerProfileNode { getTreeItem(): vscode.TreeItem { const treeItem = new vscode.TreeItem(this.profile.name, vscode.TreeItemCollapsibleState.Collapsed); + if (typeof this.profile.oscCost !== 'undefined') { + treeItem.description = this.profile.oscCost.getAccountCost(); + } treeItem.iconPath = new vscode.ThemeIcon("account"); treeItem.contextValue = this.getContextValue(); return treeItem; } - getChildren(): Thenable { + async getChildren(): Promise { const resources = [ [ACCESSKEY_FOLDER_NAME, new AccessKeysFolderNode(this.profile)], [APIACCESSRULES_FOLDER_NAME, new ApiAccessRulesFolderNode(this.profile)], diff --git a/src/flat/node.ts b/src/flat/node.ts index 0ddd7f2..6f68b88 100644 --- a/src/flat/node.ts +++ b/src/flat/node.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { AccountCost } from '../components/osc_cost'; export interface ExplorerNodeBase { getChildren(): Thenable; @@ -63,6 +64,7 @@ export class NodeImpl { export class Profile { accountId: string; + oscCost: AccountCost | undefined; constructor( public readonly name: string, public readonly accessKey: string, diff --git a/src/flat/resources/node.resources.ts b/src/flat/resources/node.resources.ts index 9e4f4bc..5199954 100644 --- a/src/flat/resources/node.resources.ts +++ b/src/flat/resources/node.resources.ts @@ -42,6 +42,9 @@ export class ResourceNode implements ExplorerResourceNode { getTreeItem(): vscode.TreeItem { const treeItem = new vscode.TreeItem(this.resourceId, vscode.TreeItemCollapsibleState.None); treeItem.description = this.resourceName; + if (typeof this.profile.oscCost !== 'undefined') { + treeItem.description += " " + this.profile.oscCost.getResourceIdCost(this.resourceType, this.resourceId); + } treeItem.iconPath = this.getIconPath(); treeItem.command = { "title": vscode.l10n.t("Get"),