This commit is contained in:
dilin-MS 2020-01-10 16:24:50 +08:00
Родитель afdd0dad0f
Коммит eaceda84d7
116 изменённых файлов: 15947 добавлений и 11860 удалений

Просмотреть файл

@ -2,6 +2,6 @@
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
out
assets
views
vendor/node-usb-native
vendor/node-usb-native
views/
assets/

Просмотреть файл

@ -1,12 +1,13 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2017,
"ecmaVersion": 2018,
"sourceType": "module"
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
"plugin:@typescript-eslint/recommended",
"prettier/@typescript-eslint",
"plugin:prettier/recommended"
],
"env": {
"browser": true,
@ -20,12 +21,12 @@
"@typescript-eslint/eslint-plugin"
],
"rules": {
"max-len": ["error", {"code": 120, "ignorePattern": "^import\\s.+\\sfrom\\s.+;$", "ignoreStrings": true}],
"no-unused-vars": "off",
"@typescript-eslint/no-var-requires": "off",
"no-dupe-class-members": "off",
"@typescript-eslint/no-empty-function": ["error", { "allow": ["constructors"] }],
"object-curly-spacing": ["error", "always"],
"indent": ["error", 2],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-use-before-define": "off"
},

Просмотреть файл

@ -6,7 +6,7 @@ function command(cmd, callback) {
return;
}
const args = Array.from(arguments);
if (typeof args[args.length - 1] === 'function') {
if (typeof args[args.length - 1] === "function") {
callback = args[args.length - 1];
args.length = args.length - 1;
} else {
@ -14,7 +14,7 @@ function command(cmd, callback) {
}
args.shift();
const messageId = new Date().getTime() + Math.random();
callbackStack.push({
messageId,
callback
@ -27,7 +27,7 @@ function command(cmd, callback) {
});
}
window.addEventListener('message', event => {
window.addEventListener("message", event => {
const message = event.data;
for (let index = 0; index < callbackStack.length; index++) {
@ -40,4 +40,4 @@ window.addEventListener('message', event => {
break;
}
}
});
});

Просмотреть файл

@ -7,7 +7,10 @@ const ALL = "All";
const repository = new Vue({
el: "#main",
data: {
companyName: _location.search === IS_PUBLIC_URL ? "Public repository" : "Company repository",
companyName:
_location.search === IS_PUBLIC_URL
? "Public repository"
: "Company repository",
selectedInterfaces: {
value: []
},
@ -44,7 +47,20 @@ const repository = new Vue({
value: true
},
allTags: {
value: ["tag1", "tag2", "tag3", "tag11", "tag12", "tag13", "tag21", "tag22", "tag23", "tag31", "tag32", "tag33"]
value: [
"tag1",
"tag2",
"tag3",
"tag11",
"tag12",
"tag13",
"tag21",
"tag22",
"tag23",
"tag31",
"tag32",
"tag33"
]
},
filterTagsKeywords: "",
nextPageLoadingCounter: null,
@ -96,18 +112,36 @@ function encodeHTML(value) {
}
function deleteDigitalTwinFiles() {
const fileIds = this.type.value === INTERFACE ? this.selectedInterfaces.value : this.selectedCapabilityModels.value;
command("azure-digital-twins.deleteModels", this.publicRepository, fileIds, refreshDigitalTwinFileList.bind(this));
const fileIds =
this.type.value === INTERFACE
? this.selectedInterfaces.value
: this.selectedCapabilityModels.value;
command(
"azure-digital-twins.deleteModels",
this.publicRepository,
fileIds,
refreshDigitalTwinFileList.bind(this)
);
}
function editDigitalTwinFiles() {
const fileIds = this.type.value === INTERFACE ? this.selectedInterfaces.value : this.selectedCapabilityModels.value;
command("azure-digital-twins.downloadModels", this.publicRepository, fileIds, refreshDigitalTwinFileList.bind(this));
const fileIds =
this.type.value === INTERFACE
? this.selectedInterfaces.value
: this.selectedCapabilityModels.value;
command(
"azure-digital-twins.downloadModels",
this.publicRepository,
fileIds,
refreshDigitalTwinFileList.bind(this)
);
}
function createDigitalTwinFile() {
const commandName =
this.type.value === INTERFACE ? "azure-digital-twins.createInterface" : "azure-digital-twins.createCapabilityModel";
this.type.value === INTERFACE
? "azure-digital-twins.createInterface"
: "azure-digital-twins.createCapabilityModel";
command(commandName);
}
@ -130,11 +164,18 @@ function getNextPageDigitalTwinFiles(fileType) {
}
loadingDigitalTwinFiles.value = true;
command(commandName, this.publicRepository, this.searchKeywords, 50, nextToken.value, res => {
Vue.set(fileList, VALUE, fileList.value.concat(res.result.results));
Vue.set(nextToken, VALUE, res.result.continuationToken);
Vue.set(loadingDigitalTwinFiles, VALUE, false);
});
command(
commandName,
this.publicRepository,
this.searchKeywords,
50,
nextToken.value,
res => {
Vue.set(fileList, VALUE, fileList.value.concat(res.result.results));
Vue.set(nextToken, VALUE, res.result.continuationToken);
Vue.set(loadingDigitalTwinFiles, VALUE, false);
}
);
}
function refreshDigitalTwinFileList() {
@ -294,10 +335,19 @@ function onScrollTable(event) {
if (this.filterKeywords) {
return;
}
const nextToken = this.type.value === INTERFACE ? this.interfaceNextToken.value : this.capabilityNextToken.value;
const nextToken =
this.type.value === INTERFACE
? this.interfaceNextToken.value
: this.capabilityNextToken.value;
const loadingDigitalTwinFiles =
this.type.value === INTERFACE ? this.loadingDigitalTwinInterfaces : this.loadingDigitalTwinCapabilityModels;
if (!nextToken || this.nextPageLoadingCounter || loadingDigitalTwinFiles.value) {
this.type.value === INTERFACE
? this.loadingDigitalTwinInterfaces
: this.loadingDigitalTwinCapabilityModels;
if (
!nextToken ||
this.nextPageLoadingCounter ||
loadingDigitalTwinFiles.value
) {
return;
}
this.nextPageLoadingCounter = setTimeout(() => {

Разница между файлами не показана из-за своего большого размера Загрузить разницу

379
package-lock.json сгенерированный
Просмотреть файл

@ -262,17 +262,6 @@
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0",
"tsutils": "^3.17.1"
},
"dependencies": {
"tsutils": {
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
"integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
}
}
},
"@typescript-eslint/experimental-utils": {
@ -330,15 +319,6 @@
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"tsutils": {
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
"integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
}
}
},
@ -602,6 +582,15 @@
"integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==",
"dev": true
},
"ansi-escapes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz",
"integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==",
"dev": true,
"requires": {
"type-fest": "^0.8.1"
}
},
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
@ -1285,6 +1274,12 @@
"supports-color": "^5.3.0"
}
},
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true
},
"chokidar": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
@ -1352,6 +1347,15 @@
}
}
},
"cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
"dev": true,
"requires": {
"restore-cursor": "^3.1.0"
}
},
"cli-width": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
@ -2031,40 +2035,10 @@
"v8-compile-cache": "^2.0.3"
},
"dependencies": {
"ansi-escapes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz",
"integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==",
"dev": true,
"requires": {
"type-fest": "^0.8.1"
}
},
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true
},
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true
},
"cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
"dev": true,
"requires": {
"restore-cursor": "^3.1.0"
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
},
"eslint-scope": {
@ -2077,26 +2051,6 @@
"estraverse": "^4.1.1"
}
},
"external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
"dev": true,
"requires": {
"chardet": "^0.7.0",
"iconv-lite": "^0.4.24",
"tmp": "^0.0.33"
}
},
"figures": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz",
"integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"glob-parent": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
@ -2106,98 +2060,18 @@
"is-glob": "^4.0.1"
}
},
"inquirer": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.3.tgz",
"integrity": "sha512-+OiOVeVydu4hnCGLCSX+wedovR/Yzskv9BFqUNNKq9uU2qg7LCcCo3R86S2E7WLo0y/x2pnEZfZe1CoYnORUAw==",
"dev": true,
"requires": {
"ansi-escapes": "^4.2.1",
"chalk": "^2.4.2",
"cli-cursor": "^3.1.0",
"cli-width": "^2.0.0",
"external-editor": "^3.0.3",
"figures": "^3.0.0",
"lodash": "^4.17.15",
"mute-stream": "0.0.8",
"run-async": "^2.2.0",
"rxjs": "^6.5.3",
"string-width": "^4.1.0",
"strip-ansi": "^5.1.0",
"through": "^2.3.6"
}
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
},
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true
},
"mute-stream": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"dev": true
},
"onetime": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
"integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
"dev": true,
"requires": {
"mimic-fn": "^2.1.0"
}
},
"regexpp": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
"integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
"dev": true
},
"restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
"dev": true,
"requires": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
},
"dependencies": {
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
@ -2205,14 +2079,6 @@
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
}
}
},
"strip-json-comments": {
@ -2223,6 +2089,24 @@
}
}
},
"eslint-config-prettier": {
"version": "6.9.0",
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.9.0.tgz",
"integrity": "sha512-k4E14HBtcLv0uqThaI6I/n1LEqROp8XaPu6SO9Z32u5NlGRC07Enu1Bh2KEFw4FNHbekH8yzbIU9kUGxbiGmCA==",
"dev": true,
"requires": {
"get-stdin": "^6.0.0"
}
},
"eslint-plugin-prettier": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz",
"integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==",
"dev": true,
"requires": {
"prettier-linter-helpers": "^1.0.0"
}
},
"eslint-scope": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
@ -2428,6 +2312,17 @@
}
}
},
"external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
"dev": true,
"requires": {
"chardet": "^0.7.0",
"iconv-lite": "^0.4.24",
"tmp": "^0.0.33"
}
},
"extglob": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
@ -2539,6 +2434,12 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
},
"fast-diff": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
"integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
"dev": true
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@ -2564,6 +2465,15 @@
"integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==",
"dev": true
},
"figures": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz",
"integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==",
"dev": true,
"requires": {
"escape-string-regexp": "^1.0.5"
}
},
"file-entry-cache": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
@ -3325,6 +3235,12 @@
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"get-stdin": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz",
"integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==",
"dev": true
},
"get-stream": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
@ -3743,6 +3659,86 @@
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"inquirer": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.3.tgz",
"integrity": "sha512-+OiOVeVydu4hnCGLCSX+wedovR/Yzskv9BFqUNNKq9uU2qg7LCcCo3R86S2E7WLo0y/x2pnEZfZe1CoYnORUAw==",
"dev": true,
"requires": {
"ansi-escapes": "^4.2.1",
"chalk": "^2.4.2",
"cli-cursor": "^3.1.0",
"cli-width": "^2.0.0",
"external-editor": "^3.0.3",
"figures": "^3.0.0",
"lodash": "^4.17.15",
"mute-stream": "0.0.8",
"run-async": "^2.2.0",
"rxjs": "^6.5.3",
"string-width": "^4.1.0",
"strip-ansi": "^5.1.0",
"through": "^2.3.6"
},
"dependencies": {
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true
},
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
"integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
"dev": true,
"requires": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.0"
},
"dependencies": {
"strip-ansi": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
"integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
"dev": true,
"requires": {
"ansi-regex": "^5.0.0"
}
}
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"dev": true,
"requires": {
"ansi-regex": "^4.1.0"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"dev": true
}
}
}
}
},
"interpret": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
@ -4306,6 +4302,12 @@
"mime-db": "1.42.0"
}
},
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true
},
"mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
@ -4506,6 +4508,12 @@
"uuid": "^3.2.1"
}
},
"mute-stream": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"dev": true
},
"nan": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
@ -4759,6 +4767,15 @@
"wrappy": "1"
}
},
"onetime": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
"integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
"dev": true,
"requires": {
"mimic-fn": "^2.1.0"
}
},
"opn": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/opn/-/opn-5.1.0.tgz",
@ -5040,6 +5057,21 @@
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"dev": true
},
"prettier": {
"version": "1.19.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
"integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
"dev": true
},
"prettier-linter-helpers": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
"integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
"dev": true,
"requires": {
"fast-diff": "^1.1.2"
}
},
"process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@ -5359,6 +5391,16 @@
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
"dev": true
},
"restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
"dev": true,
"requires": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
}
},
"ret": {
"version": "0.1.15",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
@ -6172,6 +6214,15 @@
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
"dev": true
},
"tsutils": {
"version": "3.17.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
"integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
"dev": true,
"requires": {
"tslib": "^1.8.1"
}
},
"tty-browserify": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
@ -6224,6 +6275,12 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
},
"typescript": {
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.4.tgz",
"integrity": "sha512-A25xv5XCtarLwXpcDNZzCGvW2D1S3/bACratYBx2sax8PefsFhlYmkQicKHvpYflFS8if4zne5zT5kpJ7pzuvw==",
"dev": true
},
"underscore": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz",

Просмотреть файл

@ -229,8 +229,8 @@
"watch": "tsc -watch -p ./ && node scripts/copyVendor.js",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "npm run compile && node ./node_modules/vscode/bin/test",
"check": "eslint . --ext .js,.jsx,.ts,.tsx",
"fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"check": "./node_modules/eslint/bin/eslint.js . --ext .js,.jsx,.ts,.tsx",
"fix": "./node_modules/eslint/bin/eslint.js . --ext .js,.jsx,.ts,.tsx --fix",
"prepare": "npm run compile",
"pretest": "npm run compile",
"posttest": "npm run check",
@ -257,8 +257,11 @@
"@typescript-eslint/eslint-plugin": "^2.15.0",
"@typescript-eslint/parser": "^2.15.0",
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.9.0",
"eslint-plugin-prettier": "^3.1.2",
"glob": "^7.1.4",
"typescript": "^3.2.1",
"prettier": "^1.19.1",
"typescript": "^3.7.4",
"vscode": "^1.1.33",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.9"

Просмотреть файл

@ -1,3 +1,3 @@
const fs = require('fs-plus');
const fs = require("fs-plus");
fs.copySync('vendor', 'out/vendor');
fs.copySync("vendor", "out/vendor");

Просмотреть файл

@ -1,4 +1,4 @@
const fs = require('fs');
const fs = require("fs");
/**
* Update package.json with test name, displayName, publisher, ai aky.
@ -16,7 +16,7 @@ function updateConfigForNonProduction(packageJson, testName, testDisplayName) {
packageJson.aiKey = process.env.TEST_AIKEY;
const indexOfDash = packageJson.version.indexOf('-');
const indexOfDash = packageJson.version.indexOf("-");
if (indexOfDash > 0) {
packageJson.version = packageJson.version.substring(0, indexOfDash);
}
@ -25,15 +25,22 @@ function updateConfigForNonProduction(packageJson, testName, testDisplayName) {
// Modify extensionId in template files
const extensionIdPattern = /vsciot-vscode.vscode-iot-workbench/g;
const testExtensionId = 'iotdevexbuild.' + testName;
const testExtensionId = "iotdevexbuild." + testName;
const arm7DevcontainerJsonFile = "resources/templates/arm7/devcontainer.json";
const arm8DevcontainerJsonFile = "resources/templates/arm8/devcontainer.json";
const x86DevcontainerJsonFile = "resources/templates/x86/devcontainer.json";
const files = [arm7DevcontainerJsonFile, arm8DevcontainerJsonFile, x86DevcontainerJsonFile];
const files = [
arm7DevcontainerJsonFile,
arm8DevcontainerJsonFile,
x86DevcontainerJsonFile
];
files.forEach(filePath => {
const originalJsonFile = fs.readFileSync(filePath).toString();
const replaceJson = originalJsonFile.replace(extensionIdPattern, testExtensionId.toLowerCase());
const replaceJson = originalJsonFile.replace(
extensionIdPattern,
testExtensionId.toLowerCase()
);
fs.writeFileSync(filePath, replaceJson);
});
}
@ -44,13 +51,17 @@ function updateConfigForNonProduction(packageJson, testName, testDisplayName) {
* TRAVIS_TAG =~ /^v?[0-9]+\.[0-9]+\.[0-9]+$/: Production release (eg. v0.10.18)
* TRAVIS_TAG =~ /^v?[0-9]+\.[0-9]+\.[0-9]+-[rR][cC]/: RC release (eg. v0.10.18-rc, v0.10.18-rc2, etc.)
*/
const packageJson = JSON.parse(fs.readFileSync('package.json'));
const packageJson = JSON.parse(fs.readFileSync("package.json"));
// Nightly Build
if (process.env.BUILD_REASON === "Schedule") {
const nightlyBuildName = "test-owl-project-nightly";
const nightlyBuildDisplayName = "Test OWL Project (Nightly)";
updateConfigForNonProduction(packageJson, nightlyBuildName, nightlyBuildDisplayName);
updateConfigForNonProduction(
packageJson,
nightlyBuildName,
nightlyBuildDisplayName
);
} else if (process.env.IS_PROD) {
// Update resource link
const codeGenUrl = "https://aka.ms/iot-codegen-cli-for-workbench";
@ -64,4 +75,4 @@ if (process.env.BUILD_REASON === "Schedule") {
updateConfigForNonProduction(packageJson, testName, testDisplayName);
}
fs.writeFileSync('package.json', JSON.stringify(packageJson, null, 2) + '\n');
fs.writeFileSync("package.json", JSON.stringify(packageJson, null, 2) + "\n");

Просмотреть файл

@ -1,24 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as vscode from 'vscode';
import * as vscode from "vscode";
import { ArduinoCommands } from './common/Commands';
import { Board, BoardInstallation } from './Models/Interfaces/Board';
import { ArduinoCommands } from "./common/Commands";
import { Board, BoardInstallation } from "./Models/Interfaces/Board";
export class ArduinoPackageManager {
private static INSTALLED_BOARDS: Board[] = [];
private static async setAdditionalUrl(url: string): Promise<void> {
const existedUrls =
vscode.workspace.getConfiguration().get<string[]|string>(
'arduino.additionalUrls');
const existedUrls = vscode.workspace
.getConfiguration()
.get<string[] | string>("arduino.additionalUrls");
if (!existedUrls || existedUrls.length === 0) {
await vscode.workspace.getConfiguration().update(
'arduino.additionalUrls', url, vscode.ConfigurationTarget.Global);
await vscode.workspace
.getConfiguration()
.update(
"arduino.additionalUrls",
url,
vscode.ConfigurationTarget.Global
);
} else {
let _existedUrls: string[];
if (typeof existedUrls === 'string') {
_existedUrls = existedUrls.split(',').map((url) => url.trim());
if (typeof existedUrls === "string") {
_existedUrls = existedUrls.split(",").map(url => url.trim());
} else {
_existedUrls = existedUrls;
}
@ -28,14 +33,22 @@ export class ArduinoPackageManager {
}
}
_existedUrls.push(url);
if (typeof existedUrls === 'string') {
await vscode.workspace.getConfiguration().update(
'arduino.additionalUrls', _existedUrls.join(','),
vscode.ConfigurationTarget.Global);
if (typeof existedUrls === "string") {
await vscode.workspace
.getConfiguration()
.update(
"arduino.additionalUrls",
_existedUrls.join(","),
vscode.ConfigurationTarget.Global
);
} else {
await vscode.workspace.getConfiguration().update(
'arduino.additionalUrls', _existedUrls,
vscode.ConfigurationTarget.Global);
await vscode.workspace
.getConfiguration()
.update(
"arduino.additionalUrls",
_existedUrls,
vscode.ConfigurationTarget.Global
);
}
}
}
@ -48,8 +61,10 @@ export class ArduinoPackageManager {
const cachedBoard = ArduinoPackageManager.INSTALLED_BOARDS.find(_board => {
const _installation = _board.installation as BoardInstallation;
const installation = board.installation as BoardInstallation;
return _installation.packageName === installation.packageName &&
_installation.architecture === installation.architecture;
return (
_installation.packageName === installation.packageName &&
_installation.architecture === installation.architecture
);
});
if (cachedBoard) {
@ -58,10 +73,13 @@ export class ArduinoPackageManager {
try {
await ArduinoPackageManager.setAdditionalUrl(
board.installation.additionalUrl);
board.installation.additionalUrl
);
await vscode.commands.executeCommand(
ArduinoCommands.InstallBoard, board.installation.packageName,
board.installation.architecture);
ArduinoCommands.InstallBoard,
board.installation.packageName,
board.installation.architecture
);
ArduinoPackageManager.INSTALLED_BOARDS.push(board);
} catch (ignore) {
// If we failed to install board package,
@ -73,4 +91,4 @@ export class ArduinoPackageManager {
}
return;
}
}
}

Просмотреть файл

@ -1,36 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
"use strict";
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import { TelemetryContext } from './telemetry';
import { constructAndLoadIoTProject } from './utils';
import { RemoteExtension } from './Models/RemoteExtension';
import * as vscode from "vscode";
import { TelemetryContext } from "./telemetry";
import { constructAndLoadIoTProject } from "./utils";
import { RemoteExtension } from "./Models/RemoteExtension";
export class AzureOperator {
async provision(context: vscode.ExtensionContext, channel: vscode.OutputChannel, telemetryContext: TelemetryContext): Promise<void> {
const iotProject =
await constructAndLoadIoTProject(context, channel, telemetryContext);
async provision(
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> {
const iotProject = await constructAndLoadIoTProject(
context,
channel,
telemetryContext
);
if (!iotProject) {
return;
}
const status = await iotProject.provision();
if (status) {
vscode.window.showInformationMessage('Azure provision succeeded.');
vscode.window.showInformationMessage("Azure provision succeeded.");
}
}
async deploy(context: vscode.ExtensionContext, channel: vscode.OutputChannel, telemetryContext: TelemetryContext): Promise<void> {
async deploy(
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> {
// Azure deploy command can be executed only in local environment
const isLocal = RemoteExtension.checkLocalBeforeRunCommand(context);
if (!isLocal) {
return;
}
const iotProject =
await constructAndLoadIoTProject(context, channel, telemetryContext);
const iotProject = await constructAndLoadIoTProject(
context,
channel,
telemetryContext
);
if (iotProject) {
await iotProject.deploy();
}

Просмотреть файл

@ -1,6 +1,6 @@
export class CancelOperationError extends Error {
constructor(message: string) {
super(`Operation cancelled: ${message}`);
this.name = 'CancelOperationError';
this.name = "CancelOperationError";
}
}
}

Просмотреть файл

@ -1,36 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
"use strict";
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import * as vscode from "vscode";
import { TelemetryContext } from './telemetry';
import { constructAndLoadIoTProject } from './utils';
import { TelemetryContext } from "./telemetry";
import { constructAndLoadIoTProject } from "./utils";
export class DeviceOperator {
async compile(context: vscode.ExtensionContext, channel: vscode.OutputChannel, telemetryContext: TelemetryContext): Promise<void> {
const iotProject =
await constructAndLoadIoTProject(context, channel, telemetryContext);
async compile(
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> {
const iotProject = await constructAndLoadIoTProject(
context,
channel,
telemetryContext
);
if (!iotProject) {
return;
}
await iotProject.compile();
}
async upload(context: vscode.ExtensionContext, channel: vscode.OutputChannel, telemetryContext: TelemetryContext): Promise<void> {
const iotProject =
await constructAndLoadIoTProject(context, channel, telemetryContext);
async upload(
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> {
const iotProject = await constructAndLoadIoTProject(
context,
channel,
telemetryContext
);
if (!iotProject) {
return;
}
await iotProject.upload();
}
async configDeviceSettings(context: vscode.ExtensionContext, channel: vscode.OutputChannel, telemetryContext: TelemetryContext): Promise<void> {
const iotProject =
await constructAndLoadIoTProject(context, channel, telemetryContext);
async configDeviceSettings(
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> {
const iotProject = await constructAndLoadIoTProject(
context,
channel,
telemetryContext
);
if (!iotProject) {
return;
}

Просмотреть файл

@ -1,12 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { MessageItem } from 'vscode';
import { MessageItem } from "vscode";
export class DialogResponses {
static skipForNow: MessageItem = { title: 'Skip for now' };
static all: MessageItem = { title: 'All' };
static yes: MessageItem = { title: 'Yes' };
static no: MessageItem = { title: 'No' };
static cancel: MessageItem = { title: 'Cancel', isCloseAffordance: true };
}
static skipForNow: MessageItem = { title: "Skip for now" };
static all: MessageItem = { title: "All" };
static yes: MessageItem = { title: "Yes" };
static no: MessageItem = { title: "No" };
static cancel: MessageItem = { title: "Cancel", isCloseAffordance: true };
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,21 +1,25 @@
import * as vscode from 'vscode';
import * as vscode from "vscode";
import { TelemetryContext } from '../../telemetry';
import { AnsiCCodeGenerator } from './Interfaces/AnsiCCodeGenerator';
import { CodeGenerator, CodeGenLanguage } from './Interfaces/CodeGenerator';
import { CodeGeneratorFactory } from './Interfaces/CodeGeneratorFactory';
import { TelemetryContext } from "../../telemetry";
import { AnsiCCodeGenerator } from "./Interfaces/AnsiCCodeGenerator";
import { CodeGenerator, CodeGenLanguage } from "./Interfaces/CodeGenerator";
import { CodeGeneratorFactory } from "./Interfaces/CodeGeneratorFactory";
export class AnsiCCodeGeneratorFactory implements CodeGeneratorFactory {
constructor(
private context: vscode.ExtensionContext,
private channel: vscode.OutputChannel,
private telemetryContext: TelemetryContext) {}
createCodeGeneratorImpl(language: string): CodeGenerator|null {
private context: vscode.ExtensionContext,
private channel: vscode.OutputChannel,
private telemetryContext: TelemetryContext
) {}
createCodeGeneratorImpl(language: string): CodeGenerator | null {
if (language === CodeGenLanguage.ANSIC.toString()) {
return new AnsiCCodeGenerator(
this.context, this.channel, this.telemetryContext);
this.context,
this.channel,
this.telemetryContext
);
} else {
return null;
}
}
}
}

Просмотреть файл

@ -1,33 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
"use strict";
import * as utils from '../../utils';
import * as vscode from 'vscode';
import { CodeGenExecutionItem } from './Interfaces/CodeGenerator';
import * as utils from "../../utils";
import * as vscode from "vscode";
import { CodeGenExecutionItem } from "./Interfaces/CodeGenerator";
export class CodeGenUtility {
static printCodeGenConfig(codeGenExecutionItem: CodeGenExecutionItem, channel: vscode.OutputChannel): void {
static printCodeGenConfig(
codeGenExecutionItem: CodeGenExecutionItem,
channel: vscode.OutputChannel
): void {
utils.channelShowAndAppendLine(
channel,
`Device capability model file: ${
codeGenExecutionItem.capabilityModelFilePath}`);
utils.channelShowAndAppendLine(
channel, `Project name: ${codeGenExecutionItem.projectName}`);
utils.channelShowAndAppendLine(
channel, `Language: ${codeGenExecutionItem.languageLabel}`);
`Device capability model file: ${codeGenExecutionItem.capabilityModelFilePath}`
);
utils.channelShowAndAppendLine(
channel,
`Device connection type: ${codeGenExecutionItem.deviceConnectionType}`);
utils.channelShowAndAppendLine(
channel, `Project type: ${codeGenExecutionItem.codeGenProjectType}`);
`Project name: ${codeGenExecutionItem.projectName}`
);
utils.channelShowAndAppendLine(
channel,
`Device SDK reference type: ${
codeGenExecutionItem.deviceSdkReferenceType}`);
`Language: ${codeGenExecutionItem.languageLabel}`
);
utils.channelShowAndAppendLine(
channel,
`Project output directory: ${codeGenExecutionItem.outputDirectory}`);
`Device connection type: ${codeGenExecutionItem.deviceConnectionType}`
);
utils.channelShowAndAppendLine(
channel,
`Project type: ${codeGenExecutionItem.codeGenProjectType}`
);
utils.channelShowAndAppendLine(
channel,
`Device SDK reference type: ${codeGenExecutionItem.deviceSdkReferenceType}`
);
utils.channelShowAndAppendLine(
channel,
`Project output directory: ${codeGenExecutionItem.outputDirectory}`
);
}
}
}

Просмотреть файл

@ -1,22 +1,27 @@
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import * as os from "os";
import * as path from "path";
import * as vscode from "vscode";
import { VscodeCommands } from '../../../common/Commands';
import { OSPlatform, ScaffoldType } from '../../../constants';
import { OpenScenario } from '../../../Models/IoTWorkbenchProjectBase';
import { IoTWorkspaceProject } from '../../../Models/IoTWorkspaceProject';
import { TelemetryContext } from '../../../telemetry';
import * as utils from '../../../utils';
import { DigitalTwinConstants } from '../../DigitalTwinConstants';
import { VscodeCommands } from "../../../common/Commands";
import { OSPlatform, ScaffoldType } from "../../../constants";
import { OpenScenario } from "../../../Models/IoTWorkbenchProjectBase";
import { IoTWorkspaceProject } from "../../../Models/IoTWorkspaceProject";
import { TelemetryContext } from "../../../telemetry";
import * as utils from "../../../utils";
import { DigitalTwinConstants } from "../../DigitalTwinConstants";
import { CodeGenerator, CodeGenExecutionItem, CodeGenProjectType } from './CodeGenerator';
import {
CodeGenerator,
CodeGenExecutionItem,
CodeGenProjectType
} from "./CodeGenerator";
export class AnsiCCodeGenerator implements CodeGenerator {
constructor(
protected context: vscode.ExtensionContext,
protected channel: vscode.OutputChannel,
protected telemetryContext: TelemetryContext) {}
protected context: vscode.ExtensionContext,
protected channel: vscode.OutputChannel,
protected telemetryContext: TelemetryContext
) {}
async generateCode(codegenInfo: CodeGenExecutionItem): Promise<boolean> {
// Invoke PnP toolset to generate the code
@ -25,26 +30,36 @@ export class AnsiCCodeGenerator implements CodeGenerator {
if (codegenSucceeded) {
if (codegenInfo.codeGenProjectType === CodeGenProjectType.IoTDevKit) {
const project: IoTWorkspaceProject = new IoTWorkspaceProject(
this.context, this.channel, this.telemetryContext,
codegenInfo.outputDirectory);
this.context,
this.channel,
this.telemetryContext,
codegenInfo.outputDirectory
);
project.openProject(
ScaffoldType.Local, true, OpenScenario.createNewProject);
ScaffoldType.Local,
true,
OpenScenario.createNewProject
);
} else {
await vscode.commands.executeCommand(
VscodeCommands.VscodeOpenFolder,
vscode.Uri.file(codegenInfo.outputDirectory), true);
vscode.Uri.file(codegenInfo.outputDirectory),
true
);
}
return true;
} else {
vscode.window.showErrorMessage(
'Unable to generate code, please check output window for detail.');
"Unable to generate code, please check output window for detail."
);
return false;
}
}
async generateAnsiCCodeCore(codegenInfo: CodeGenExecutionItem):
Promise<boolean> {
async generateAnsiCCodeCore(
codegenInfo: CodeGenExecutionItem
): Promise<boolean> {
// Invoke DigitalTwinCodeGen toolset to generate the code
const projectTypeValue = codegenInfo.codeGenProjectType.toString();
const connectionTypeValue = codegenInfo.deviceConnectionType.toString();
@ -58,29 +73,26 @@ export class AnsiCCodeGenerator implements CodeGenerator {
const platform = os.platform();
const homeDir = os.homedir();
const cmdPath = path.join(homeDir, DigitalTwinConstants.codeGenCliFolder);
let codeGenCommand = '';
let codeGenCommand = "";
if (platform === OSPlatform.WIN32) {
codeGenCommand = `${DigitalTwinConstants.codeGenCliApp}.exe`;
} else {
codeGenCommand = `./${DigitalTwinConstants.codeGenCliApp}`;
}
const command = `${codeGenCommand} generate -d "${dcmFilePath}" -i "${
interfaceDir}" -p "${projectTypeValue}" -c "${
connectionTypeValue}" -r "${sdkReferenceTypeValue}" -l ansic -o "${
outputDir}" -n "${projectName}"`;
const command = `${codeGenCommand} generate -d "${dcmFilePath}" -i "${interfaceDir}" \
-p "${projectTypeValue}" -c "${connectionTypeValue}" \
-r "${sdkReferenceTypeValue}" -l ansic -o "${outputDir}" -n "${projectName}"`;
let message: string;
try {
await utils.runCommand(command, [], cmdPath, this.channel);
message = `${
DigitalTwinConstants.dtPrefix} generate PnP device code completed.`;
message = `${DigitalTwinConstants.dtPrefix} generate PnP device code completed.`;
utils.channelShowAndAppendLine(this.channel, message);
return true;
} catch {
message =
`${DigitalTwinConstants.dtPrefix} generate PnP device code failed.`;
message = `${DigitalTwinConstants.dtPrefix} generate PnP device code failed.`;
utils.channelShowAndAppendLine(this.channel, message);
return false;
}

Просмотреть файл

@ -1,30 +1,30 @@
export enum CodeGenLanguage {
ANSIC = 'ANSI C'
ANSIC = "ANSI C"
}
export enum DeviceConnectionType {
ConnectionString = 'ConnectionString',
DpsSasKey = 'DpsSasKey',
IoTCX509 = 'IoTCX509'
ConnectionString = "ConnectionString",
DpsSasKey = "DpsSasKey",
IoTCX509 = "IoTCX509"
}
export enum CodeGenProjectType {
CMakeWindows = 'CMake_Windows',
CMakeLinux = 'CMake_Linux',
VisualStudio = 'VisualStudio',
IoTDevKit = 'IoTDevKit'
CMakeWindows = "CMake_Windows",
CMakeLinux = "CMake_Linux",
VisualStudio = "VisualStudio",
IoTDevKit = "IoTDevKit"
}
export enum DeviceSdkReferenceType {
Vcpkg = 'Vcpkg',
SourceCode = 'SourceCode',
DevKitSDK = 'DevKitSDK'
Vcpkg = "Vcpkg",
SourceCode = "SourceCode",
DevKitSDK = "DevKitSDK"
}
export enum CodeGenPlatform {
Windows = 'Windows',
Linux = 'Linux',
MacOS = 'MacOS'
Windows = "Windows",
Linux = "Linux",
MacOS = "MacOS"
}
export interface CodeGenExecutionItem {

Просмотреть файл

@ -1,8 +1,14 @@
import { CodeGenerator, CodeGenProjectType, DeviceConnectionType, DeviceSdkReferenceType } from './CodeGenerator';
import {
CodeGenerator,
CodeGenProjectType,
DeviceConnectionType,
DeviceSdkReferenceType
} from "./CodeGenerator";
export interface CodeGeneratorFactory {
createCodeGeneratorImpl(
projectType: CodeGenProjectType, sdkReferenceType: DeviceSdkReferenceType,
connectionType: DeviceConnectionType): CodeGenerator|null;
projectType: CodeGenProjectType,
sdkReferenceType: DeviceSdkReferenceType,
connectionType: DeviceConnectionType
): CodeGenerator | null;
}

Просмотреть файл

@ -2,15 +2,16 @@
// Licensed under the MIT License.
export class DigitalTwinConstants {
static readonly dtPrefix = '[IoT Plug and Play]';
static readonly codeGenCli = 'IoT Plug and Play CodeGen CLI';
static readonly codeGenCliFolder = 'iotpnp-codegen';
static readonly codeGenCliApp = 'dtcodegen';
static readonly codegenProjectNameRegex =
new RegExp('^[a-zA-Z_][-a-zA-Z0-9_]*$');
static readonly dtPrefix = "[IoT Plug and Play]";
static readonly codeGenCli = "IoT Plug and Play CodeGen CLI";
static readonly codeGenCliFolder = "iotpnp-codegen";
static readonly codeGenCliApp = "dtcodegen";
static readonly codegenProjectNameRegex = new RegExp(
"^[a-zA-Z_][-a-zA-Z0-9_]*$"
);
static readonly codegenProjectNameRegexDescription =
'alphanumeric, underscore and dash character, and cannot start with number and dash character';
static readonly codeGenProjectTypeSeperator = '-';
static readonly cmakeListsFileName = 'CMakeLists.txt';
static readonly codeGenConfigFileName = '.codeGenConfigs';
"alphanumeric, underscore and dash character, and cannot start with number and dash character";
static readonly codeGenProjectTypeSeperator = "-";
static readonly cmakeListsFileName = "CMakeLists.txt";
static readonly codeGenConfigFileName = ".codeGenConfigs";
}

Просмотреть файл

@ -1,21 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
"use strict";
import * as vscode from 'vscode';
import * as utils from '../utils';
import { DigitalTwinConstants } from './DigitalTwinConstants';
import { CancelOperationError } from '../CancelOperationError';
import { ModelRepositoryManager } from './pnp/src/modelRepository/modelRepositoryManager';
import { ApiProvider } from './pnp/src/api/apiProvider';
import * as vscode from "vscode";
import * as utils from "../utils";
import { DigitalTwinConstants } from "./DigitalTwinConstants";
import { CancelOperationError } from "../CancelOperationError";
import { ModelRepositoryManager } from "./pnp/src/modelRepository/modelRepositoryManager";
import { ApiProvider } from "./pnp/src/api/apiProvider";
/**
* Digital Twin extension utility
*/
export class DigitalTwinUtility {
private static readonly EXTENSION_NOT_INIT =
'Azure Digital Twin extension is not inititalized';
"Azure Digital Twin extension is not inititalized";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static extensionInstance: any;
private static channel: vscode.OutputChannel;
@ -26,9 +26,11 @@ export class DigitalTwinUtility {
*/
static init(
modelRepositoryManager: ModelRepositoryManager,
channel: vscode.OutputChannel): void {
DigitalTwinUtility.extensionInstance =
new ApiProvider(modelRepositoryManager);
channel: vscode.OutputChannel
): void {
DigitalTwinUtility.extensionInstance = new ApiProvider(
modelRepositoryManager
);
DigitalTwinUtility.channel = channel;
}
@ -39,23 +41,22 @@ export class DigitalTwinUtility {
if (!DigitalTwinUtility.extensionInstance) {
throw new Error(DigitalTwinUtility.EXTENSION_NOT_INIT);
}
let result = '';
let result = "";
try {
result =
await DigitalTwinUtility.extensionInstance.selectCapabilityModel();
result = await DigitalTwinUtility.extensionInstance.selectCapabilityModel();
} catch {
// skip for UserCancelledError
}
if (!result) {
throw new CancelOperationError(
`Selected device capability model file cancelled.`);
`Selected device capability model file cancelled.`
);
}
utils.channelShowAndAppendLine(
DigitalTwinUtility.channel,
`${
DigitalTwinConstants
.dtPrefix} Selected device capability model file: ${result}`);
`${DigitalTwinConstants.dtPrefix} Selected device capability model file: ${result}`
);
return result;
}
@ -66,19 +67,24 @@ export class DigitalTwinUtility {
* @param capabilityModelFile capability model file path
*/
static async downloadDependentInterface(
folder: string, capabilityModelFile: string): Promise<boolean> {
folder: string,
capabilityModelFile: string
): Promise<boolean> {
if (!DigitalTwinUtility.extensionInstance) {
throw new Error(DigitalTwinUtility.EXTENSION_NOT_INIT);
}
try {
await DigitalTwinUtility.extensionInstance.downloadDependentInterface(
folder, capabilityModelFile);
folder,
capabilityModelFile
);
} catch (error) {
utils.channelShowAndAppendLine(
DigitalTwinUtility.channel,
`${DigitalTwinConstants.dtPrefix} ${error.message}`);
`${DigitalTwinConstants.dtPrefix} ${error.message}`
);
return false;
}
return true;
}
}
}

Просмотреть файл

@ -10,13 +10,18 @@ import { UIConstants } from "../view/uiConstants";
* Api provider for extension integration
*/
export class ApiProvider {
constructor(private readonly modelRepositoryManager: ModelRepositoryManager) {}
constructor(
private readonly modelRepositoryManager: ModelRepositoryManager
) {}
/**
* select capability model
*/
async selectCapabilityModel(): Promise<string> {
return await UI.selectOneModelFile(UIConstants.SELECT_CAPABILITY_MODEL_LABEL, ModelType.CapabilityModel);
return await UI.selectOneModelFile(
UIConstants.SELECT_CAPABILITY_MODEL_LABEL,
ModelType.CapabilityModel
);
}
/**
@ -24,7 +29,13 @@ export class ApiProvider {
* @param folder folder to download interface
* @param capabilityModelFile capability model file path
*/
async downloadDependentInterface(folder: string, capabilityModelFile: string): Promise<void> {
await this.modelRepositoryManager.downloadDependentInterface(folder, capabilityModelFile);
async downloadDependentInterface(
folder: string,
capabilityModelFile: string
): Promise<void> {
await this.modelRepositoryManager.downloadDependentInterface(
folder,
capabilityModelFile
);
}
}

Просмотреть файл

@ -14,7 +14,8 @@ export class ColorizedChannel {
*/
static formatMessage(operation: string, error?: Error): string {
if (error) {
const message: string = operation.charAt(0).toLowerCase() + operation.slice(1);
const message: string =
operation.charAt(0).toLowerCase() + operation.slice(1);
return `Fail to ${message}. Error: ${error.message}`;
} else {
return `${operation} successfully`;
@ -51,7 +52,9 @@ export class ColorizedChannel {
*/
end(operation: string, component?: string): void {
const tag: string = ColorizedChannel.createTag(component);
this.channel.appendLine(`[Done]${tag} ${ColorizedChannel.formatMessage(operation)}`);
this.channel.appendLine(
`[Done]${tag} ${ColorizedChannel.formatMessage(operation)}`
);
}
/**
@ -80,7 +83,9 @@ export class ColorizedChannel {
*/
error(operation: string, component?: string, error?: Error): void {
const tag: string = ColorizedChannel.createTag(component);
const message: string = error ? ColorizedChannel.formatMessage(operation, error) : operation;
const message: string = error
? ColorizedChannel.formatMessage(operation, error)
: operation;
this.channel.appendLine(`[Error]${tag} ${message}`);
}

Просмотреть файл

@ -13,5 +13,5 @@ export enum Command {
SearchCapabilityModel = "azure-digital-twins.searchCapabilityModel",
SubmitFiles = "azure-digital-twins.submitFiles",
DeleteModels = "azure-digital-twins.deleteModels",
DownloadModels = "azure-digital-twins.downloadModels",
DownloadModels = "azure-digital-twins.downloadModels"
}

Просмотреть файл

@ -37,7 +37,7 @@ export class Configuration {
}
private static readonly instance: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(
Constants.EXTENSION_NAME,
Constants.EXTENSION_NAME
);
private constructor() {}
}

Просмотреть файл

@ -29,24 +29,30 @@ export class Constants {
static readonly DEVICE_MODEL_COMPONENT = "Device Model";
static readonly MODEL_REPOSITORY_COMPONENT = "Model Repository";
static readonly MODEL_REPOSITORY_CONNECTION_KEY = "ModelRepositoryConnectionKey";
static readonly MODEL_REPOSITORY_CONNECTION_KEY =
"ModelRepositoryConnectionKey";
static readonly MODEL_REPOSITORY_API_VERSION = "2019-07-01-Preview";
static readonly URL_PROTOCAL_REGEX = new RegExp("^[a-zA-Z]+://");
static readonly HTTPS_PROTOCAL = "https://";
static readonly MODEL_NAME_REGEX = new RegExp("^[a-zA-Z_][a-zA-Z0-9_]*$");
static readonly MODEL_NAME_REGEX_DESCRIPTION = "alphanumeric and underscore, not start with number";
static readonly MODEL_NAME_REGEX_DESCRIPTION =
"alphanumeric and underscore, not start with number";
static readonly DIGITAL_TWIN_ID_PLACEHOLDER = "{DigitalTwinIdentifier}";
static readonly EXTENSION_ACTIVATED_MSG = "extensionActivated";
static readonly NOT_EMPTY_MSG = "could not be empty";
static readonly CONNECTION_STRING_NOT_FOUND_MSG =
"Company repository connection string is not found. Please sign out and sign in with a valid connection string";
static readonly PUBLIC_REPOSITORY_URL_NOT_FOUND_MSG = "Public repository url is not found";
static readonly CONNECTION_STRING_INVALID_FORMAT_MSG = "Invalid connection string format";
static readonly PUBLIC_REPOSITORY_URL_NOT_FOUND_MSG =
"Public repository url is not found";
static readonly CONNECTION_STRING_INVALID_FORMAT_MSG =
"Invalid connection string format";
static readonly MODEL_TYPE_INVALID_MSG = "Invalid model type";
static readonly NEED_OPEN_COMPANY_REPOSITORY_MSG = "Please open company repository and try again";
static readonly NEED_OPEN_COMPANY_REPOSITORY_MSG =
"Please open company repository and try again";
static readonly NSAT_SURVEY_URL = "https://aka.ms/vscode-iot-workbench-survey";
static readonly NSAT_SURVEY_URL =
"https://aka.ms/vscode-iot-workbench-survey";
static readonly WEB_VIEW_PATH = "assets/modelRepository";
static readonly COMPANY_REPOSITORY_PAGE = "index.html";
static readonly PUBLIC_REPOSITORY_PAGE = "index.html?public";

Просмотреть файл

@ -4,7 +4,10 @@
import { createHash } from "crypto";
import * as fs from "fs-extra";
import * as path from "path";
import { DeviceModelManager, ModelType } from "../deviceModel/deviceModelManager";
import {
DeviceModelManager,
ModelType
} from "../deviceModel/deviceModelManager";
import { DigitalTwinConstants } from "../intelliSense/digitalTwinConstants";
import { ModelFileInfo } from "../modelRepository/modelRepositoryManager";
import { Constants } from "./constants";
@ -22,12 +25,15 @@ export class Utility {
static async createFileFromTemplate(
templatePath: string,
filePath: string,
replacement: Map<string, string>,
replacement: Map<string, string>
): Promise<void> {
const template: string = await fs.readFile(templatePath, Constants.UTF8);
const content: string = Utility.replaceAll(template, replacement);
const jsonContent = JSON.parse(content);
await fs.writeJson(filePath, jsonContent, { spaces: Constants.JSON_SPACE, encoding: Constants.UTF8 });
await fs.writeJson(filePath, jsonContent, {
spaces: Constants.JSON_SPACE,
encoding: Constants.UTF8
});
}
/**
@ -36,11 +42,15 @@ export class Utility {
* @param replacement replacement
* @param caseInsensitive identify if it is case insensitive
*/
static replaceAll(str: string, replacement: Map<string, string>, caseInsensitive = false): string {
static replaceAll(
str: string,
replacement: Map<string, string>,
caseInsensitive = false
): string {
const flag = caseInsensitive ? "ig" : "g";
const keys = Array.from(replacement.keys());
const pattern = new RegExp(keys.join("|"), flag);
return str.replace(pattern, (matched) => {
return str.replace(pattern, matched => {
const value: string | undefined = replacement.get(matched);
return value || matched;
});
@ -52,14 +62,21 @@ export class Utility {
* @param type model type
* @param folder target folder
*/
static async validateModelName(name: string, type: ModelType, folder: string): Promise<string | undefined> {
static async validateModelName(
name: string,
type: ModelType,
folder: string
): Promise<string | undefined> {
if (!name || name.trim() === Constants.EMPTY_STRING) {
return `Name ${Constants.NOT_EMPTY_MSG}`;
}
if (!Constants.MODEL_NAME_REGEX.test(name)) {
return `Name can only contain ${Constants.MODEL_NAME_REGEX_DESCRIPTION}`;
}
const filename: string = DeviceModelManager.generateModelFileName(name, type);
const filename: string = DeviceModelManager.generateModelFileName(
name,
type
);
if (await fs.pathExists(path.join(folder, filename))) {
return `${type} ${name} already exists in folder ${folder}`;
}
@ -71,7 +88,10 @@ export class Utility {
* @param name name
* @param placeholder placeholder for message
*/
static validateNotEmpty(name: string, placeholder: string): string | undefined {
static validateNotEmpty(
name: string,
placeholder: string
): string | undefined {
if (!name || name.trim() === Constants.EMPTY_STRING) {
return `${placeholder} ${Constants.NOT_EMPTY_MSG}`;
}
@ -94,16 +114,25 @@ export class Utility {
* @param modelId model id
* @param content model content
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static async createModelFile(folder: string, modelId: string, content: any): Promise<void> {
const type: ModelType = DeviceModelManager.convertToModelType(content[DigitalTwinConstants.TYPE]);
static async createModelFile(
folder: string,
modelId: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
content: any
): Promise<void> {
const type: ModelType = DeviceModelManager.convertToModelType(
content[DigitalTwinConstants.TYPE]
);
if (!type) {
throw new Error(Constants.MODEL_TYPE_INVALID_MSG);
}
const replacement = new Map<string, string>();
replacement.set(":", "_");
const modelName: string = Utility.replaceAll(modelId, replacement);
let candidate: string = DeviceModelManager.generateModelFileName(modelName, type);
let candidate: string = DeviceModelManager.generateModelFileName(
modelName,
type
);
let counter = 0;
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (true) {
@ -111,11 +140,14 @@ export class Utility {
break;
}
counter++;
candidate = DeviceModelManager.generateModelFileName(`${modelName}_${counter}`, type);
candidate = DeviceModelManager.generateModelFileName(
`${modelName}_${counter}`,
type
);
}
await fs.writeJson(path.join(folder, candidate), content, {
spaces: Constants.JSON_SPACE,
encoding: Constants.UTF8,
encoding: Constants.UTF8
});
}
@ -123,16 +155,20 @@ export class Utility {
* get model file info
* @param filePath file path
*/
static async getModelFileInfo(filePath: string): Promise<ModelFileInfo | undefined> {
static async getModelFileInfo(
filePath: string
): Promise<ModelFileInfo | undefined> {
const content = await Utility.getJsonContent(filePath);
const modelId: string = content[DigitalTwinConstants.ID];
const context: string = content[DigitalTwinConstants.CONTEXT];
const modelType: ModelType = DeviceModelManager.convertToModelType(content[DigitalTwinConstants.TYPE]);
const modelType: ModelType = DeviceModelManager.convertToModelType(
content[DigitalTwinConstants.TYPE]
);
if (modelId && context && modelType) {
return {
id: modelId,
type: modelType,
filePath,
filePath
};
}
return undefined;

Просмотреть файл

@ -15,7 +15,7 @@ import { UIConstants } from "../view/uiConstants";
*/
export enum ModelType {
Interface = "Interface",
CapabilityModel = "Capability Model",
CapabilityModel = "Capability Model"
}
/**
@ -44,7 +44,9 @@ export class DeviceModelManager {
* @param type model type
*/
static generateModelFileName(name: string, type: ModelType): string {
const fileType: string = type.replace(/\s+/g, Constants.EMPTY_STRING).toLowerCase();
const fileType: string = type
.replace(/\s+/g, Constants.EMPTY_STRING)
.toLowerCase();
return `${name}.${fileType}.json`;
}
@ -53,11 +55,17 @@ export class DeviceModelManager {
* @param type model type
*/
static getTemplateFileName(type: ModelType): string {
return DeviceModelManager.generateModelFileName(Constants.SAMPLE_FILE_NAME, type);
return DeviceModelManager.generateModelFileName(
Constants.SAMPLE_FILE_NAME,
type
);
}
private readonly component: string;
constructor(private readonly context: vscode.ExtensionContext, private readonly outputChannel: ColorizedChannel) {
constructor(
private readonly context: vscode.ExtensionContext,
private readonly outputChannel: ColorizedChannel
) {
this.component = Constants.DEVICE_MODEL_COMPONENT;
}
@ -66,8 +74,14 @@ export class DeviceModelManager {
* @param type model type
*/
async createModel(type: ModelType): Promise<void> {
const folder: string = await UI.selectRootFolder(UIConstants.SELECT_ROOT_FOLDER_LABEL);
const name: string = await UI.inputModelName(UIConstants.INPUT_MODEL_NAME_LABEL, type, folder);
const folder: string = await UI.selectRootFolder(
UIConstants.SELECT_ROOT_FOLDER_LABEL
);
const name: string = await UI.inputModelName(
UIConstants.INPUT_MODEL_NAME_LABEL,
type,
folder
);
const operation = `Create ${type} ${name} in folder ${folder}`;
this.outputChannel.start(operation, this.component);
@ -79,7 +93,10 @@ export class DeviceModelManager {
}
await UI.openAndShowTextDocument(filePath);
UI.showNotification(MessageType.Info, ColorizedChannel.formatMessage(operation));
UI.showNotification(
MessageType.Info,
ColorizedChannel.formatMessage(operation)
);
this.outputChannel.end(operation, this.component);
}
@ -89,11 +106,22 @@ export class DeviceModelManager {
* @param folder root folder
* @param name model name
*/
private async doCreateModel(type: ModelType, folder: string, name: string): Promise<string> {
private async doCreateModel(
type: ModelType,
folder: string,
name: string
): Promise<string> {
const modelId: string = DeviceModelManager.generateModelId(name);
const filePath: string = path.join(folder, DeviceModelManager.generateModelFileName(name, type));
const filePath: string = path.join(
folder,
DeviceModelManager.generateModelFileName(name, type)
);
const templatePath: string = this.context.asAbsolutePath(
path.join(Constants.RESOURCE_FOLDER, Constants.TEMPLATE_FOLDER, DeviceModelManager.getTemplateFileName(type)),
path.join(
Constants.RESOURCE_FOLDER,
Constants.TEMPLATE_FOLDER,
DeviceModelManager.getTemplateFileName(type)
)
);
const replacement = new Map<string, string>();
replacement.set(Constants.DIGITAL_TWIN_ID_PLACEHOLDER, modelId);

Просмотреть файл

@ -5,27 +5,40 @@ import * as parser from "jsonc-parser";
import * as vscode from "vscode";
import { Constants } from "../common/constants";
import { DigitalTwinConstants } from "./digitalTwinConstants";
import { ClassNode, DigitalTwinGraph, PropertyNode, ValueSchema } from "./digitalTwinGraph";
import { IntelliSenseUtility, JsonNodeType, PropertyPair } from "./intelliSenseUtility";
import {
ClassNode,
DigitalTwinGraph,
PropertyNode,
ValueSchema
} from "./digitalTwinGraph";
import {
IntelliSenseUtility,
JsonNodeType,
PropertyPair
} from "./intelliSenseUtility";
import { LANGUAGE_CODE } from "./languageCode";
/**
* Completion item provider for DigitalTwin IntelliSense
*/
export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemProvider {
export class DigitalTwinCompletionItemProvider
implements vscode.CompletionItemProvider {
/**
* get text for json parser after completion
* @param document text document
* @param position position
*/
private static getTextForParse(document: vscode.TextDocument, position: vscode.Position): string {
private static getTextForParse(
document: vscode.TextDocument,
position: vscode.Position
): string {
const text: string = document.getText();
const offset: number = document.offsetAt(position);
if (text[offset] === Constants.COMPLETION_TRIGGER) {
const edit: parser.Edit = {
offset,
length: 1,
content: Constants.COMPLETION_TRIGGER + Constants.DEFAULT_SEPARATOR,
content: Constants.COMPLETION_TRIGGER + Constants.DEFAULT_SEPARATOR
};
return parser.applyEdits(text, [edit]);
}
@ -45,17 +58,21 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
isProperty: boolean,
insertText: string,
position: vscode.Position,
range: vscode.Range,
range: vscode.Range
): vscode.CompletionItem {
const completionItem: vscode.CompletionItem = {
label,
kind: isProperty ? vscode.CompletionItemKind.Property : vscode.CompletionItemKind.Value,
kind: isProperty
? vscode.CompletionItemKind.Property
: vscode.CompletionItemKind.Value,
insertText: new vscode.SnippetString(insertText),
// the start of range should not be before position, otherwise completion item will not be shown
range: new vscode.Range(position, range.end),
range: new vscode.Range(position, range.end)
};
if (position.isAfter(range.start)) {
completionItem.additionalTextEdits = [vscode.TextEdit.delete(new vscode.Range(range.start, position))];
completionItem.additionalTextEdits = [
vscode.TextEdit.delete(new vscode.Range(range.start, position))
];
}
return completionItem;
}
@ -69,13 +86,20 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
private static evaluateOverwriteRange(
document: vscode.TextDocument,
position: vscode.Position,
node: parser.Node,
node: parser.Node
): vscode.Range {
let range: vscode.Range;
if (node.type === JsonNodeType.String || node.type === JsonNodeType.Number || node.type === JsonNodeType.Boolean) {
if (
node.type === JsonNodeType.String ||
node.type === JsonNodeType.Number ||
node.type === JsonNodeType.Boolean
) {
range = IntelliSenseUtility.getNodeRange(document, node);
} else {
const word: string = DigitalTwinCompletionItemProvider.getCurrentWord(document, position);
const word: string = DigitalTwinCompletionItemProvider.getCurrentWord(
document,
position
);
const start: number = document.offsetAt(position) - word.length;
range = new vscode.Range(document.positionAt(start), position);
}
@ -87,10 +111,16 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
* @param document text document
* @param position position
*/
private static getCurrentWord(document: vscode.TextDocument, position: vscode.Position): string {
private static getCurrentWord(
document: vscode.TextDocument,
position: vscode.Position
): string {
let i: number = position.character - 1;
const text: string = document.lineAt(position.line).text;
while (i >= 0 && DigitalTwinConstants.WORD_STOP.indexOf(text.charAt(i)) === -1) {
while (
i >= 0 &&
DigitalTwinConstants.WORD_STOP.indexOf(text.charAt(i)) === -1
) {
i--;
}
return text.substring(i + 1, position.character);
@ -106,13 +136,13 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
scanner.setPosition(offset);
const token: parser.SyntaxKind = scanner.scan();
switch (token) {
case parser.SyntaxKind.CommaToken:
case parser.SyntaxKind.CloseBraceToken:
case parser.SyntaxKind.CloseBracketToken:
case parser.SyntaxKind.EOF:
return Constants.EMPTY_STRING;
default:
return Constants.DEFAULT_SEPARATOR;
case parser.SyntaxKind.CommaToken:
case parser.SyntaxKind.CloseBraceToken:
case parser.SyntaxKind.CloseBracketToken:
case parser.SyntaxKind.EOF:
return Constants.EMPTY_STRING;
default:
return Constants.DEFAULT_SEPARATOR;
}
}
@ -129,11 +159,16 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
position: vscode.Position,
range: vscode.Range,
includeValue: boolean,
separator: string,
separator: string
): vscode.CompletionItem[] {
const completionItems: vscode.CompletionItem[] = [];
const exist = new Set<string>();
const classNode: ClassNode | undefined = DigitalTwinCompletionItemProvider.getObjectType(node, exist);
const classNode:
| ClassNode
| undefined = DigitalTwinCompletionItemProvider.getObjectType(
node,
exist
);
let dummyNode: PropertyNode;
if (!classNode) {
// there are two cases when classNode is not defined
@ -146,10 +181,14 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
DigitalTwinCompletionItemProvider.createCompletionItem(
`${dummyNode.id} ${DigitalTwinConstants.REQUIRED_PROPERTY_LABEL}`,
true,
DigitalTwinCompletionItemProvider.getInsertTextForProperty(dummyNode, includeValue, separator),
DigitalTwinCompletionItemProvider.getInsertTextForProperty(
dummyNode,
includeValue,
separator
),
position,
range,
),
range
)
);
}
} else if (IntelliSenseUtility.isLanguageNode(classNode)) {
@ -163,10 +202,14 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
DigitalTwinCompletionItemProvider.createCompletionItem(
code,
true,
DigitalTwinCompletionItemProvider.getInsertTextForProperty(dummyNode, includeValue, separator),
DigitalTwinCompletionItemProvider.getInsertTextForProperty(
dummyNode,
includeValue,
separator
),
position,
range,
),
range
)
);
}
} else if (classNode.properties) {
@ -180,12 +223,19 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
}
completionItems.push(
DigitalTwinCompletionItemProvider.createCompletionItem(
DigitalTwinCompletionItemProvider.formatLabel(child.label, required),
DigitalTwinCompletionItemProvider.formatLabel(
child.label,
required
),
true,
DigitalTwinCompletionItemProvider.getInsertTextForProperty(child, includeValue, separator),
DigitalTwinCompletionItemProvider.getInsertTextForProperty(
child,
includeValue,
separator
),
position,
range,
),
range
)
);
}
const suggestion: vscode.CompletionItem[] = DigitalTwinCompletionItemProvider.suggestReservedProperty(
@ -194,7 +244,7 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
includeValue,
separator,
exist,
required,
required
);
completionItems.push(...suggestion);
}
@ -206,7 +256,10 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
* @param node json node
* @param exist existing properties
*/
private static getObjectType(node: parser.Node, exist: Set<string>): ClassNode | undefined {
private static getObjectType(
node: parser.Node,
exist: Set<string>
): ClassNode | undefined {
const parent: parser.Node | undefined = node.parent;
if (!parent || parent.type !== JsonNodeType.Object || !parent.children) {
return undefined;
@ -228,13 +281,21 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
if (propertyName === DigitalTwinConstants.TYPE) {
const propertyValue: parser.Node = propertyPair.value;
if (propertyValue.type === JsonNodeType.String) {
objectType = IntelliSenseUtility.getClasNode(propertyValue.value as string);
} else if (propertyValue.type === JsonNodeType.Array && propertyValue.children) {
objectType = IntelliSenseUtility.getClasNode(
propertyValue.value as string
);
} else if (
propertyValue.type === JsonNodeType.Array &&
propertyValue.children
) {
// support semantic type array
for (const element of propertyValue.children) {
if (element.type === JsonNodeType.String) {
const type: string = element.value as string;
if (type && DigitalTwinConstants.SUPPORT_SEMANTIC_TYPES.has(type)) {
if (
type &&
DigitalTwinConstants.SUPPORT_SEMANTIC_TYPES.has(type)
) {
objectType = IntelliSenseUtility.getClasNode(type);
}
}
@ -244,9 +305,15 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
}
// infer from outer property
if (!objectType) {
const propertyNode: PropertyNode | undefined = DigitalTwinCompletionItemProvider.getOuterPropertyNode(parent);
const propertyNode:
| PropertyNode
| undefined = DigitalTwinCompletionItemProvider.getOuterPropertyNode(
parent
);
if (propertyNode) {
const classes: ClassNode[] = IntelliSenseUtility.getObjectClasses(propertyNode);
const classes: ClassNode[] = IntelliSenseUtility.getObjectClasses(
propertyNode
);
if (classes.length === 1) {
objectType = classes[0];
}
@ -259,12 +326,18 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
* get outer DigitalTwin property node from current node
* @param node json node
*/
private static getOuterPropertyNode(node: parser.Node): PropertyNode | undefined {
const propertyPair: PropertyPair | undefined = IntelliSenseUtility.getOuterPropertyPair(node);
private static getOuterPropertyNode(
node: parser.Node
): PropertyNode | undefined {
const propertyPair:
| PropertyPair
| undefined = IntelliSenseUtility.getOuterPropertyPair(node);
if (!propertyPair) {
return undefined;
}
const propertyName: string = IntelliSenseUtility.resolvePropertyName(propertyPair);
const propertyName: string = IntelliSenseUtility.resolvePropertyName(
propertyPair
);
return IntelliSenseUtility.getPropertyNode(propertyName);
}
@ -274,7 +347,9 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
* @param required required properties
*/
private static formatLabel(label: string, required: Set<string>): string {
return required.has(label) ? `${label} ${DigitalTwinConstants.REQUIRED_PROPERTY_LABEL}` : label;
return required.has(label)
? `${label} ${DigitalTwinConstants.REQUIRED_PROPERTY_LABEL}`
: label;
}
/**
@ -292,11 +367,15 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
includeValue: boolean,
separator: string,
exist: Set<string>,
required: Set<string>,
required: Set<string>
): vscode.CompletionItem[] {
const completionItems: vscode.CompletionItem[] = [];
const properties: PropertyNode[] = [];
const propertyNode: PropertyNode | undefined = IntelliSenseUtility.getPropertyNode(DigitalTwinConstants.ID);
const propertyNode:
| PropertyNode
| undefined = IntelliSenseUtility.getPropertyNode(
DigitalTwinConstants.ID
);
if (propertyNode) {
properties.push(propertyNode);
}
@ -312,10 +391,14 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
DigitalTwinCompletionItemProvider.createCompletionItem(
DigitalTwinCompletionItemProvider.formatLabel(property.id, required),
true,
DigitalTwinCompletionItemProvider.getInsertTextForProperty(property, includeValue, separator),
DigitalTwinCompletionItemProvider.getInsertTextForProperty(
property,
includeValue,
separator
),
position,
range,
),
range
)
);
}
return completionItems;
@ -330,7 +413,7 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
private static getInsertTextForProperty(
propertyNode: PropertyNode,
includeValue: boolean,
separator: string,
separator: string
): string {
const name: string = propertyNode.label || propertyNode.id;
if (!includeValue) {
@ -345,16 +428,16 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
value = "{$1}";
} else if (!classNode.label) {
switch (classNode.id) {
case ValueSchema.String:
value = '"$1"';
break;
case ValueSchema.Int:
value = "${1:0}";
break;
case ValueSchema.Boolean:
value = "${1:false}";
break;
default:
case ValueSchema.String:
value = '"$1"';
break;
case ValueSchema.Int:
value = "${1:0}";
break;
case ValueSchema.Boolean:
value = "${1:false}";
break;
default:
}
}
}
@ -373,10 +456,12 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
node: parser.Node,
position: vscode.Position,
range: vscode.Range,
separator: string,
separator: string
): vscode.CompletionItem[] {
const completionItems: vscode.CompletionItem[] = [];
const propertyPair: PropertyPair | undefined = IntelliSenseUtility.parseProperty(node);
const propertyPair:
| PropertyPair
| undefined = IntelliSenseUtility.parseProperty(node);
if (!propertyPair) {
return completionItems;
}
@ -388,29 +473,38 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
DigitalTwinCompletionItemProvider.createCompletionItem(
DigitalTwinConstants.IOT_MODEL_LABEL,
false,
DigitalTwinCompletionItemProvider.getInsertTextForValue(DigitalTwinConstants.CONTEXT_TEMPLATE, separator),
DigitalTwinCompletionItemProvider.getInsertTextForValue(
DigitalTwinConstants.CONTEXT_TEMPLATE,
separator
),
position,
range,
),
range
)
);
} else if (propertyName === DigitalTwinConstants.TYPE) {
// suggest value of @type property
if (node.parent) {
// assign to entry node if the json object node is the top node
propertyNode =
DigitalTwinCompletionItemProvider.getOuterPropertyNode(node.parent) || IntelliSenseUtility.getEntryNode();
DigitalTwinCompletionItemProvider.getOuterPropertyNode(node.parent) ||
IntelliSenseUtility.getEntryNode();
if (propertyNode) {
const classes: ClassNode[] = IntelliSenseUtility.getObjectClasses(propertyNode);
const classes: ClassNode[] = IntelliSenseUtility.getObjectClasses(
propertyNode
);
for (const classNode of classes) {
const value: string = DigitalTwinGraph.getClassType(classNode);
completionItems.push(
DigitalTwinCompletionItemProvider.createCompletionItem(
value,
false,
DigitalTwinCompletionItemProvider.getInsertTextForValue(value, separator),
DigitalTwinCompletionItemProvider.getInsertTextForValue(
value,
separator
),
position,
range,
),
range
)
);
}
}
@ -426,10 +520,13 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
DigitalTwinCompletionItemProvider.createCompletionItem(
value,
false,
DigitalTwinCompletionItemProvider.getInsertTextForValue(value, separator),
DigitalTwinCompletionItemProvider.getInsertTextForValue(
value,
separator
),
position,
range,
),
range
)
);
}
}
@ -442,7 +539,10 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
* @param value property value
* @param separator separator after text
*/
private static getInsertTextForValue(value: string, separator: string): string {
private static getInsertTextForValue(
value: string,
separator: string
): string {
return `"${value}"${separator}`;
}
@ -453,25 +553,42 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
* @param token cancellation token
* @param context completion context
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, _token: vscode.CancellationToken, _context: vscode.CompletionContext,
provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_token: vscode.CancellationToken,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_context: vscode.CompletionContext
): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
const text: string = DigitalTwinCompletionItemProvider.getTextForParse(document, position);
const jsonNode: parser.Node | undefined = IntelliSenseUtility.parseDigitalTwinModel(text);
const text: string = DigitalTwinCompletionItemProvider.getTextForParse(
document,
position
);
const jsonNode:
| parser.Node
| undefined = IntelliSenseUtility.parseDigitalTwinModel(text);
if (!jsonNode) {
return undefined;
}
if (!IntelliSenseUtility.enabled()) {
return undefined;
}
const node: parser.Node | undefined = parser.findNodeAtOffset(jsonNode, document.offsetAt(position));
const node: parser.Node | undefined = parser.findNodeAtOffset(
jsonNode,
document.offsetAt(position)
);
if (!node || node.type !== JsonNodeType.String) {
return undefined;
}
const range: vscode.Range = DigitalTwinCompletionItemProvider.evaluateOverwriteRange(document, position, node);
const range: vscode.Range = DigitalTwinCompletionItemProvider.evaluateOverwriteRange(
document,
position,
node
);
const separator: string = DigitalTwinCompletionItemProvider.evaluateSeparatorAfter(
document.getText(),
document.offsetAt(range.end),
document.offsetAt(range.end)
);
const parent: parser.Node | undefined = node.parent;
if (!parent || parent.type !== JsonNodeType.Property || !parent.children) {
@ -479,9 +596,20 @@ export class DigitalTwinCompletionItemProvider implements vscode.CompletionItemP
}
if (node === parent.children[0]) {
const includeValue: boolean = parent.children.length < 2;
return DigitalTwinCompletionItemProvider.suggestProperty(parent, position, range, includeValue, separator);
return DigitalTwinCompletionItemProvider.suggestProperty(
parent,
position,
range,
includeValue,
separator
);
} else {
return DigitalTwinCompletionItemProvider.suggestValue(parent, position, range, separator);
return DigitalTwinCompletionItemProvider.suggestValue(
parent,
position,
range,
separator
);
}
}
}

Просмотреть файл

@ -22,7 +22,7 @@ export enum DiagnosticMessage {
InvalidEnum = "Invalid value. Valid values:",
InvalidContext = "Invalid context of DigitalTwin.",
ConflictType = "Conflict type:",
ValueNotString = "Value is not string.",
ValueNotString = "Value is not string."
}
/**
@ -55,7 +55,13 @@ export class DigitalTwinConstants {
static readonly WORD_STOP = ' \t\n\r\v":{[,';
static readonly REQUIRED_PROPERTY_LABEL = "(required)";
static readonly IOT_MODEL_LABEL = "IoTModel";
static readonly CONTEXT_TEMPLATE = "http://azureiot.com/v1/contexts/IoTModel.json";
static readonly CONTEXT_REGEX = new RegExp("^http://azureiot.com/v[0-9]+/contexts/IoTModel.json$");
static readonly SUPPORT_SEMANTIC_TYPES = new Set<string>(["Telemetry", "Property"]);
static readonly CONTEXT_TEMPLATE =
"http://azureiot.com/v1/contexts/IoTModel.json";
static readonly CONTEXT_REGEX = new RegExp(
"^http://azureiot.com/v[0-9]+/contexts/IoTModel.json$"
);
static readonly SUPPORT_SEMANTIC_TYPES = new Set<string>([
"Telemetry",
"Property"
]);
}

Просмотреть файл

@ -4,9 +4,21 @@
import * as parser from "jsonc-parser";
import * as vscode from "vscode";
import { Constants } from "../common/constants";
import { DiagnosticMessage, DigitalTwinConstants } from "./digitalTwinConstants";
import { ClassNode, DigitalTwinGraph, PropertyNode, ValueSchema } from "./digitalTwinGraph";
import { IntelliSenseUtility, JsonNodeType, PropertyPair } from "./intelliSenseUtility";
import {
DiagnosticMessage,
DigitalTwinConstants
} from "./digitalTwinConstants";
import {
ClassNode,
DigitalTwinGraph,
PropertyNode,
ValueSchema
} from "./digitalTwinGraph";
import {
IntelliSenseUtility,
JsonNodeType,
PropertyPair
} from "./intelliSenseUtility";
import { LANGUAGE_CODE } from "./languageCode";
/**
@ -27,9 +39,14 @@ export class DigitalTwinDiagnosticProvider {
* @param propertyNode DigitalTwin property node
* @param type class node type
*/
private static findClassNode(propertyNode: PropertyNode, type: string): ClassNode | undefined {
private static findClassNode(
propertyNode: PropertyNode,
type: string
): ClassNode | undefined {
if (propertyNode.range) {
return propertyNode.range.find((c) => DigitalTwinGraph.getClassType(c) === type);
return propertyNode.range.find(
c => DigitalTwinGraph.getClassType(c) === type
);
}
return undefined;
}
@ -38,8 +55,14 @@ export class DigitalTwinDiagnosticProvider {
* get property pair of name property
* @param jsonNode json node
*/
private static getNamePropertyPair(jsonNode: parser.Node): PropertyPair | undefined {
if (jsonNode.type !== JsonNodeType.Object || !jsonNode.children || jsonNode.children.length === 0) {
private static getNamePropertyPair(
jsonNode: parser.Node
): PropertyPair | undefined {
if (
jsonNode.type !== JsonNodeType.Object ||
!jsonNode.children ||
jsonNode.children.length === 0
) {
return undefined;
}
let propertyPair: PropertyPair | undefined;
@ -64,10 +87,14 @@ export class DigitalTwinDiagnosticProvider {
private static addProblemOfInvalidType(
jsonNode: parser.Node,
digitalTwinNode: PropertyNode,
problems: Problem[],
problems: Problem[]
): void {
const validTypes: string[] = DigitalTwinGraph.getValidTypes(digitalTwinNode);
const message: string = [DiagnosticMessage.InvalidType, ...validTypes].join(Constants.LINE_FEED);
const validTypes: string[] = DigitalTwinGraph.getValidTypes(
digitalTwinNode
);
const message: string = [DiagnosticMessage.InvalidType, ...validTypes].join(
Constants.LINE_FEED
);
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, message);
}
@ -76,8 +103,13 @@ export class DigitalTwinDiagnosticProvider {
* @param jsonNode json node
* @param problems problem collection
*/
private static addProblemOfUnexpectedProperty(jsonNode: parser.Node, problems: Problem[]): void {
const message = `${jsonNode.value as string} ${DiagnosticMessage.UnexpectedProperty}`;
private static addProblemOfUnexpectedProperty(
jsonNode: parser.Node,
problems: Problem[]
): void {
const message = `${jsonNode.value as string} ${
DiagnosticMessage.UnexpectedProperty
}`;
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, message);
}
@ -88,7 +120,12 @@ export class DigitalTwinDiagnosticProvider {
* @param message diagnostic message
* @param isContainer identify if json node is a container (e.g. object or array)
*/
private static addProblem(jsonNode: parser.Node, problems: Problem[], message: string, isContainer?: boolean): void {
private static addProblem(
jsonNode: parser.Node,
problems: Problem[],
message: string,
isContainer?: boolean
): void {
const length: number = isContainer ? 0 : jsonNode.length;
problems.push({ offset: jsonNode.offset, length, message });
}
@ -99,25 +136,49 @@ export class DigitalTwinDiagnosticProvider {
* @param digitalTwinNode DigitalTwin property node
* @param problems problem collection
*/
private static validateNode(jsonNode: parser.Node, digitalTwinNode: PropertyNode, problems: Problem[]): void {
private static validateNode(
jsonNode: parser.Node,
digitalTwinNode: PropertyNode,
problems: Problem[]
): void {
const nodeType: parser.NodeType = jsonNode.type;
switch (nodeType) {
case JsonNodeType.Object:
DigitalTwinDiagnosticProvider.validateObjectNode(jsonNode, digitalTwinNode, problems);
break;
case JsonNodeType.Array:
DigitalTwinDiagnosticProvider.validateArrayNode(jsonNode, digitalTwinNode, problems);
break;
case JsonNodeType.String:
DigitalTwinDiagnosticProvider.validateStringNode(jsonNode, digitalTwinNode, problems);
break;
case JsonNodeType.Number:
DigitalTwinDiagnosticProvider.validateNumberNode(jsonNode, digitalTwinNode, problems);
break;
case JsonNodeType.Boolean:
DigitalTwinDiagnosticProvider.validateBooleanNode(jsonNode, digitalTwinNode, problems);
break;
default:
case JsonNodeType.Object:
DigitalTwinDiagnosticProvider.validateObjectNode(
jsonNode,
digitalTwinNode,
problems
);
break;
case JsonNodeType.Array:
DigitalTwinDiagnosticProvider.validateArrayNode(
jsonNode,
digitalTwinNode,
problems
);
break;
case JsonNodeType.String:
DigitalTwinDiagnosticProvider.validateStringNode(
jsonNode,
digitalTwinNode,
problems
);
break;
case JsonNodeType.Number:
DigitalTwinDiagnosticProvider.validateNumberNode(
jsonNode,
digitalTwinNode,
problems
);
break;
case JsonNodeType.Boolean:
DigitalTwinDiagnosticProvider.validateBooleanNode(
jsonNode,
digitalTwinNode,
problems
);
break;
default:
}
}
@ -127,27 +188,55 @@ export class DigitalTwinDiagnosticProvider {
* @param digitalTwinNode DigitalTwin property node
* @param problems problem collection
*/
private static validateObjectNode(jsonNode: parser.Node, digitalTwinNode: PropertyNode, problems: Problem[]): void {
const classes: ClassNode[] = IntelliSenseUtility.getObjectClasses(digitalTwinNode);
private static validateObjectNode(
jsonNode: parser.Node,
digitalTwinNode: PropertyNode,
problems: Problem[]
): void {
const classes: ClassNode[] = IntelliSenseUtility.getObjectClasses(
digitalTwinNode
);
if (classes.length === 0) {
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, DiagnosticMessage.NotObjectType);
DigitalTwinDiagnosticProvider.addProblem(
jsonNode,
problems,
DiagnosticMessage.NotObjectType
);
return;
}
if (!jsonNode.children || jsonNode.children.length === 0) {
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, DiagnosticMessage.EmptyObject, true);
DigitalTwinDiagnosticProvider.addProblem(
jsonNode,
problems,
DiagnosticMessage.EmptyObject,
true
);
return;
}
const typePath: parser.JSONPath = [DigitalTwinConstants.TYPE];
const typeNode: parser.Node | undefined = parser.findNodeAtLocation(jsonNode, typePath);
const typeNode: parser.Node | undefined = parser.findNodeAtLocation(
jsonNode,
typePath
);
// @type is required when there are multiple choice
if (!typeNode && classes.length !== 1) {
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, DiagnosticMessage.MissingType, true);
DigitalTwinDiagnosticProvider.addProblem(
jsonNode,
problems,
DiagnosticMessage.MissingType,
true
);
return;
}
// validate @type property
let classNode: ClassNode | undefined;
if (typeNode) {
classNode = DigitalTwinDiagnosticProvider.getValidObjectType(typeNode, digitalTwinNode, classes, problems);
classNode = DigitalTwinDiagnosticProvider.getValidObjectType(
typeNode,
digitalTwinNode,
classes,
problems
);
} else {
classNode = classes[0];
}
@ -156,25 +245,43 @@ export class DigitalTwinDiagnosticProvider {
}
// validate language node
if (IntelliSenseUtility.isLanguageNode(classNode)) {
DigitalTwinDiagnosticProvider.validateLanguageNode(jsonNode, digitalTwinNode, problems);
DigitalTwinDiagnosticProvider.validateLanguageNode(
jsonNode,
digitalTwinNode,
problems
);
return;
}
// validate other properties
const exist = new Set<string>();
DigitalTwinDiagnosticProvider.validateProperties(jsonNode, classNode, problems, exist);
DigitalTwinDiagnosticProvider.validateProperties(
jsonNode,
classNode,
problems,
exist
);
// validate required property
if (classNode.constraint && classNode.constraint.required) {
const requiredProperty: string[] = classNode.constraint.required.filter((p) => {
// @context is not required for inline Interface
const isInterfaceSchem: boolean =
p === DigitalTwinConstants.CONTEXT && digitalTwinNode.label === DigitalTwinConstants.SCHEMA;
return !exist.has(p) && !isInterfaceSchem;
});
const requiredProperty: string[] = classNode.constraint.required.filter(
p => {
// @context is not required for inline Interface
const isInterfaceSchem: boolean =
p === DigitalTwinConstants.CONTEXT &&
digitalTwinNode.label === DigitalTwinConstants.SCHEMA;
return !exist.has(p) && !isInterfaceSchem;
}
);
if (requiredProperty.length > 0) {
const message: string = [DiagnosticMessage.MissingRequiredProperties, ...requiredProperty].join(
Constants.LINE_FEED,
const message: string = [
DiagnosticMessage.MissingRequiredProperties,
...requiredProperty
].join(Constants.LINE_FEED);
DigitalTwinDiagnosticProvider.addProblem(
jsonNode,
problems,
message,
true
);
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, message, true);
}
}
}
@ -190,24 +297,40 @@ export class DigitalTwinDiagnosticProvider {
jsonNode: parser.Node,
digitalTwinNode: PropertyNode,
classes: ClassNode[],
problems: Problem[],
problems: Problem[]
): ClassNode | undefined {
let classNode: ClassNode | undefined;
const dummyNode: PropertyNode = { id: DigitalTwinConstants.DUMMY_NODE, range: classes };
const dummyNode: PropertyNode = {
id: DigitalTwinConstants.DUMMY_NODE,
range: classes
};
if (jsonNode.type === JsonNodeType.String) {
classNode = DigitalTwinDiagnosticProvider.findClassNode(dummyNode, jsonNode.value as string);
} else if (jsonNode.type === JsonNodeType.Array && digitalTwinNode.label === DigitalTwinConstants.CONTENTS) {
classNode = DigitalTwinDiagnosticProvider.findClassNode(
dummyNode,
jsonNode.value as string
);
} else if (
jsonNode.type === JsonNodeType.Array &&
digitalTwinNode.label === DigitalTwinConstants.CONTENTS
) {
// support semantic type array
if (jsonNode.children && jsonNode.children.length === 2) {
let currentNode: ClassNode | undefined;
for (const child of jsonNode.children) {
if (child.type === JsonNodeType.String) {
// validate conflict type
currentNode = DigitalTwinDiagnosticProvider.findClassNode(dummyNode, child.value as string);
currentNode = DigitalTwinDiagnosticProvider.findClassNode(
dummyNode,
child.value as string
);
if (currentNode) {
if (classNode) {
const message = `${DiagnosticMessage.ConflictType} ${classNode.label} and ${currentNode.label}`;
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, message);
DigitalTwinDiagnosticProvider.addProblem(
jsonNode,
problems,
message
);
return undefined;
} else {
classNode = currentNode;
@ -218,7 +341,11 @@ export class DigitalTwinDiagnosticProvider {
}
}
if (!classNode) {
DigitalTwinDiagnosticProvider.addProblemOfInvalidType(jsonNode, dummyNode, problems);
DigitalTwinDiagnosticProvider.addProblemOfInvalidType(
jsonNode,
dummyNode,
problems
);
}
return classNode;
}
@ -234,14 +361,14 @@ export class DigitalTwinDiagnosticProvider {
jsonNode: parser.Node,
classNode: ClassNode,
problems: Problem[],
exist: Set<string>,
exist: Set<string>
): void {
if (!jsonNode.children) {
return;
}
const expectedProperties = new Map<string, PropertyNode>();
if (classNode.properties) {
classNode.properties.forEach((p) => {
classNode.properties.forEach(p => {
if (p.label) {
expectedProperties.set(p.label, p);
}
@ -259,38 +386,56 @@ export class DigitalTwinDiagnosticProvider {
// duplicate property name is handled by json validator
exist.add(propertyName);
switch (propertyName) {
case DigitalTwinConstants.ID:
// @id is available for each class
propertyNode = IntelliSenseUtility.getPropertyNode(propertyName);
if (propertyNode) {
DigitalTwinDiagnosticProvider.validateNode(propertyPair.value, propertyNode, problems);
}
break;
case DigitalTwinConstants.CONTEXT:
// @context is available when it is required
if (
classNode.constraint &&
case DigitalTwinConstants.ID:
// @id is available for each class
propertyNode = IntelliSenseUtility.getPropertyNode(propertyName);
if (propertyNode) {
DigitalTwinDiagnosticProvider.validateNode(
propertyPair.value,
propertyNode,
problems
);
}
break;
case DigitalTwinConstants.CONTEXT:
// @context is available when it is required
if (
classNode.constraint &&
classNode.constraint.required &&
classNode.constraint.required.includes(propertyName)
) {
if (!IntelliSenseUtility.isDigitalTwinContext(propertyPair.value)) {
DigitalTwinDiagnosticProvider.addProblem(propertyPair.value, problems, DiagnosticMessage.InvalidContext);
) {
if (!IntelliSenseUtility.isDigitalTwinContext(propertyPair.value)) {
DigitalTwinDiagnosticProvider.addProblem(
propertyPair.value,
problems,
DiagnosticMessage.InvalidContext
);
}
} else {
DigitalTwinDiagnosticProvider.addProblemOfUnexpectedProperty(
propertyPair.name,
problems
);
}
break;
case DigitalTwinConstants.TYPE:
// skip since @type is already validated
break;
default:
// validate expected property
propertyNode = expectedProperties.get(propertyName);
if (!propertyNode) {
DigitalTwinDiagnosticProvider.addProblemOfUnexpectedProperty(
propertyPair.name,
problems
);
} else {
DigitalTwinDiagnosticProvider.validateNode(
propertyPair.value,
propertyNode,
problems
);
}
} else {
DigitalTwinDiagnosticProvider.addProblemOfUnexpectedProperty(propertyPair.name, problems);
}
break;
case DigitalTwinConstants.TYPE:
// skip since @type is already validated
break;
default:
// validate expected property
propertyNode = expectedProperties.get(propertyName);
if (!propertyNode) {
DigitalTwinDiagnosticProvider.addProblemOfUnexpectedProperty(propertyPair.name, problems);
} else {
DigitalTwinDiagnosticProvider.validateNode(propertyPair.value, propertyNode, problems);
}
}
}
}
@ -301,25 +446,54 @@ export class DigitalTwinDiagnosticProvider {
* @param digitalTwinNode DigitalTwin property node
* @param problems problem collection
*/
private static validateArrayNode(jsonNode: parser.Node, digitalTwinNode: PropertyNode, problems: Problem[]): void {
private static validateArrayNode(
jsonNode: parser.Node,
digitalTwinNode: PropertyNode,
problems: Problem[]
): void {
if (!digitalTwinNode.isArray) {
DigitalTwinDiagnosticProvider.addProblemOfInvalidType(jsonNode, digitalTwinNode, problems);
DigitalTwinDiagnosticProvider.addProblemOfInvalidType(
jsonNode,
digitalTwinNode,
problems
);
return;
}
if (!jsonNode.children || jsonNode.children.length === 0) {
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, DiagnosticMessage.EmptyArray, true);
DigitalTwinDiagnosticProvider.addProblem(
jsonNode,
problems,
DiagnosticMessage.EmptyArray,
true
);
return;
}
// validate item constraint
let message: string;
if (digitalTwinNode.constraint) {
if (digitalTwinNode.constraint.minItems && jsonNode.children.length < digitalTwinNode.constraint.minItems) {
if (
digitalTwinNode.constraint.minItems &&
jsonNode.children.length < digitalTwinNode.constraint.minItems
) {
message = `${DiagnosticMessage.TooFewItems} ${digitalTwinNode.constraint.minItems}.`;
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, message, true);
DigitalTwinDiagnosticProvider.addProblem(
jsonNode,
problems,
message,
true
);
}
if (digitalTwinNode.constraint.maxItems && jsonNode.children.length > digitalTwinNode.constraint.maxItems) {
if (
digitalTwinNode.constraint.maxItems &&
jsonNode.children.length > digitalTwinNode.constraint.maxItems
) {
message = `${DiagnosticMessage.TooManyItems} ${digitalTwinNode.constraint.maxItems}.`;
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, message, true);
DigitalTwinDiagnosticProvider.addProblem(
jsonNode,
problems,
message,
true
);
}
}
// validate item uniqueness by name
@ -332,12 +506,20 @@ export class DigitalTwinDiagnosticProvider {
objectName = propertyPair.value.value as string;
if (exist.has(objectName)) {
message = `${objectName} ${DiagnosticMessage.DuplicateItem}`;
DigitalTwinDiagnosticProvider.addProblem(propertyPair.value, problems, message);
DigitalTwinDiagnosticProvider.addProblem(
propertyPair.value,
problems,
message
);
} else {
exist.add(objectName);
}
}
DigitalTwinDiagnosticProvider.validateNode(child, digitalTwinNode, problems);
DigitalTwinDiagnosticProvider.validateNode(
child,
digitalTwinNode,
problems
);
}
}
@ -347,29 +529,49 @@ export class DigitalTwinDiagnosticProvider {
* @param digitalTwinNode DigitalTwin property node
* @param problems problem collection
*/
private static validateStringNode(jsonNode: parser.Node, digitalTwinNode: PropertyNode, problems: Problem[]): void {
const classNode: ClassNode | undefined = DigitalTwinDiagnosticProvider.findClassNode(
private static validateStringNode(
jsonNode: parser.Node,
digitalTwinNode: PropertyNode,
problems: Problem[]
): void {
const classNode:
| ClassNode
| undefined = DigitalTwinDiagnosticProvider.findClassNode(
digitalTwinNode,
ValueSchema.String,
ValueSchema.String
);
// validate enum node
if (!classNode) {
DigitalTwinDiagnosticProvider.validateEnumNode(jsonNode, digitalTwinNode, problems);
DigitalTwinDiagnosticProvider.validateEnumNode(
jsonNode,
digitalTwinNode,
problems
);
return;
}
const value: string = jsonNode.value as string;
if (!value) {
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, DiagnosticMessage.EmptyString);
DigitalTwinDiagnosticProvider.addProblem(
jsonNode,
problems,
DiagnosticMessage.EmptyString
);
return;
}
// validate string constraint
let message: string;
if (digitalTwinNode.constraint) {
if (digitalTwinNode.constraint.minLength && value.length < digitalTwinNode.constraint.minLength) {
if (
digitalTwinNode.constraint.minLength &&
value.length < digitalTwinNode.constraint.minLength
) {
message = `${DiagnosticMessage.ShorterThanMinLength} ${digitalTwinNode.constraint.minLength}.`;
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, message);
return;
} else if (digitalTwinNode.constraint.maxLength && value.length > digitalTwinNode.constraint.maxLength) {
} else if (
digitalTwinNode.constraint.maxLength &&
value.length > digitalTwinNode.constraint.maxLength
) {
message = `${DiagnosticMessage.LongerThanMaxLength} ${digitalTwinNode.constraint.maxLength}.`;
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, message);
return;
@ -390,12 +592,22 @@ export class DigitalTwinDiagnosticProvider {
* @param digitalTwinNode DigitalTwin property node
* @param problems problem collection
*/
private static validateEnumNode(jsonNode: parser.Node, digitalTwinNode: PropertyNode, problems: Problem[]): void {
private static validateEnumNode(
jsonNode: parser.Node,
digitalTwinNode: PropertyNode,
problems: Problem[]
): void {
const enums: string[] = IntelliSenseUtility.getEnums(digitalTwinNode);
if (enums.length === 0) {
DigitalTwinDiagnosticProvider.addProblemOfInvalidType(jsonNode, digitalTwinNode, problems);
DigitalTwinDiagnosticProvider.addProblemOfInvalidType(
jsonNode,
digitalTwinNode,
problems
);
} else if (!enums.includes(jsonNode.value as string)) {
const message: string = [DiagnosticMessage.InvalidEnum, ...enums].join(Constants.LINE_FEED);
const message: string = [DiagnosticMessage.InvalidEnum, ...enums].join(
Constants.LINE_FEED
);
DigitalTwinDiagnosticProvider.addProblem(jsonNode, problems, message);
}
}
@ -406,14 +618,24 @@ export class DigitalTwinDiagnosticProvider {
* @param digitalTwinNode DigitalTwin property node
* @param problems problem collection
*/
private static validateNumberNode(jsonNode: parser.Node, digitalTwinNode: PropertyNode, problems: Problem[]): void {
const classNode: ClassNode | undefined = DigitalTwinDiagnosticProvider.findClassNode(
private static validateNumberNode(
jsonNode: parser.Node,
digitalTwinNode: PropertyNode,
problems: Problem[]
): void {
const classNode:
| ClassNode
| undefined = DigitalTwinDiagnosticProvider.findClassNode(
digitalTwinNode,
ValueSchema.Int,
ValueSchema.Int
);
// validate number is integer
if (!classNode || !Number.isInteger(jsonNode.value as number)) {
DigitalTwinDiagnosticProvider.addProblemOfInvalidType(jsonNode, digitalTwinNode, problems);
DigitalTwinDiagnosticProvider.addProblemOfInvalidType(
jsonNode,
digitalTwinNode,
problems
);
return;
}
}
@ -424,13 +646,23 @@ export class DigitalTwinDiagnosticProvider {
* @param digitalTwinNode DigitalTwin property node
* @param problems problem collection
*/
private static validateBooleanNode(jsonNode: parser.Node, digitalTwinNode: PropertyNode, problems: Problem[]): void {
const classNode: ClassNode | undefined = DigitalTwinDiagnosticProvider.findClassNode(
private static validateBooleanNode(
jsonNode: parser.Node,
digitalTwinNode: PropertyNode,
problems: Problem[]
): void {
const classNode:
| ClassNode
| undefined = DigitalTwinDiagnosticProvider.findClassNode(
digitalTwinNode,
ValueSchema.Boolean,
ValueSchema.Boolean
);
if (!classNode) {
DigitalTwinDiagnosticProvider.addProblemOfInvalidType(jsonNode, digitalTwinNode, problems);
DigitalTwinDiagnosticProvider.addProblemOfInvalidType(
jsonNode,
digitalTwinNode,
problems
);
return;
}
}
@ -441,7 +673,11 @@ export class DigitalTwinDiagnosticProvider {
* @param digitalTwinNode DigitalTwin property node
* @param problems problem collection
*/
private static validateLanguageNode(jsonNode: parser.Node, digitalTwinNode: PropertyNode, problems: Problem[]): void {
private static validateLanguageNode(
jsonNode: parser.Node,
digitalTwinNode: PropertyNode,
problems: Problem[]
): void {
if (!jsonNode.children) {
return;
}
@ -454,11 +690,22 @@ export class DigitalTwinDiagnosticProvider {
}
propertyName = propertyPair.name.value as string;
if (!LANGUAGE_CODE.has(propertyName)) {
DigitalTwinDiagnosticProvider.addProblemOfUnexpectedProperty(propertyPair.name, problems);
DigitalTwinDiagnosticProvider.addProblemOfUnexpectedProperty(
propertyPair.name,
problems
);
} else if (typeof propertyPair.value.value !== "string") {
DigitalTwinDiagnosticProvider.addProblem(propertyPair.value, problems, DiagnosticMessage.ValueNotString);
DigitalTwinDiagnosticProvider.addProblem(
propertyPair.value,
problems,
DiagnosticMessage.ValueNotString
);
} else {
DigitalTwinDiagnosticProvider.validateStringNode(propertyPair.value, digitalTwinNode, problems);
DigitalTwinDiagnosticProvider.validateStringNode(
propertyPair.value,
digitalTwinNode,
problems
);
}
}
}
@ -468,17 +715,27 @@ export class DigitalTwinDiagnosticProvider {
* @param document text document
* @param collection diagnostic collection
*/
updateDiagnostics(document: vscode.TextDocument, collection: vscode.DiagnosticCollection): void {
updateDiagnostics(
document: vscode.TextDocument,
collection: vscode.DiagnosticCollection
): void {
// clean diagnostic cache
collection.delete(document.uri);
const jsonNode: parser.Node | undefined = IntelliSenseUtility.parseDigitalTwinModel(document.getText());
const jsonNode:
| parser.Node
| undefined = IntelliSenseUtility.parseDigitalTwinModel(
document.getText()
);
if (!jsonNode) {
return;
}
if (!IntelliSenseUtility.enabled()) {
return;
}
const diagnostics: vscode.Diagnostic[] = this.provideDiagnostics(document, jsonNode);
const diagnostics: vscode.Diagnostic[] = this.provideDiagnostics(
document,
jsonNode
);
collection.set(document.uri, diagnostics);
}
@ -487,21 +744,33 @@ export class DigitalTwinDiagnosticProvider {
* @param document text document
* @param jsonNode json node
*/
private provideDiagnostics(document: vscode.TextDocument, jsonNode: parser.Node): vscode.Diagnostic[] {
private provideDiagnostics(
document: vscode.TextDocument,
jsonNode: parser.Node
): vscode.Diagnostic[] {
let diagnostics: vscode.Diagnostic[] = [];
const digitalTwinNode: PropertyNode | undefined = IntelliSenseUtility.getEntryNode();
const digitalTwinNode:
| PropertyNode
| undefined = IntelliSenseUtility.getEntryNode();
if (!digitalTwinNode) {
return diagnostics;
}
const problems: Problem[] = [];
DigitalTwinDiagnosticProvider.validateNode(jsonNode, digitalTwinNode, problems);
DigitalTwinDiagnosticProvider.validateNode(
jsonNode,
digitalTwinNode,
problems
);
diagnostics = problems.map(
(p) =>
p =>
new vscode.Diagnostic(
new vscode.Range(document.positionAt(p.offset), document.positionAt(p.offset + p.length)),
new vscode.Range(
document.positionAt(p.offset),
document.positionAt(p.offset + p.length)
),
p.message,
vscode.DiagnosticSeverity.Error,
),
vscode.DiagnosticSeverity.Error
)
);
return diagnostics;
}

Просмотреть файл

@ -58,7 +58,7 @@ interface ContextNode {
export enum ValueSchema {
String = "http://www.w3.org/2001/XMLSchema#string",
Int = "http://www.w3.org/2001/XMLSchema#int",
Boolean = "http://www.w3.org/2001/XMLSchema#boolean",
Boolean = "http://www.w3.org/2001/XMLSchema#boolean"
}
/**
@ -66,7 +66,7 @@ export enum ValueSchema {
*/
enum NodeType {
Class = "http://www.w3.org/2000/01/rdf-schema#Class",
Property = "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property",
Property = "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"
}
/**
@ -78,7 +78,7 @@ enum EdgeType {
Label = "http://www.w3.org/2000/01/rdf-schema#label",
Domain = "http://www.w3.org/2000/01/rdf-schema#domain",
SubClassOf = "http://www.w3.org/2000/01/rdf-schema#subClassOf",
Comment = "http://www.w3.org/2000/01/rdf-schema#comment",
Comment = "http://www.w3.org/2000/01/rdf-schema#comment"
}
/**
@ -87,7 +87,7 @@ enum EdgeType {
enum ContainerType {
None,
Array,
Language,
Language
}
/**
@ -98,7 +98,9 @@ export class DigitalTwinGraph {
* get singleton instance of DigitalTwin graph
* @param context extension context
*/
static async getInstance(context: vscode.ExtensionContext): Promise<DigitalTwinGraph> {
static async getInstance(
context: vscode.ExtensionContext
): Promise<DigitalTwinGraph> {
if (!DigitalTwinGraph.instance) {
DigitalTwinGraph.instance = new DigitalTwinGraph();
await DigitalTwinGraph.instance.init(context);
@ -122,12 +124,14 @@ export class DigitalTwinGraph {
if (!propertyNode.range) {
return [];
}
return propertyNode.range.map((c) => {
return propertyNode.range.map(c => {
if (c.label) {
return c.label;
} else {
// get the name of XMLSchema
const index: number = c.id.lastIndexOf(DigitalTwinConstants.SCHEMA_SEPARATOR);
const index: number = c.id.lastIndexOf(
DigitalTwinConstants.SCHEMA_SEPARATOR
);
return index === -1 ? c.id : c.id.slice(index + 1);
}
});
@ -156,7 +160,12 @@ export class DigitalTwinGraph {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static isConstraintNode(object: any): object is ConstraintNode {
return (
object.minItems || object.maxItems || object.minLength || object.maxLength || object.pattern || object.required
object.minItems ||
object.maxItems ||
object.minLength ||
object.maxLength ||
object.pattern ||
object.required
);
}
@ -180,13 +189,13 @@ export class DigitalTwinGraph {
return ContainerType.None;
}
switch (container) {
case DigitalTwinConstants.LIST:
case DigitalTwinConstants.SET:
return ContainerType.Array;
case DigitalTwinConstants.LANGUAGE:
return ContainerType.Language;
default:
return ContainerType.None;
case DigitalTwinConstants.LIST:
case DigitalTwinConstants.SET:
return ContainerType.Array;
case DigitalTwinConstants.LANGUAGE:
return ContainerType.Language;
default:
return ContainerType.None;
}
}
@ -203,10 +212,17 @@ export class DigitalTwinGraph {
* @param context extension context
* @param fileName file name
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static async resolveDefinition(context: vscode.ExtensionContext, fileName: string): Promise<any> {
private static async resolveDefinition(
context: vscode.ExtensionContext,
fileName: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): Promise<any> {
const filePath: string = context.asAbsolutePath(
path.join(Constants.RESOURCE_FOLDER, Constants.DEFINITION_FOLDER, fileName),
path.join(
Constants.RESOURCE_FOLDER,
Constants.DEFINITION_FOLDER,
fileName
)
);
return await Utility.getJsonContent(filePath);
}
@ -261,9 +277,18 @@ export class DigitalTwinGraph {
let graphJson;
// load definition file
try {
contextJson = await DigitalTwinGraph.resolveDefinition(context, Constants.CONTEXT_FILE_NAME);
constraintJson = await DigitalTwinGraph.resolveDefinition(context, Constants.CONSTRAINT_FILE_NAME);
graphJson = await DigitalTwinGraph.resolveDefinition(context, Constants.GRAPH_FILE_NAME);
contextJson = await DigitalTwinGraph.resolveDefinition(
context,
Constants.CONTEXT_FILE_NAME
);
constraintJson = await DigitalTwinGraph.resolveDefinition(
context,
Constants.CONSTRAINT_FILE_NAME
);
graphJson = await DigitalTwinGraph.resolveDefinition(
context,
Constants.GRAPH_FILE_NAME
);
} catch (error) {
return;
}
@ -299,7 +324,9 @@ export class DigitalTwinGraph {
id = this.getId(value);
this.contextNodes.set(id, { name: key, container: ContainerType.None });
} else {
const containerType: ContainerType = DigitalTwinGraph.resolveContainerType(value);
const containerType: ContainerType = DigitalTwinGraph.resolveContainerType(
value
);
id = this.getId(value[DigitalTwinConstants.ID] as string);
this.contextNodes.set(id, { name: key, container: containerType });
}
@ -343,25 +370,25 @@ export class DigitalTwinGraph {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private handleEdge(edge: any): void {
switch (edge.Label) {
case EdgeType.Type:
this.handleEdgeOfType(edge);
break;
case EdgeType.Label:
this.handleEdgeOfLabel(edge);
break;
case EdgeType.Domain:
this.handleEdgeOfDomain(edge);
break;
case EdgeType.Range:
this.handleEdgeOfRange(edge);
break;
case EdgeType.SubClassOf:
this.handleEdgeOfSubClassOf(edge);
break;
case EdgeType.Comment:
this.handleEdgeOfComment(edge);
break;
default:
case EdgeType.Type:
this.handleEdgeOfType(edge);
break;
case EdgeType.Label:
this.handleEdgeOfLabel(edge);
break;
case EdgeType.Domain:
this.handleEdgeOfDomain(edge);
break;
case EdgeType.Range:
this.handleEdgeOfRange(edge);
break;
case EdgeType.SubClassOf:
this.handleEdgeOfSubClassOf(edge);
break;
case EdgeType.Comment:
this.handleEdgeOfComment(edge);
break;
default:
}
}
@ -376,22 +403,22 @@ export class DigitalTwinGraph {
const id: string = edge.SourceNode.Id as string;
const type: string = edge.TargetNode.Id as string;
switch (type) {
case NodeType.Class:
this.ensureClassNode(id);
break;
case NodeType.Property:
this.ensurePropertyNode(id);
break;
default:{
// mark target class as enum node
const contextNode: ContextNode | undefined = this.contextNodes.get(id);
const enumValue: string = contextNode ? contextNode.name : id;
const enumNode: ClassNode = this.ensureClassNode(type);
if (!enumNode.enums) {
enumNode.enums = [];
case NodeType.Class:
this.ensureClassNode(id);
break;
case NodeType.Property:
this.ensurePropertyNode(id);
break;
default: {
// mark target class as enum node
const contextNode: ContextNode | undefined = this.contextNodes.get(id);
const enumValue: string = contextNode ? contextNode.name : id;
const enumNode: ClassNode = this.ensureClassNode(type);
if (!enumNode.enums) {
enumNode.enums = [];
}
enumNode.enums.push(enumValue);
}
enumNode.enums.push(enumValue);
}
}
}
@ -413,7 +440,9 @@ export class DigitalTwinGraph {
const classNode: ClassNode = this.ensureClassNode(id);
if (!classNode.label) {
classNode.label = label;
const constraintNode: ConstraintNode | undefined = this.constraintNodes.get(label);
const constraintNode:
| ConstraintNode
| undefined = this.constraintNodes.get(label);
if (constraintNode) {
classNode.constraint = constraintNode;
}
@ -497,7 +526,9 @@ export class DigitalTwinGraph {
const contextNode: ContextNode | undefined = this.contextNodes.get(id);
if (contextNode) {
classNode.label = contextNode.name;
const constraintNode: ConstraintNode | undefined = this.constraintNodes.get(contextNode.name);
const constraintNode:
| ConstraintNode
| undefined = this.constraintNodes.get(contextNode.name);
if (constraintNode) {
classNode.constraint = constraintNode;
}
@ -521,11 +552,15 @@ export class DigitalTwinGraph {
propertyNode.isArray = contextNode.container === ContainerType.Array;
// handle language node
if (contextNode.container === ContainerType.Language) {
const languageNode: ClassNode = this.ensureClassNode(DigitalTwinConstants.LANGUAGE);
const languageNode: ClassNode = this.ensureClassNode(
DigitalTwinConstants.LANGUAGE
);
languageNode.label = DigitalTwinConstants.LANGUAGE;
propertyNode.range = [languageNode];
}
const constraintNode: ConstraintNode | undefined = this.constraintNodes.get(contextNode.name);
const constraintNode:
| ConstraintNode
| undefined = this.constraintNodes.get(contextNode.name);
if (constraintNode) {
propertyNode.constraint = constraintNode;
}
@ -549,13 +584,15 @@ export class DigitalTwinGraph {
// update label and range of interfaceSchema property
const propertyNode: PropertyNode | undefined = this.propertyNodes.get(
this.getId(DigitalTwinConstants.INTERFACE_SCHEMA_NODE),
this.getId(DigitalTwinConstants.INTERFACE_SCHEMA_NODE)
);
if (propertyNode) {
propertyNode.label = DigitalTwinConstants.SCHEMA;
if (propertyNode.range) {
propertyNode.range.push(stringNode);
propertyNode.constraint = this.constraintNodes.get(DigitalTwinConstants.ID);
propertyNode.constraint = this.constraintNodes.get(
DigitalTwinConstants.ID
);
}
}
}
@ -567,7 +604,9 @@ export class DigitalTwinGraph {
*/
private buildReservedProperty(id: string, classNode: ClassNode): void {
const propertyNode: PropertyNode = { id, range: [classNode] };
const constraintNode: ConstraintNode | undefined = this.constraintNodes.get(id);
const constraintNode: ConstraintNode | undefined = this.constraintNodes.get(
id
);
if (constraintNode) {
propertyNode.constraint = constraintNode;
}
@ -579,7 +618,9 @@ export class DigitalTwinGraph {
* @param name class node name
*/
private markAbstractClass(name: string): void {
const classNode: ClassNode | undefined = this.classNodes.get(this.getId(name));
const classNode: ClassNode | undefined = this.classNodes.get(
this.getId(name)
);
if (classNode) {
classNode.isAbstract = true;
}
@ -589,7 +630,9 @@ export class DigitalTwinGraph {
* expand properties from base class node
*/
private expandProperties(): void {
let classNode: ClassNode | undefined = this.classNodes.get(this.getId(DigitalTwinConstants.BASE_CLASS));
let classNode: ClassNode | undefined = this.classNodes.get(
this.getId(DigitalTwinConstants.BASE_CLASS)
);
if (!classNode) {
return;
}
@ -618,14 +661,16 @@ export class DigitalTwinGraph {
* build entry node of DigitalTwin graph
*/
private buildEntryNode(): void {
const interfaceNode: ClassNode | undefined = this.classNodes.get(this.getId(DigitalTwinConstants.INTERFACE_NODE));
const interfaceNode: ClassNode | undefined = this.classNodes.get(
this.getId(DigitalTwinConstants.INTERFACE_NODE)
);
const capabilityModelNode: ClassNode | undefined = this.classNodes.get(
this.getId(DigitalTwinConstants.CAPABILITY_MODEL_NODE),
this.getId(DigitalTwinConstants.CAPABILITY_MODEL_NODE)
);
if (interfaceNode && capabilityModelNode) {
const entryNode: PropertyNode = {
id: DigitalTwinConstants.ENTRY_NODE,
range: [interfaceNode, capabilityModelNode],
range: [interfaceNode, capabilityModelNode]
};
this.propertyNodes.set(entryNode.id, entryNode);
}

Просмотреть файл

@ -21,16 +21,20 @@ export class DigitalTwinHoverProvider implements vscode.HoverProvider {
return Constants.EMPTY_STRING;
}
switch (propertyName) {
case DigitalTwinConstants.ID:
return `An identifier for ${Constants.CHANNEL_NAME} Capability Model or interface`;
case DigitalTwinConstants.TYPE:
return `The type of ${Constants.CHANNEL_NAME} meta model object`;
case DigitalTwinConstants.CONTEXT:
return `The context for ${Constants.CHANNEL_NAME} Capability Model or interface`;
default: {
const propertyNode: PropertyNode | undefined = IntelliSenseUtility.getPropertyNode(propertyName);
return propertyNode && propertyNode.comment ? propertyNode.comment : Constants.EMPTY_STRING;
}
case DigitalTwinConstants.ID:
return `An identifier for ${Constants.CHANNEL_NAME} Capability Model or interface`;
case DigitalTwinConstants.TYPE:
return `The type of ${Constants.CHANNEL_NAME} meta model object`;
case DigitalTwinConstants.CONTEXT:
return `The context for ${Constants.CHANNEL_NAME} Capability Model or interface`;
default: {
const propertyNode:
| PropertyNode
| undefined = IntelliSenseUtility.getPropertyNode(propertyName);
return propertyNode && propertyNode.comment
? propertyNode.comment
: Constants.EMPTY_STRING;
}
}
}
@ -44,25 +48,41 @@ export class DigitalTwinHoverProvider implements vscode.HoverProvider {
document: vscode.TextDocument,
position: vscode.Position,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_token: vscode.CancellationToken,
_token: vscode.CancellationToken
): vscode.ProviderResult<vscode.Hover> {
const jsonNode: parser.Node | undefined = IntelliSenseUtility.parseDigitalTwinModel(document.getText());
const jsonNode:
| parser.Node
| undefined = IntelliSenseUtility.parseDigitalTwinModel(
document.getText()
);
if (!jsonNode) {
return undefined;
}
if (!IntelliSenseUtility.enabled()) {
return undefined;
}
const node: parser.Node | undefined = parser.findNodeAtOffset(jsonNode, document.offsetAt(position));
const node: parser.Node | undefined = parser.findNodeAtOffset(
jsonNode,
document.offsetAt(position)
);
if (!node || !node.parent) {
return undefined;
}
const propertyPair: PropertyPair | undefined = IntelliSenseUtility.parseProperty(node.parent);
const propertyPair:
| PropertyPair
| undefined = IntelliSenseUtility.parseProperty(node.parent);
if (!propertyPair) {
return undefined;
}
const propertyName: string = IntelliSenseUtility.resolvePropertyName(propertyPair);
const propertyName: string = IntelliSenseUtility.resolvePropertyName(
propertyPair
);
const content: string = DigitalTwinHoverProvider.getContent(propertyName);
return content ? new vscode.Hover(content, IntelliSenseUtility.getNodeRange(document, node.parent)) : undefined;
return content
? new vscode.Hover(
content,
IntelliSenseUtility.getNodeRange(document, node.parent)
)
: undefined;
}
}

Просмотреть файл

@ -15,7 +15,7 @@ export enum JsonNodeType {
String = "string",
Number = "number",
Boolean = "boolean",
Property = "property",
Property = "property"
}
/**
@ -49,7 +49,9 @@ export class IntelliSenseUtility {
* get entry node of DigitalTwin model
*/
static getEntryNode(): PropertyNode | undefined {
return IntelliSenseUtility.graph.getPropertyNode(DigitalTwinConstants.ENTRY_NODE);
return IntelliSenseUtility.graph.getPropertyNode(
DigitalTwinConstants.ENTRY_NODE
);
}
/**
@ -76,7 +78,10 @@ export class IntelliSenseUtility {
// skip checking errors in order to do IntelliSense at best effort
const jsonNode: parser.Node = parser.parseTree(text);
const contextPath: string[] = [DigitalTwinConstants.CONTEXT];
const contextNode: parser.Node | undefined = parser.findNodeAtLocation(jsonNode, contextPath);
const contextNode: parser.Node | undefined = parser.findNodeAtLocation(
jsonNode,
contextPath
);
if (contextNode && IntelliSenseUtility.isDigitalTwinContext(contextNode)) {
return jsonNode;
}
@ -108,7 +113,11 @@ export class IntelliSenseUtility {
* @param node json node
*/
static parseProperty(node: parser.Node): PropertyPair | undefined {
if (node.type !== JsonNodeType.Property || !node.children || node.children.length !== 2) {
if (
node.type !== JsonNodeType.Property ||
!node.children ||
node.children.length !== 2
) {
return undefined;
}
return { name: node.children[0], value: node.children[1] };
@ -119,8 +128,14 @@ export class IntelliSenseUtility {
* @param document text document
* @param node json node
*/
static getNodeRange(document: vscode.TextDocument, node: parser.Node): vscode.Range {
return new vscode.Range(document.positionAt(node.offset), document.positionAt(node.offset + node.length));
static getNodeRange(
document: vscode.TextDocument,
node: parser.Node
): vscode.Range {
return new vscode.Range(
document.positionAt(node.offset),
document.positionAt(node.offset + node.length)
);
}
/**
@ -182,7 +197,9 @@ export class IntelliSenseUtility {
// get outer object node
if (node.parent && node.parent.parent) {
node = node.parent.parent;
const outPropertyPair: PropertyPair | undefined = IntelliSenseUtility.getOuterPropertyPair(node);
const outPropertyPair:
| PropertyPair
| undefined = IntelliSenseUtility.getOuterPropertyPair(node);
if (outPropertyPair) {
const name: string = outPropertyPair.name.value as string;
if (name === DigitalTwinConstants.IMPLEMENTS) {
@ -205,7 +222,9 @@ export class IntelliSenseUtility {
if (outerProperty && outerProperty.type === JsonNodeType.Array) {
outerProperty = outerProperty.parent;
}
return outerProperty ? IntelliSenseUtility.parseProperty(outerProperty) : undefined;
return outerProperty
? IntelliSenseUtility.parseProperty(outerProperty)
: undefined;
}
private static graph: DigitalTwinGraph;

Просмотреть файл

@ -240,5 +240,5 @@ export const LANGUAGE_CODE = new Set<string>([
"zh-SG",
"zh-TW",
"zu",
"zu-ZA",
"zu-ZA"
]);

Просмотреть файл

@ -4,7 +4,12 @@
import * as request from "request-promise";
import { Constants } from "../common/constants";
import { ModelType } from "../deviceModel/deviceModelManager";
import { GetResult, MetaModelType, SearchOptions, SearchResult } from "./modelRepositoryInterface";
import {
GetResult,
MetaModelType,
SearchOptions,
SearchResult
} from "./modelRepositoryInterface";
import { RepositoryInfo } from "./modelRepositoryManager";
/**
@ -14,7 +19,7 @@ enum HttpMethod {
Get = "GET",
Post = "POST",
Put = "PUT",
Delete = "DELETE",
Delete = "DELETE"
}
/**
@ -27,22 +32,30 @@ export class ModelRepositoryClient {
* @param modelId model id
* @param expand identify if expand result
*/
static async getModel(repoInfo: RepositoryInfo, modelId: string, expand = false): Promise<GetResult> {
const options: request.OptionsWithUri = ModelRepositoryClient.createOptions(HttpMethod.Get, repoInfo, modelId);
static async getModel(
repoInfo: RepositoryInfo,
modelId: string,
expand = false
): Promise<GetResult> {
const options: request.OptionsWithUri = ModelRepositoryClient.createOptions(
HttpMethod.Get,
repoInfo,
modelId
);
if (expand) {
options.qs.expand = "true";
}
return new Promise<GetResult>((resolve, reject) => {
request(options)
.then((response) => {
.then(response => {
const result: GetResult = {
etag: response.headers[ModelRepositoryClient.ETAG_HEADER],
modelId: response.headers["x-ms-model-id"],
content: response.body,
content: response.body
};
return resolve(result);
})
.catch((err) => {
.catch(err => {
reject(err);
});
});
@ -61,24 +74,29 @@ export class ModelRepositoryClient {
type: ModelType,
keyword: string,
pageSize: number,
continuationToken: string | null,
continuationToken: string | null
): Promise<SearchResult> {
const options: request.OptionsWithUri = ModelRepositoryClient.createOptions(HttpMethod.Post, repoInfo);
const modelFilterType: MetaModelType = ModelRepositoryClient.convertToMetaModelType(type);
const options: request.OptionsWithUri = ModelRepositoryClient.createOptions(
HttpMethod.Post,
repoInfo
);
const modelFilterType: MetaModelType = ModelRepositoryClient.convertToMetaModelType(
type
);
const payload: SearchOptions = {
searchKeyword: keyword,
modelFilterType,
continuationToken,
pageSize,
pageSize
};
options.body = payload;
return new Promise<SearchResult>((resolve, reject) => {
request(options)
.then((response) => {
.then(response => {
const result = response.body as SearchResult;
return resolve(result);
})
.catch((err) => {
.catch(err => {
reject(err);
});
});
@ -90,17 +108,26 @@ export class ModelRepositoryClient {
* @param modelId model id
* @param content content to update
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static async updateModel(repoInfo: RepositoryInfo, modelId: string, content: any): Promise<string> {
const options: request.OptionsWithUri = ModelRepositoryClient.createOptions(HttpMethod.Put, repoInfo, modelId);
static async updateModel(
repoInfo: RepositoryInfo,
modelId: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
content: any
): Promise<string> {
const options: request.OptionsWithUri = ModelRepositoryClient.createOptions(
HttpMethod.Put,
repoInfo,
modelId
);
options.body = content;
return new Promise<string>((resolve, reject) => {
request(options)
.then((response) => {
const result: string = response.headers[ModelRepositoryClient.ETAG_HEADER];
.then(response => {
const result: string =
response.headers[ModelRepositoryClient.ETAG_HEADER];
return resolve(result);
})
.catch((err) => {
.catch(err => {
reject(err);
});
});
@ -111,14 +138,21 @@ export class ModelRepositoryClient {
* @param repoInfo repository info
* @param modelId model id
*/
static async deleteModel(repoInfo: RepositoryInfo, modelId: string): Promise<void> {
const options: request.OptionsWithUri = ModelRepositoryClient.createOptions(HttpMethod.Delete, repoInfo, modelId);
static async deleteModel(
repoInfo: RepositoryInfo,
modelId: string
): Promise<void> {
const options: request.OptionsWithUri = ModelRepositoryClient.createOptions(
HttpMethod.Delete,
repoInfo,
modelId
);
return new Promise<void>((resolve, reject) => {
request(options)
.then(() => {
resolve();
})
.catch((err) => {
.catch(err => {
reject(err);
});
});
@ -132,12 +166,12 @@ export class ModelRepositoryClient {
*/
private static convertToMetaModelType(type: ModelType): MetaModelType {
switch (type) {
case ModelType.Interface:
return MetaModelType.Interface;
case ModelType.CapabilityModel:
return MetaModelType.CapabilityModel;
default:
return MetaModelType.None;
case ModelType.Interface:
return MetaModelType.Interface;
case ModelType.CapabilityModel:
return MetaModelType.CapabilityModel;
default:
return MetaModelType.None;
}
}
@ -147,7 +181,11 @@ export class ModelRepositoryClient {
* @param repoInfo repository info
* @param modelId model id
*/
private static createOptions(method: HttpMethod, repoInfo: RepositoryInfo, modelId?: string): request.OptionsWithUri {
private static createOptions(
method: HttpMethod,
repoInfo: RepositoryInfo,
modelId?: string
): request.OptionsWithUri {
const uri = modelId
? `${repoInfo.hostname}/models/${encodeURIComponent(modelId)}`
: `${repoInfo.hostname}/models/search`;
@ -163,8 +201,11 @@ export class ModelRepositoryClient {
qs,
encoding: Constants.UTF8,
json: true,
headers: { "Authorization": accessToken, "Content-Type": "application/json" },
resolveWithFullResponse: true,
headers: {
Authorization: accessToken,
"Content-Type": "application/json"
},
resolveWithFullResponse: true
};
return options;
}

Просмотреть файл

@ -38,7 +38,7 @@ export class ModelRepositoryConnection {
map[ModelRepositoryConnection.HOSTNAME_PROPERTY],
map[ModelRepositoryConnection.REPOSITORY_ID_PROPERTY],
map[ModelRepositoryConnection.SHARED_ACCESS_KEY_NAME_PROPERTY],
map[ModelRepositoryConnection.SHARED_ACCESS_KEY_PROPERTY],
map[ModelRepositoryConnection.SHARED_ACCESS_KEY_PROPERTY]
);
connection.validate();
return connection;
@ -47,10 +47,13 @@ export class ModelRepositoryConnection {
private static readonly PROPERTY_COUNT = 4;
private static readonly HOSTNAME_PROPERTY = "HostName";
private static readonly REPOSITORY_ID_PROPERTY = "RepositoryId";
private static readonly SHARED_ACCESS_KEY_NAME_PROPERTY = "SharedAccessKeyName";
private static readonly SHARED_ACCESS_KEY_NAME_PROPERTY =
"SharedAccessKeyName";
private static readonly SHARED_ACCESS_KEY_PROPERTY = "SharedAccessKey";
private static readonly HOSTNAME_REGEX = new RegExp("[a-zA-Z0-9_\\-\\.]+$");
private static readonly SHARED_ACCESS_KEY_NAME_REGEX = new RegExp("^[a-zA-Z0-9_\\-@\\.]+$");
private static readonly SHARED_ACCESS_KEY_NAME_REGEX = new RegExp(
"^[a-zA-Z0-9_\\-@\\.]+$"
);
private static readonly EXPIRY_IN_MINUTES = 30;
private readonly expiry: string;
@ -58,10 +61,13 @@ export class ModelRepositoryConnection {
readonly hostName: string,
readonly repositoryId: string,
readonly sharedAccessKeyName: string,
readonly sharedAccessKey: string,
readonly sharedAccessKey: string
) {
const now: number = new Date().getTime();
this.expiry = (Math.round(now / 1000) + ModelRepositoryConnection.EXPIRY_IN_MINUTES * 60).toString();
this.expiry = (
Math.round(now / 1000) +
ModelRepositoryConnection.EXPIRY_IN_MINUTES * 60
).toString();
}
/**
@ -69,13 +75,19 @@ export class ModelRepositoryConnection {
*/
generateAccessToken(): string {
const endpoint: string = encodeURIComponent(this.hostName);
const payload: string = [encodeURIComponent(this.repositoryId), endpoint, this.expiry].join("\n").toLowerCase();
const payload: string = [
encodeURIComponent(this.repositoryId),
endpoint,
this.expiry
]
.join("\n")
.toLowerCase();
const signature: Buffer = Buffer.from(payload, Constants.UTF8);
const secret: Buffer = Buffer.from(this.sharedAccessKey, Constants.BASE64);
const hash: string = encodeURIComponent(
createHmac(Constants.SHA256, secret)
.update(signature)
.digest(Constants.BASE64),
.digest(Constants.BASE64)
);
return (
"SharedAccessSignature " +
@ -87,27 +99,35 @@ export class ModelRepositoryConnection {
* validate model repository connection
*/
private validate(): void {
if (!this.hostName || !ModelRepositoryConnection.HOSTNAME_REGEX.test(this.hostName)) {
if (
!this.hostName ||
!ModelRepositoryConnection.HOSTNAME_REGEX.test(this.hostName)
) {
throw new Error(
`${Constants.CONNECTION_STRING_INVALID_FORMAT_MSG} on property ${ModelRepositoryConnection.HOSTNAME_PROPERTY}`,
`${Constants.CONNECTION_STRING_INVALID_FORMAT_MSG} on property ${ModelRepositoryConnection.HOSTNAME_PROPERTY}`
);
}
if (!this.repositoryId) {
throw new Error(
`${Constants.CONNECTION_STRING_INVALID_FORMAT_MSG} on property ${ModelRepositoryConnection.REPOSITORY_ID_PROPERTY}`,
`${Constants.CONNECTION_STRING_INVALID_FORMAT_MSG} on \
property ${ModelRepositoryConnection.REPOSITORY_ID_PROPERTY}`
);
}
if (
!this.sharedAccessKeyName ||
!ModelRepositoryConnection.SHARED_ACCESS_KEY_NAME_REGEX.test(this.sharedAccessKeyName)
!ModelRepositoryConnection.SHARED_ACCESS_KEY_NAME_REGEX.test(
this.sharedAccessKeyName
)
) {
throw new Error(
`${Constants.CONNECTION_STRING_INVALID_FORMAT_MSG} on property ${ModelRepositoryConnection.SHARED_ACCESS_KEY_NAME_PROPERTY}`,
`${Constants.CONNECTION_STRING_INVALID_FORMAT_MSG} on \
property ${ModelRepositoryConnection.SHARED_ACCESS_KEY_NAME_PROPERTY}`
);
}
if (!this.sharedAccessKey) {
throw new Error(
`${Constants.CONNECTION_STRING_INVALID_FORMAT_MSG} on property ${ModelRepositoryConnection.SHARED_ACCESS_KEY_PROPERTY}`,
`${Constants.CONNECTION_STRING_INVALID_FORMAT_MSG} on \
property ${ModelRepositoryConnection.SHARED_ACCESS_KEY_PROPERTY}`
);
}
}

Просмотреть файл

@ -7,7 +7,7 @@
export enum MetaModelType {
None = "none",
Interface = "interface",
CapabilityModel = "capabilityModel",
CapabilityModel = "capabilityModel"
}
/**

Просмотреть файл

@ -11,7 +11,10 @@ import { CredentialStore } from "../common/credentialStore";
import { ProcessError } from "../common/processError";
import { UserCancelledError } from "../common/userCancelledError";
import { Utility } from "../common/utility";
import { DeviceModelManager, ModelType } from "../deviceModel/deviceModelManager";
import {
DeviceModelManager,
ModelType
} from "../deviceModel/deviceModelManager";
import { DigitalTwinConstants } from "../intelliSense/digitalTwinConstants";
import { ChoiceType, MessageType, UI } from "../view/ui";
import { UIConstants } from "../view/uiConstants";
@ -62,10 +65,14 @@ export class ModelRepositoryManager {
* create repository info
* @param publicRepository identify if it is public repository
*/
private static async createRepositoryInfo(publicRepository: boolean): Promise<RepositoryInfo> {
private static async createRepositoryInfo(
publicRepository: boolean
): Promise<RepositoryInfo> {
if (publicRepository) {
// get public repository connection from configuration
const url: string | undefined = Configuration.getProperty<string>(Constants.PUBLIC_REPOSITORY_URL);
const url: string | undefined = Configuration.getProperty<string>(
Constants.PUBLIC_REPOSITORY_URL
);
if (!url) {
throw new Error(Constants.PUBLIC_REPOSITORY_URL_NOT_FOUND_MSG);
}
@ -75,7 +82,9 @@ export class ModelRepositoryManager {
};
} else {
// get company repository connection from credential store
const connectionString: string | null = await CredentialStore.get(Constants.MODEL_REPOSITORY_CONNECTION_KEY);
const connectionString: string | null = await CredentialStore.get(
Constants.MODEL_REPOSITORY_CONNECTION_KEY
);
if (!connectionString) {
throw new Error(Constants.CONNECTION_STRING_NOT_FOUND_MSG);
}
@ -88,9 +97,13 @@ export class ModelRepositoryManager {
*/
private static async getAvailableRepositoryInfo(): Promise<RepositoryInfo[]> {
const repoInfos: RepositoryInfo[] = [];
const connectionString: string | null = await CredentialStore.get(Constants.MODEL_REPOSITORY_CONNECTION_KEY);
const connectionString: string | null = await CredentialStore.get(
Constants.MODEL_REPOSITORY_CONNECTION_KEY
);
if (connectionString) {
repoInfos.push(ModelRepositoryManager.getCompanyRepositoryInfo(connectionString));
repoInfos.push(
ModelRepositoryManager.getCompanyRepositoryInfo(connectionString)
);
}
repoInfos.push(await ModelRepositoryManager.createRepositoryInfo(true));
return repoInfos;
@ -101,16 +114,31 @@ export class ModelRepositoryManager {
*/
private static async setupConnection(): Promise<void> {
let newConnection = false;
let connectionString: string | null = await CredentialStore.get(Constants.MODEL_REPOSITORY_CONNECTION_KEY);
let connectionString: string | null = await CredentialStore.get(
Constants.MODEL_REPOSITORY_CONNECTION_KEY
);
if (!connectionString) {
connectionString = await UI.inputConnectionString(UIConstants.INPUT_REPOSITORY_CONNECTION_STRING_LABEL);
connectionString = await UI.inputConnectionString(
UIConstants.INPUT_REPOSITORY_CONNECTION_STRING_LABEL
);
newConnection = true;
}
const repoInfo: RepositoryInfo = ModelRepositoryManager.getCompanyRepositoryInfo(connectionString);
const repoInfo: RepositoryInfo = ModelRepositoryManager.getCompanyRepositoryInfo(
connectionString
);
// test connection by calling searchModel
await ModelRepositoryClient.searchModel(repoInfo, ModelType.Interface, Constants.EMPTY_STRING, 1, null);
await ModelRepositoryClient.searchModel(
repoInfo,
ModelType.Interface,
Constants.EMPTY_STRING,
1,
null
);
if (newConnection) {
await CredentialStore.set(Constants.MODEL_REPOSITORY_CONNECTION_KEY, connectionString);
await CredentialStore.set(
Constants.MODEL_REPOSITORY_CONNECTION_KEY,
connectionString
);
}
}
@ -118,8 +146,12 @@ export class ModelRepositoryManager {
* get company repository info
* @param connectionString connection string
*/
private static getCompanyRepositoryInfo(connectionString: string): RepositoryInfo {
const connection: ModelRepositoryConnection = ModelRepositoryConnection.parse(connectionString);
private static getCompanyRepositoryInfo(
connectionString: string
): RepositoryInfo {
const connection: ModelRepositoryConnection = ModelRepositoryConnection.parse(
connectionString
);
return {
hostname: Utility.enforceHttps(connection.hostName),
apiVersion: Constants.MODEL_REPOSITORY_API_VERSION,
@ -140,7 +172,11 @@ export class ModelRepositoryManager {
private readonly express: VSCExpress;
private readonly component: string;
constructor(context: vscode.ExtensionContext, filePath: string, private readonly outputChannel: ColorizedChannel) {
constructor(
context: vscode.ExtensionContext,
filePath: string,
private readonly outputChannel: ColorizedChannel
) {
this.express = new VSCExpress(context, filePath);
this.component = Constants.MODEL_REPOSITORY_COMPONENT;
}
@ -149,8 +185,14 @@ export class ModelRepositoryManager {
* sign in model repository
*/
async signIn(): Promise<void> {
const items: vscode.QuickPickItem[] = [{ label: RepositoryType.Public }, { label: RepositoryType.Company }];
const selected: vscode.QuickPickItem = await UI.showQuickPick(UIConstants.SELECT_REPOSITORY_LABEL, items);
const items: vscode.QuickPickItem[] = [
{ label: RepositoryType.Public },
{ label: RepositoryType.Company }
];
const selected: vscode.QuickPickItem = await UI.showQuickPick(
UIConstants.SELECT_REPOSITORY_LABEL,
items
);
const operation = `Connect to ${selected.label}`;
this.outputChannel.start(operation, this.component);
@ -168,12 +210,22 @@ export class ModelRepositoryManager {
// open web view
const uri: string =
selected.label === RepositoryType.Company ? Constants.COMPANY_REPOSITORY_PAGE : Constants.PUBLIC_REPOSITORY_PAGE;
this.express.open(uri, UIConstants.MODEL_REPOSITORY_TITLE, vscode.ViewColumn.Two, {
retainContextWhenHidden: true,
enableScripts: true
});
UI.showNotification(MessageType.Info, ColorizedChannel.formatMessage(operation));
selected.label === RepositoryType.Company
? Constants.COMPANY_REPOSITORY_PAGE
: Constants.PUBLIC_REPOSITORY_PAGE;
this.express.open(
uri,
UIConstants.MODEL_REPOSITORY_TITLE,
vscode.ViewColumn.Two,
{
retainContextWhenHidden: true,
enableScripts: true
}
);
UI.showNotification(
MessageType.Info,
ColorizedChannel.formatMessage(operation)
);
this.outputChannel.end(operation, this.component);
}
@ -190,7 +242,10 @@ export class ModelRepositoryManager {
if (this.express) {
this.express.close(Constants.COMPANY_REPOSITORY_PAGE);
}
UI.showNotification(MessageType.Info, ColorizedChannel.formatMessage(operation));
UI.showNotification(
MessageType.Info,
ColorizedChannel.formatMessage(operation)
);
this.outputChannel.end(operation, this.component);
}
@ -199,7 +254,9 @@ export class ModelRepositoryManager {
* @param telemetryContext telemetry context
*/
async submitFiles(telemetryContext: TelemetryContext): Promise<void> {
const files: string[] = await UI.selectModelFiles(UIConstants.SELECT_MODELS_LABEL);
const files: string[] = await UI.selectModelFiles(
UIConstants.SELECT_MODELS_LABEL
);
if (files.length === 0) {
return;
}
@ -211,12 +268,18 @@ export class ModelRepositoryManager {
if (error instanceof UserCancelledError) {
throw error;
} else {
throw new ProcessError(`Connect to ${RepositoryType.Company}`, error, this.component);
throw new ProcessError(
`Connect to ${RepositoryType.Company}`,
error,
this.component
);
}
}
try {
const repoInfo: RepositoryInfo = await ModelRepositoryManager.createRepositoryInfo(false);
const repoInfo: RepositoryInfo = await ModelRepositoryManager.createRepositoryInfo(
false
);
await this.doSubmitLoopSilently(repoInfo, files, telemetryContext);
} catch (error) {
const operation = `Submit models to ${RepositoryType.Company}`;
@ -254,8 +317,16 @@ export class ModelRepositoryManager {
let result: SearchResult;
try {
const repoInfo: RepositoryInfo = await ModelRepositoryManager.createRepositoryInfo(publicRepository);
result = await ModelRepositoryClient.searchModel(repoInfo, type, keyword, pageSize, continuationToken);
const repoInfo: RepositoryInfo = await ModelRepositoryManager.createRepositoryInfo(
publicRepository
);
result = await ModelRepositoryClient.searchModel(
repoInfo,
type,
keyword,
pageSize,
continuationToken
);
} catch (error) {
throw new ProcessError(operation, error, this.component);
}
@ -271,14 +342,21 @@ export class ModelRepositoryManager {
* @param publicRepository identify if it is public repository
* @param modelIds model id list
*/
async deleteModels(publicRepository: boolean, modelIds: string[]): Promise<void> {
async deleteModels(
publicRepository: boolean,
modelIds: string[]
): Promise<void> {
if (publicRepository) {
throw new BadRequestError(`${RepositoryType.Public} not support delete operation`);
throw new BadRequestError(
`${RepositoryType.Public} not support delete operation`
);
}
ModelRepositoryManager.validateModelIds(modelIds);
try {
const repoInfo: RepositoryInfo = await ModelRepositoryManager.createRepositoryInfo(publicRepository);
const repoInfo: RepositoryInfo = await ModelRepositoryManager.createRepositoryInfo(
publicRepository
);
await this.doDeleteLoopSilently(repoInfo, modelIds);
} catch (error) {
const operation = `Delete models from ${RepositoryType.Company}`;
@ -291,16 +369,25 @@ export class ModelRepositoryManager {
* @param publicRepository identify if it is public repository
* @param modelIds model id list
*/
async downloadModels(publicRepository: boolean, modelIds: string[]): Promise<void> {
async downloadModels(
publicRepository: boolean,
modelIds: string[]
): Promise<void> {
ModelRepositoryManager.validateModelIds(modelIds);
const folder: string = await UI.selectRootFolder(UIConstants.SELECT_ROOT_FOLDER_LABEL);
const folder: string = await UI.selectRootFolder(
UIConstants.SELECT_ROOT_FOLDER_LABEL
);
try {
const repoInfo: RepositoryInfo = await ModelRepositoryManager.createRepositoryInfo(publicRepository);
const repoInfo: RepositoryInfo = await ModelRepositoryManager.createRepositoryInfo(
publicRepository
);
await this.doDownloadLoopSilently([repoInfo], modelIds, folder);
} catch (error) {
const operation = `Download models from ${publicRepository ? RepositoryType.Public : RepositoryType.Company}`;
const operation = `Download models from ${
publicRepository ? RepositoryType.Public : RepositoryType.Company
}`;
throw new ProcessError(operation, error, this.component);
}
}
@ -310,20 +397,29 @@ export class ModelRepositoryManager {
* @param folder folder to download interface
* @param capabilityModelFile capability model file path
*/
async downloadDependentInterface(folder: string, capabilityModelFile: string): Promise<void> {
async downloadDependentInterface(
folder: string,
capabilityModelFile: string
): Promise<void> {
if (!folder || !capabilityModelFile) {
throw new BadRequestError(`folder and capabilityModelFile ${Constants.NOT_EMPTY_MSG}`);
throw new BadRequestError(
`folder and capabilityModelFile ${Constants.NOT_EMPTY_MSG}`
);
}
// get implemented interface of capability model
const content = await Utility.getJsonContent(capabilityModelFile);
const implementedInterface = content[DigitalTwinConstants.IMPLEMENTS];
if (!implementedInterface || implementedInterface.length === 0) {
throw new BadRequestError("no implemented interface found in capability model");
throw new BadRequestError(
"no implemented interface found in capability model"
);
}
// get existing interface file in workspace
const repoInfos: RepositoryInfo[] = await ModelRepositoryManager.getAvailableRepositoryInfo();
const fileInfos: ModelFileInfo[] = await UI.findModelFiles(ModelType.Interface);
const fileInfos: ModelFileInfo[] = await UI.findModelFiles(
ModelType.Interface
);
const exist = new Set<string>(fileInfos.map(f => f.id));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let schema: any;
@ -351,7 +447,11 @@ export class ModelRepositoryManager {
* @param modelIds model id list
* @param folder folder to download models
*/
private async doDownloadLoopSilently(repoInfos: RepositoryInfo[], modelIds: string[], folder: string): Promise<void> {
private async doDownloadLoopSilently(
repoInfos: RepositoryInfo[],
modelIds: string[],
folder: string
): Promise<void> {
for (const modelId of modelIds) {
const operation = `Download model by id ${modelId}`;
this.outputChannel.start(operation, this.component);
@ -371,7 +471,11 @@ export class ModelRepositoryManager {
* @param modelId model id
* @param folder folder to download model
*/
private async doDownloadModel(repoInfos: RepositoryInfo[], modelId: string, folder: string): Promise<boolean> {
private async doDownloadModel(
repoInfos: RepositoryInfo[],
modelId: string,
folder: string
): Promise<boolean> {
let result: GetResult | undefined;
for (const repoInfo of repoInfos) {
try {
@ -379,7 +483,9 @@ export class ModelRepositoryManager {
break;
} catch (error) {
if (error.statusCode === Constants.NOT_FOUND_CODE) {
this.outputChannel.warn(`Model ${modelId} is not found from ${repoInfo.hostname}`);
this.outputChannel.warn(
`Model ${modelId} is not found from ${repoInfo.hostname}`
);
} else {
this.outputChannel.error(
`Fail to get model ${modelId} from ${repoInfo.hostname}, statusCode: ${error.statusCode}`
@ -399,7 +505,10 @@ export class ModelRepositoryManager {
* @param repoInfo repository info
* @param modelIds model id list
*/
private async doDeleteLoopSilently(repoInfo: RepositoryInfo, modelIds: string[]): Promise<void> {
private async doDeleteLoopSilently(
repoInfo: RepositoryInfo,
modelIds: string[]
): Promise<void> {
for (const modelId of modelIds) {
const operation = `Delete model by id ${modelId}`;
this.outputChannel.start(operation, this.component);
@ -456,7 +565,9 @@ export class ModelRepositoryManager {
): Promise<void> {
const content = await Utility.getJsonContent(filePath);
const modelId: string = content[DigitalTwinConstants.ID];
const modelType: ModelType = DeviceModelManager.convertToModelType(content[DigitalTwinConstants.TYPE]);
const modelType: ModelType = DeviceModelManager.convertToModelType(
content[DigitalTwinConstants.TYPE]
);
let result: GetResult | undefined;
try {
result = await ModelRepositoryClient.getModel(repoInfo, modelId, true);
@ -470,7 +581,9 @@ export class ModelRepositoryManager {
if (result) {
if (!option.overwrite) {
const message = `Model ${modelId} already exist, ${UIConstants.ASK_TO_OVERWRITE_MSG}`;
const choice: string | undefined = await vscode.window.showWarningMessage(
const choice:
| string
| undefined = await vscode.window.showWarningMessage(
message,
ChoiceType.All,
ChoiceType.Yes,
@ -510,15 +623,17 @@ export class ModelRepositoryManager {
let hashId: string;
for (const [key, value] of usageData) {
succeedCount += value.length;
hashId = value.map(id => Utility.hash(id)).join(Constants.DEFAULT_SEPARATOR);
hashId = value
.map(id => Utility.hash(id))
.join(Constants.DEFAULT_SEPARATOR);
switch (key) {
case ModelType.Interface:
telemetryContext.properties.interfaceId = hashId;
break;
case ModelType.CapabilityModel:
telemetryContext.properties.capabilityModelId = hashId;
break;
default:
case ModelType.Interface:
telemetryContext.properties.interfaceId = hashId;
break;
case ModelType.CapabilityModel:
telemetryContext.properties.capabilityModelId = hashId;
break;
default:
}
}
telemetryContext.measurements.totalCount = totalCount;

Просмотреть файл

@ -16,7 +16,7 @@ import { UIConstants } from "./uiConstants";
export enum MessageType {
Info,
Warn,
Error,
Error
}
/**
@ -26,7 +26,7 @@ export enum ChoiceType {
All = "All",
Yes = "Yes",
No = "No",
Cancel = "Cancel",
Cancel = "Cancel"
}
/**
@ -46,7 +46,11 @@ export class UI {
*/
static async openAndShowTextDocument(filePath: string): Promise<void> {
const folder: string = path.dirname(filePath);
await vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(folder), false);
await vscode.commands.executeCommand(
"vscode.openFolder",
vscode.Uri.file(folder),
false
);
await vscode.window.showTextDocument(vscode.Uri.file(filePath));
}
@ -57,16 +61,16 @@ export class UI {
*/
static showNotification(type: MessageType, message: string): void {
switch (type) {
case MessageType.Info:
vscode.window.showInformationMessage(message);
break;
case MessageType.Warn:
vscode.window.showWarningMessage(message);
break;
case MessageType.Error:
vscode.window.showErrorMessage(message);
break;
default:
case MessageType.Info:
vscode.window.showInformationMessage(message);
break;
case MessageType.Warn:
vscode.window.showWarningMessage(message);
break;
case MessageType.Error:
vscode.window.showErrorMessage(message);
break;
default:
}
}
@ -75,7 +79,8 @@ export class UI {
* @param label label
*/
static async selectRootFolder(label: string): Promise<string> {
const workspaceFolders: vscode.WorkspaceFolder[] | undefined = vscode.workspace.workspaceFolders;
const workspaceFolders: vscode.WorkspaceFolder[] | undefined =
vscode.workspace.workspaceFolders;
// use the only workspace as default
if (workspaceFolders && workspaceFolders.length === 1) {
return workspaceFolders[0].uri.fsPath;
@ -87,11 +92,14 @@ export class UI {
const fsPath: string = f.uri.fsPath;
return {
label: path.basename(fsPath),
description: fsPath,
description: fsPath
};
});
}
items.push({ label: UIConstants.BROWSE_LABEL, description: Constants.EMPTY_STRING });
items.push({
label: UIConstants.BROWSE_LABEL,
description: Constants.EMPTY_STRING
});
const selected: vscode.QuickPickItem = await UI.showQuickPick(label, items);
return selected.description || (await UI.showOpenDialog(label));
}
@ -101,12 +109,17 @@ export class UI {
* @param label label
* @param items quick pick item list
*/
static async showQuickPick(label: string, items: vscode.QuickPickItem[]): Promise<vscode.QuickPickItem> {
static async showQuickPick(
label: string,
items: vscode.QuickPickItem[]
): Promise<vscode.QuickPickItem> {
const options: vscode.QuickPickOptions = {
placeHolder: label,
ignoreFocusOut: true,
ignoreFocusOut: true
};
const selected: vscode.QuickPickItem | undefined = await vscode.window.showQuickPick(items, options);
const selected:
| vscode.QuickPickItem
| undefined = await vscode.window.showQuickPick(items, options);
if (!selected) {
throw new UserCancelledError(label);
}
@ -118,15 +131,20 @@ export class UI {
* @param label label
* @param defaultUri default uri
*/
static async showOpenDialog(label: string, defaultUri?: vscode.Uri): Promise<string> {
static async showOpenDialog(
label: string,
defaultUri?: vscode.Uri
): Promise<string> {
const options: vscode.OpenDialogOptions = {
openLabel: label,
defaultUri,
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
canSelectMany: false
};
const selected: vscode.Uri[] | undefined = await vscode.window.showOpenDialog(options);
const selected:
| vscode.Uri[]
| undefined = await vscode.window.showOpenDialog(options);
if (!selected || selected.length === 0) {
throw new UserCancelledError(label);
}
@ -139,9 +157,13 @@ export class UI {
* @param type model type
* @param folder target folder
*/
static async inputModelName(label: string, type: ModelType, folder: string): Promise<string> {
static async inputModelName(
label: string,
type: ModelType,
folder: string
): Promise<string> {
const placeHolder = `${type} name`;
const validateInput = async (name: string): Promise<string|undefined> => {
const validateInput = async (name: string): Promise<string | undefined> => {
return await Utility.validateModelName(name, type, folder);
};
return await UI.showInputBox(label, placeHolder, validateInput);
@ -158,16 +180,18 @@ export class UI {
static async showInputBox(
label: string,
placeHolder: string,
validateInput?: (s: string) => string | undefined | Promise<string | undefined>,
validateInput?: (
s: string
) => string | undefined | Promise<string | undefined>,
value?: string,
ignoreFocusOut = true,
ignoreFocusOut = true
): Promise<string> {
const options: vscode.InputBoxOptions = {
prompt: label,
placeHolder,
validateInput,
value,
ignoreFocusOut,
ignoreFocusOut
};
const input: string | undefined = await vscode.window.showInputBox(options);
if (!input) {
@ -181,10 +205,14 @@ export class UI {
* @param label label
*/
static async inputConnectionString(label: string): Promise<string> {
const validateInput = (name: string): string|undefined => {
const validateInput = (name: string): string | undefined => {
return Utility.validateNotEmpty(name, "Connection string");
};
return await UI.showInputBox(label, UIConstants.REPOSITORY_CONNECTION_STRING_TEMPLATE, validateInput);
return await UI.showInputBox(
label,
UIConstants.REPOSITORY_CONNECTION_STRING_TEMPLATE,
validateInput
);
}
/**
@ -192,29 +220,34 @@ export class UI {
* @param label label
* @param type model type
*/
static async selectModelFiles(label: string, type?: ModelType): Promise<string[]> {
static async selectModelFiles(
label: string,
type?: ModelType
): Promise<string[]> {
const fileInfos: ModelFileInfo[] = await UI.findModelFiles(type);
if (fileInfos.length === 0) {
UI.showNotification(MessageType.Warn, UIConstants.MODELS_NOT_FOUND_MSG);
return [];
}
const items: Array<QuickPickItemWithData<string>> = fileInfos.map((f) => {
const items: Array<QuickPickItemWithData<string>> = fileInfos.map(f => {
return {
label: path.basename(f.filePath),
description: f.id,
data: f.filePath,
data: f.filePath
};
});
const selected: Array<QuickPickItemWithData<string>> | undefined = await vscode.window.showQuickPick(items, {
const selected:
| Array<QuickPickItemWithData<string>>
| undefined = await vscode.window.showQuickPick(items, {
placeHolder: label,
ignoreFocusOut: true,
canPickMany: true,
matchOnDescription: true,
matchOnDescription: true
});
if (!selected || selected.length === 0) {
throw new UserCancelledError(label);
}
return selected.map((s) => s.data);
return selected.map(s => s.data);
}
/**
@ -222,24 +255,29 @@ export class UI {
* @param label label
* @param type model type
*/
static async selectOneModelFile(label: string, type?: ModelType): Promise<string> {
static async selectOneModelFile(
label: string,
type?: ModelType
): Promise<string> {
const fileInfos: ModelFileInfo[] = await UI.findModelFiles(type);
if (fileInfos.length === 0) {
UI.showNotification(MessageType.Warn, UIConstants.MODELS_NOT_FOUND_MSG);
return Constants.EMPTY_STRING;
}
const items: Array<QuickPickItemWithData<string>> = fileInfos.map((f) => {
const items: Array<QuickPickItemWithData<string>> = fileInfos.map(f => {
return {
label: path.basename(f.filePath),
description: f.id,
data: f.filePath,
data: f.filePath
};
});
const selected: QuickPickItemWithData<string> | undefined = await vscode.window.showQuickPick(items, {
const selected:
| QuickPickItemWithData<string>
| undefined = await vscode.window.showQuickPick(items, {
placeHolder: label,
ignoreFocusOut: true,
canPickMany: false,
matchOnDescription: true,
matchOnDescription: true
});
if (!selected) {
throw new UserCancelledError(label);
@ -253,13 +291,15 @@ export class UI {
*/
static async findModelFiles(type?: ModelType): Promise<ModelFileInfo[]> {
const fileInfos: ModelFileInfo[] = [];
const files: vscode.Uri[] = await vscode.workspace.findFiles(UIConstants.MODEL_FILE_GLOB);
const files: vscode.Uri[] = await vscode.workspace.findFiles(
UIConstants.MODEL_FILE_GLOB
);
if (files.length === 0) {
return fileInfos;
}
// process in parallel
await Promise.all(
files.map(async (f) => {
files.map(async f => {
let fileInfo: ModelFileInfo | undefined;
try {
fileInfo = await Utility.getModelFileInfo(f.fsPath);
@ -273,7 +313,7 @@ export class UI {
if (!type || type === fileInfo.type) {
fileInfos.push(fileInfo);
}
}),
})
);
return fileInfos;
}
@ -284,20 +324,26 @@ export class UI {
* @param files file list
*/
static async ensureFilesSaved(label: string, files: string[]): Promise<void> {
const dirtyFiles: vscode.TextDocument[] = vscode.workspace.textDocuments.filter((f) => f.isDirty);
const unsaved: vscode.TextDocument[] = dirtyFiles.filter((f) => files.some((file) => file === f.fileName));
const dirtyFiles: vscode.TextDocument[] = vscode.workspace.textDocuments.filter(
f => f.isDirty
);
const unsaved: vscode.TextDocument[] = dirtyFiles.filter(f =>
files.some(file => file === f.fileName)
);
if (unsaved.length === 0) {
return;
}
const nameList: string = unsaved.map((f) => path.basename(f.fileName)).toString();
const nameList: string = unsaved
.map(f => path.basename(f.fileName))
.toString();
const message = `${UIConstants.ASK_TO_SAVE_MSG} [${nameList}]`;
const choice: string | undefined = await vscode.window.showWarningMessage(
message,
ChoiceType.Yes,
ChoiceType.Cancel,
ChoiceType.Cancel
);
if (choice === ChoiceType.Yes) {
await Promise.all(unsaved.map((f) => f.save()));
await Promise.all(unsaved.map(f => f.save()));
} else {
throw new UserCancelledError(label);
}

Просмотреть файл

@ -11,7 +11,8 @@ export class UIConstants {
static readonly SELECT_REPOSITORY_LABEL = "Select model repository";
static readonly SELECT_MODELS_LABEL = "Select device models";
static readonly SELECT_CAPABILITY_MODEL_LABEL = "Select a capability model";
static readonly INPUT_REPOSITORY_CONNECTION_STRING_LABEL = "Input company repository connection string";
static readonly INPUT_REPOSITORY_CONNECTION_STRING_LABEL =
"Input company repository connection string";
static readonly SAVE_FILE_CHANGE_LABEL = "Save file change";
static readonly MODEL_REPOSITORY_TITLE = "IoT Plug and Play Model Repository";
static readonly MODEL_FILE_GLOB = "**/*.json";
@ -20,6 +21,7 @@ export class UIConstants {
"SharedAccessKeyName=<Shared AccessKey Name>;SharedAccessKey=<access Key>";
static readonly MODELS_NOT_FOUND_MSG =
"No device model is found in current workspace. Please open the folder that contains models and try again";
static readonly ASK_TO_SAVE_MSG = "The following files contain unsaved changes, do you want to save them?";
static readonly ASK_TO_SAVE_MSG =
"The following files contain unsaved changes, do you want to save them?";
static readonly ASK_TO_OVERWRITE_MSG = "do you want to overwrite it?";
}

Просмотреть файл

@ -1,24 +1,26 @@
'use strict';
"use strict";
import * as fs from 'fs-plus';
import * as path from 'path';
import * as sdk from 'vscode-iot-device-cube-sdk';
import * as crypto from 'crypto';
import { ScaffoldType } from './constants';
import extractzip = require('extract-zip');
import * as fs from "fs-plus";
import * as path from "path";
import * as sdk from "vscode-iot-device-cube-sdk";
import * as crypto from "crypto";
import { ScaffoldType } from "./constants";
import extractzip = require("extract-zip");
export class FileUtility {
static async directoryExists(type: ScaffoldType, dirPath: string):
Promise<boolean> {
static async directoryExists(
type: ScaffoldType,
dirPath: string
): Promise<boolean> {
if (type === ScaffoldType.Local) {
if (!await sdk.FileSystem.exists(dirPath)) {
if (!(await sdk.FileSystem.exists(dirPath))) {
return false;
}
const isDirectory = await sdk.FileSystem.isDirectory(dirPath);
return isDirectory;
} else {
return new Promise((resolve: (exist: boolean) => void) => {
fs.stat(dirPath, (error: Error|null, stats) => {
fs.stat(dirPath, (error: Error | null, stats) => {
if (error) {
resolve(false);
return;
@ -30,8 +32,10 @@ export class FileUtility {
}
}
static async fileExists(type: ScaffoldType, filePath: string):
Promise<boolean> {
static async fileExists(
type: ScaffoldType,
filePath: string
): Promise<boolean> {
if (type === ScaffoldType.Local) {
const directoryExists = await sdk.FileSystem.exists(filePath);
if (!directoryExists) {
@ -41,7 +45,7 @@ export class FileUtility {
return isFile;
} else {
return new Promise((resolve: (exist: boolean) => void) => {
fs.stat(filePath, (error: Error|null, stats) => {
fs.stat(filePath, (error: Error | null, stats) => {
if (error) {
resolve(false);
return;
@ -58,7 +62,7 @@ export class FileUtility {
return await sdk.FileSystem.mkDir(dirPath);
} else {
return new Promise((resolve: (value?: void) => void, reject) => {
fs.mkdir(dirPath, (error) => {
fs.mkdir(dirPath, error => {
if (error) {
reject(error);
return;
@ -70,8 +74,10 @@ export class FileUtility {
}
}
static async mkdirRecursively(type: ScaffoldType, dirPath: string):
Promise<void> {
static async mkdirRecursively(
type: ScaffoldType,
dirPath: string
): Promise<void> {
if (await FileUtility.directoryExists(type, dirPath)) {
return;
}
@ -88,13 +94,15 @@ export class FileUtility {
// Make sure filepath's parent directory exists
static async writeFile(
type: ScaffoldType, filePath: string,
data: string|Buffer): Promise<void> {
type: ScaffoldType,
filePath: string,
data: string | Buffer
): Promise<void> {
if (type === ScaffoldType.Local) {
return await sdk.FileSystem.writeFile(filePath, data);
} else {
return new Promise((resolve: (value?: void) => void, reject) => {
fs.writeFile(filePath, data, (err) => {
fs.writeFile(filePath, data, err => {
if (err) {
reject(err);
return;
@ -113,40 +121,46 @@ export class FileUtility {
* @param data Json object
*/
static async writeJsonFile(
type: ScaffoldType,
fileDestPath: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type: ScaffoldType, fileDestPath: string, data: any): Promise<void> {
data: any
): Promise<void> {
const indentationSpace = 4;
const jsonString = JSON.stringify(data, null, indentationSpace);
const fileDir = path.dirname(fileDestPath);
if (!await FileUtility.directoryExists(type, fileDir)) {
if (!(await FileUtility.directoryExists(type, fileDir))) {
await FileUtility.mkdirRecursively(type, fileDir);
}
await FileUtility.writeFile(type, fileDestPath, jsonString);
}
static async readFile(
type: ScaffoldType, filePath: string,
encoding?: string): Promise<string|Buffer> {
type: ScaffoldType,
filePath: string,
encoding?: string
): Promise<string | Buffer> {
if (type === ScaffoldType.Local) {
return await sdk.FileSystem.readFile(filePath, encoding);
} else {
return new Promise(
(resolve: (data: string|Buffer) => void, reject) => {
fs.readFile(filePath, encoding, (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
return new Promise((resolve: (data: string | Buffer) => void, reject) => {
fs.readFile(filePath, encoding, (err, data) => {
if (err) {
reject(err);
return;
});
}
resolve(data);
return;
});
});
}
}
static async extractZipFile(sourceZip: string, targetFoder: string):
Promise<boolean> {
static async extractZipFile(
sourceZip: string,
targetFoder: string
): Promise<boolean> {
return new Promise((resolve: (value: boolean) => void, reject) => {
extractzip(sourceZip, { dir: targetFoder }, err => {
if (err) {
@ -158,22 +172,24 @@ export class FileUtility {
});
}
static async getFileHash(filename: string, algorithm = 'md5'):
Promise<string> {
static async getFileHash(
filename: string,
algorithm = "md5"
): Promise<string> {
const hash = crypto.createHash(algorithm);
const input = fs.createReadStream(filename);
let hashvalue = '';
let hashvalue = "";
return new Promise((resolve: (value: string) => void, reject) => {
input.on('readable', () => {
input.on("readable", () => {
const data = input.read();
if (data) {
hash.update(data);
}
});
input.on('error', reject);
input.on('end', () => {
hashvalue = hash.digest('hex');
input.on("error", reject);
input.on("end", () => {
hashvalue = hash.digest("hex");
return resolve(hashvalue);
});
});

Просмотреть файл

@ -1,18 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as path from 'path';
import * as vscode from 'vscode';
import * as path from "path";
import * as vscode from "vscode";
import { CancelOperationError } from './CancelOperationError';
import { ConfigHandler } from './configHandler';
import { ConfigKey, OSPlatform } from './constants';
import { PickWithData } from './Models/Interfaces/UI';
import { getHomeDir, getPlatform } from './utils';
import { CancelOperationError } from "./CancelOperationError";
import { ConfigHandler } from "./configHandler";
import { ConfigKey, OSPlatform } from "./constants";
import { PickWithData } from "./Models/Interfaces/UI";
import { getHomeDir, getPlatform } from "./utils";
export class IoTWorkbenchSettings {
private workbenchPath = '';
private static instance: IoTWorkbenchSettings|undefined;
private workbenchPath = "";
private static instance: IoTWorkbenchSettings | undefined;
private constructor() {}
@ -20,11 +20,13 @@ export class IoTWorkbenchSettings {
if (!IoTWorkbenchSettings.instance) {
IoTWorkbenchSettings.instance = new IoTWorkbenchSettings();
IoTWorkbenchSettings.instance.workbenchPath =
ConfigHandler.get<string>(ConfigKey.workbench) ||
(await this.getDefaultWorkbenchPath());
ConfigHandler.get<string>(ConfigKey.workbench) ||
(await this.getDefaultWorkbenchPath());
await ConfigHandler.update(
ConfigKey.workbench, IoTWorkbenchSettings.instance.workbenchPath,
vscode.ConfigurationTarget.Global);
ConfigKey.workbench,
IoTWorkbenchSettings.instance.workbenchPath,
vscode.ConfigurationTarget.Global
);
}
return IoTWorkbenchSettings.instance;
@ -34,15 +36,15 @@ export class IoTWorkbenchSettings {
const platform = await getPlatform();
const homeDir = await getHomeDir();
let workbenchPath = '';
let workbenchPath = "";
if (platform === OSPlatform.WIN32) {
workbenchPath = path.join(homeDir, 'Documents', 'IoTWorkbenchProjects');
workbenchPath = path.join(homeDir, "Documents", "IoTWorkbenchProjects");
} else if (platform === OSPlatform.LINUX) {
workbenchPath = path.join(homeDir, 'IoTWorkbenchProjects');
workbenchPath = path.join(homeDir, "IoTWorkbenchProjects");
} else if (platform === OSPlatform.DARWIN) {
workbenchPath = path.join(homeDir, 'Documents', 'IoTWorkbenchProjects');
workbenchPath = path.join(homeDir, "Documents", "IoTWorkbenchProjects");
} else {
workbenchPath = '/IoTWorkbenchProjects';
workbenchPath = "/IoTWorkbenchProjects";
}
return workbenchPath;
@ -56,7 +58,7 @@ export class IoTWorkbenchSettings {
const selection = await this.selectWorkbenchPath();
let userWorkbenchPath;
if (selection.data === '$') {
if (selection.data === "$") {
userWorkbenchPath = await this.selectFolder();
} else {
userWorkbenchPath = selection.data;
@ -64,29 +66,32 @@ export class IoTWorkbenchSettings {
if (userWorkbenchPath) {
await ConfigHandler.update(
ConfigKey.workbench, userWorkbenchPath,
vscode.ConfigurationTarget.Global);
ConfigKey.workbench,
userWorkbenchPath,
vscode.ConfigurationTarget.Global
);
await vscode.window.showInformationMessage(
'Change workbench successfully.');
"Change workbench successfully."
);
}
}
private async selectWorkbenchPath(): Promise<PickWithData<string>> {
const userWorkbenchPath = this.getWorkbenchPath();
const workbenchPicks: Array<PickWithData<string>> = [
{ label: userWorkbenchPath, description: '', data: userWorkbenchPath },
{ label: '$(file-directory) Browse...', description: '', data: '$' }
{ label: userWorkbenchPath, description: "", data: userWorkbenchPath },
{ label: "$(file-directory) Browse...", description: "", data: "$" }
];
const selection = await vscode.window.showQuickPick(workbenchPicks, {
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select workbench folder'
placeHolder: "Select workbench folder"
});
if (!selection) {
throw new CancelOperationError('Workbench path selection cancelled.');
throw new CancelOperationError("Workbench path selection cancelled.");
}
return selection;
}
@ -94,14 +99,14 @@ export class IoTWorkbenchSettings {
private async selectFolder(): Promise<string> {
const options: vscode.OpenDialogOptions = {
canSelectMany: false,
openLabel: 'Select',
openLabel: "Select",
canSelectFolders: true,
canSelectFiles: false
};
const folderUri = await vscode.window.showOpenDialog(options);
if (!folderUri || folderUri.length === 0) {
throw new CancelOperationError('Folder selection cancelled.');
throw new CancelOperationError("Folder selection cancelled.");
}
return folderUri[0].fsPath;

Просмотреть файл

@ -1,36 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as crypto from 'crypto';
import * as fs from 'fs-plus';
import * as getmac from 'getmac';
import { Guid } from 'guid-typescript';
import * as _ from 'lodash';
import * as opn from 'opn';
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import * as WinReg from 'winreg';
import * as crypto from "crypto";
import * as fs from "fs-plus";
import * as getmac from "getmac";
import { Guid } from "guid-typescript";
import * as _ from "lodash";
import * as opn from "opn";
import * as os from "os";
import * as path from "path";
import * as vscode from "vscode";
import * as WinReg from "winreg";
import { BoardProvider } from '../boardProvider';
import { ArduinoCommands } from '../common/Commands';
import { ConfigHandler } from '../configHandler';
import { ConfigKey, FileNames, OSPlatform, ScaffoldType } from '../constants';
import { DialogResponses } from '../DialogResponses';
import { FileUtility } from '../FileUtility';
import { TelemetryContext } from '../telemetry';
import { delay, getRegistryValues } from '../utils';
import { BoardProvider } from "../boardProvider";
import { ArduinoCommands } from "../common/Commands";
import { ConfigHandler } from "../configHandler";
import { ConfigKey, FileNames, OSPlatform, ScaffoldType } from "../constants";
import { DialogResponses } from "../DialogResponses";
import { FileUtility } from "../FileUtility";
import { TelemetryContext } from "../telemetry";
import { delay, getRegistryValues } from "../utils";
import { ArduinoDeviceBase } from './ArduinoDeviceBase';
import { DeviceType } from './Interfaces/Device';
import { DeviceConfig, TemplateFileInfo } from './Interfaces/ProjectTemplate';
import { Board } from './Interfaces/Board';
import { reject } from 'bluebird';
import { ArduinoDeviceBase } from "./ArduinoDeviceBase";
import { DeviceType } from "./Interfaces/Device";
import { DeviceConfig, TemplateFileInfo } from "./Interfaces/ProjectTemplate";
import { Board } from "./Interfaces/Board";
import { reject } from "bluebird";
const impor = require('impor')(__dirname);
const forEach = impor('lodash.foreach') as typeof import('lodash.foreach');
const trimStart =
impor('lodash.trimstart') as typeof import('lodash.trimstart');
const impor = require("impor")(__dirname);
const forEach = impor("lodash.foreach") as typeof import("lodash.foreach");
const trimStart = impor(
"lodash.trimstart"
) as typeof import("lodash.trimstart");
interface SerialPortInfo {
comName: string;
@ -40,12 +41,12 @@ interface SerialPortInfo {
}
const constants = {
outputPath: './.build',
platformLocalFileName: 'platform.local.txt',
outputPath: "./.build",
platformLocalFileName: "platform.local.txt",
cExtraFlag: 'compiler.c.extra_flags=-DCORRELATIONID="',
cppExtraFlag: 'compiler.cpp.extra_flags=-DCORRELATIONID="',
traceExtraFlag: ' -DENABLETRACE=',
informationPageUrl: 'https://aka.ms/AA35xln',
traceExtraFlag: " -DENABLETRACE=",
informationPageUrl: "https://aka.ms/AA35xln"
};
enum ConfigDeviceOptions {
@ -58,8 +59,7 @@ export class AZ3166Device extends ArduinoDeviceBase {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static get serialport(): any {
if (!AZ3166Device._serialport) {
AZ3166Device._serialport =
require('../../vendor/node-usb-native').SerialPort;
AZ3166Device._serialport = require("../../vendor/node-usb-native").SerialPort;
}
return AZ3166Device._serialport;
}
@ -73,19 +73,26 @@ export class AZ3166Device extends ArduinoDeviceBase {
}
private templateFiles: TemplateFileInfo[] = [];
private static _boardId = 'devkit';
private static _boardId = "devkit";
static get boardId(): string {
return AZ3166Device._boardId;
}
constructor(
context: vscode.ExtensionContext, channel: vscode.OutputChannel,
telemetryContext: TelemetryContext, devicePath: string,
templateFiles?: TemplateFileInfo[]) {
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext,
devicePath: string,
templateFiles?: TemplateFileInfo[]
) {
super(
context, devicePath, channel, telemetryContext,
DeviceType.MXChipAZ3166);
context,
devicePath,
channel,
telemetryContext,
DeviceType.MXChipAZ3166
);
this.channel = channel;
this.componentId = Guid.create().toString();
if (templateFiles) {
@ -93,9 +100,9 @@ export class AZ3166Device extends ArduinoDeviceBase {
}
}
name = 'AZ3166';
name = "AZ3166";
get board(): Board|undefined {
get board(): Board | undefined {
const boardProvider = new BoardProvider(this.boardFolderPath);
const az3166 = boardProvider.find({ id: AZ3166Device._boardId });
return az3166;
@ -103,7 +110,7 @@ export class AZ3166Device extends ArduinoDeviceBase {
get version(): string {
const packageRootPath = this.getArduinoPackagePath();
let version = '0.0.1';
let version = "0.0.1";
if (fs.existsSync(packageRootPath)) {
const versions = fs.readdirSync(packageRootPath);
@ -132,15 +139,19 @@ export class AZ3166Device extends ArduinoDeviceBase {
const isStlinkInstalled = await this.stlinkDriverInstalled();
if (!isStlinkInstalled) {
const message =
'The ST-LINK driver for DevKit is not installed. Install now?';
const result: vscode.MessageItem|undefined =
await vscode.window.showWarningMessage(
message, DialogResponses.yes, DialogResponses.skipForNow,
DialogResponses.cancel);
"The ST-LINK driver for DevKit is not installed. Install now?";
const result:
| vscode.MessageItem
| undefined = await vscode.window.showWarningMessage(
message,
DialogResponses.yes,
DialogResponses.skipForNow,
DialogResponses.cancel
);
if (result === DialogResponses.yes) {
// Open the download page
const installUri =
'http://www.st.com/en/development-tools/stsw-link009.html';
"http://www.st.com/en/development-tools/stsw-link009.html";
opn(installUri);
return true;
} else if (result !== DialogResponses.cancel) {
@ -154,43 +165,51 @@ export class AZ3166Device extends ArduinoDeviceBase {
async configDeviceSettings(): Promise<boolean> {
const configSelectionItems = this.getConfigDeviceSettingsMainOptions();
const configSelection =
await vscode.window.showQuickPick(configSelectionItems, {
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select an option',
});
const configSelection = await vscode.window.showQuickPick(
configSelectionItems,
{
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: "Select an option"
}
);
if (!configSelection) {
return false;
}
if (configSelection.detail === 'Config CRC') {
const retValue: boolean =
await this.generateCrc(this.channel);
if (configSelection.detail === "Config CRC") {
const retValue: boolean = await this.generateCrc(this.channel);
return retValue;
} else if (configSelection.detail === 'Config Connection String') {
} else if (configSelection.detail === "Config Connection String") {
// Get IoT Hub device connection string from config
let deviceConnectionString =
ConfigHandler.get<string>(ConfigKey.iotHubDeviceConnectionString);
const deviceConnectionStringSelection =
this.getDeviceConnectionStringOptions(deviceConnectionString);
let deviceConnectionString = ConfigHandler.get<string>(
ConfigKey.iotHubDeviceConnectionString
);
const deviceConnectionStringSelection = this.getDeviceConnectionStringOptions(
deviceConnectionString
);
const selection = await vscode.window.showQuickPick(
deviceConnectionStringSelection,
{ ignoreFocusOut: true, placeHolder: 'Choose an option:' });
{ ignoreFocusOut: true, placeHolder: "Choose an option:" }
);
if (!selection) {
return false;
}
if (selection.label === 'Input IoT Hub Device Connection String') {
if (selection.label === "Input IoT Hub Device Connection String") {
const option = this.getInputDeviceConnectionStringOptions();
deviceConnectionString = await vscode.window.showInputBox(option);
if (!deviceConnectionString) {
const message =
'Need more information on how to get device connection string?';
const result: vscode.MessageItem|undefined =
await vscode.window.showWarningMessage(
message, DialogResponses.yes, DialogResponses.no);
"Need more information on how to get device connection string?";
const result:
| vscode.MessageItem
| undefined = await vscode.window.showWarningMessage(
message,
DialogResponses.yes,
DialogResponses.no
);
if (result === DialogResponses.yes) {
opn(constants.informationPageUrl);
}
@ -203,15 +222,18 @@ export class AZ3166Device extends ArduinoDeviceBase {
console.log(deviceConnectionString);
const res = await this.setDeviceConfig(
deviceConnectionString, ConfigDeviceOptions.ConnectionString);
deviceConnectionString,
ConfigDeviceOptions.ConnectionString
);
if (!res) {
return false;
} else {
vscode.window.showInformationMessage(
'Configure Device connection string completely.');
"Configure Device connection string completely."
);
return true;
}
} else if (configSelection.detail === 'Config DPS') {
} else if (configSelection.detail === "Config DPS") {
const option = this.getDPSConnectionStringOptions();
const deviceConnectionString = await vscode.window.showInputBox(option);
if (!deviceConnectionString) {
@ -220,15 +242,17 @@ export class AZ3166Device extends ArduinoDeviceBase {
console.log(deviceConnectionString);
const res = await this.setDeviceConfig(
deviceConnectionString, ConfigDeviceOptions.DPS);
deviceConnectionString,
ConfigDeviceOptions.DPS
);
if (!res) {
return false;
} else {
vscode.window.showInformationMessage(
'Config DPS credentials completely.');
"Config DPS credentials completely."
);
return true;
}
} else {
const option = this.getUDSStringOptions();
const UDS = await vscode.window.showInputBox(option);
@ -242,24 +266,33 @@ export class AZ3166Device extends ArduinoDeviceBase {
return false;
} else {
vscode.window.showInformationMessage(
'Configure Unique Device String (UDS) completed successfully.');
"Configure Unique Device String (UDS) completed successfully."
);
return true;
}
}
}
private async getConfigDeviceSettingsMainOptions():
Promise<vscode.QuickPickItem[]> {
private async getConfigDeviceSettingsMainOptions(): Promise<
vscode.QuickPickItem[]
> {
// Read options configuration JSON
const devciceConfigFilePath: string =
this.extensionContext.asAbsolutePath(path.join(
FileNames.resourcesFolderName, FileNames.templatesFolderName,
FileNames.configDeviceOptionsFileName));
const devciceConfigFilePath: string = this.extensionContext.asAbsolutePath(
path.join(
FileNames.resourcesFolderName,
FileNames.templatesFolderName,
FileNames.configDeviceOptionsFileName
)
);
const configSelectionItemsFilePath = await FileUtility.readFile(
ScaffoldType.Local, devciceConfigFilePath, 'utf8');
const configSelectionItemsContent =
JSON.parse(configSelectionItemsFilePath as string);
ScaffoldType.Local,
devciceConfigFilePath,
"utf8"
);
const configSelectionItemsContent = JSON.parse(
configSelectionItemsFilePath as string
);
const configSelectionItems: vscode.QuickPickItem[] = [];
configSelectionItemsContent.configSelectionItems.forEach(
@ -269,24 +302,28 @@ export class AZ3166Device extends ArduinoDeviceBase {
description: element.description,
detail: element.detail
});
});
}
);
return configSelectionItems;
}
private getDeviceConnectionStringOptions(deviceConnectionString: string|
undefined): vscode.QuickPickItem[] {
let hostName = '';
let deviceId = '';
private getDeviceConnectionStringOptions(
deviceConnectionString: string | undefined
): vscode.QuickPickItem[] {
let hostName = "";
let deviceId = "";
if (deviceConnectionString) {
const hostnameMatches =
deviceConnectionString.match(/HostName=(.*?)(;|$)/);
const hostnameMatches = deviceConnectionString.match(
/HostName=(.*?)(;|$)/
);
if (hostnameMatches) {
hostName = hostnameMatches[0];
}
const deviceIDMatches =
deviceConnectionString.match(/DeviceId=(.*?)(;|$)/);
const deviceIDMatches = deviceConnectionString.match(
/DeviceId=(.*?)(;|$)/
);
if (deviceIDMatches) {
deviceId = deviceIDMatches[0];
}
@ -296,22 +333,24 @@ export class AZ3166Device extends ArduinoDeviceBase {
if (deviceId && hostName) {
deviceConnectionStringSelection = [
{
label: 'Select IoT Hub Device Connection String',
description: '',
label: "Select IoT Hub Device Connection String",
description: "",
detail: `Device Information: ${hostName} ${deviceId}`
},
{
label: 'Input IoT Hub Device Connection String',
description: '',
detail: ''
label: "Input IoT Hub Device Connection String",
description: "",
detail: ""
}
];
} else {
deviceConnectionStringSelection = [{
label: 'Input IoT Hub Device Connection String',
description: '',
detail: ''
}];
deviceConnectionStringSelection = [
{
label: "Input IoT Hub Device Connection String",
description: "",
detail: ""
}
];
}
return deviceConnectionStringSelection;
}
@ -319,18 +358,20 @@ export class AZ3166Device extends ArduinoDeviceBase {
private getInputDeviceConnectionStringOptions(): vscode.InputBoxOptions {
const option: vscode.InputBoxOptions = {
value:
'HostName=<Host Name>;DeviceId=<Device Name>;SharedAccessKey=<Device Key>',
"HostName=<Host Name>;DeviceId=<Device Name>;SharedAccessKey=<Device Key>",
prompt: `Please input device connection string here.`,
ignoreFocusOut: true,
validateInput: (deviceConnectionString: string) => {
if (!deviceConnectionString) {
return 'Please provide a valid device connection string.';
return "Please provide a valid device connection string.";
}
if ((deviceConnectionString.indexOf('HostName') === -1) ||
(deviceConnectionString.indexOf('DeviceId') === -1) ||
(deviceConnectionString.indexOf('SharedAccessKey') === -1)) {
return 'The format of the IoT Hub Device connection string is invalid.';
if (
deviceConnectionString.indexOf("HostName") === -1 ||
deviceConnectionString.indexOf("DeviceId") === -1 ||
deviceConnectionString.indexOf("SharedAccessKey") === -1
) {
return "The format of the IoT Hub Device connection string is invalid.";
}
return;
}
@ -342,19 +383,21 @@ export class AZ3166Device extends ArduinoDeviceBase {
private getDPSConnectionStringOptions(): vscode.InputBoxOptions {
const option: vscode.InputBoxOptions = {
value:
'DPSEndpoint=global.azure-devices-provisioning.net;IdScope=<Id Scope>;DeviceId=<Device Id>;SymmetricKey=<Symmetric Key>',
"DPSEndpoint=global.azure-devices-provisioning.net;IdScope=<Id Scope>;DeviceId=<Device Id>;SymmetricKey=<Symmetric Key>",
prompt: `Please input DPS credentials here.`,
ignoreFocusOut: true,
validateInput: (deviceConnectionString: string) => {
if (!deviceConnectionString) {
return 'Please provide a valid DPS credentials.';
return "Please provide a valid DPS credentials.";
}
if ((deviceConnectionString.indexOf('DPSEndpoint') === -1) ||
(deviceConnectionString.indexOf('IdScope') === -1) ||
(deviceConnectionString.indexOf('DeviceId') === -1) ||
(deviceConnectionString.indexOf('SymmetricKey') === -1)) {
return 'The format of the DPS credentials is invalid.';
if (
deviceConnectionString.indexOf("DPSEndpoint") === -1 ||
deviceConnectionString.indexOf("IdScope") === -1 ||
deviceConnectionString.indexOf("DeviceId") === -1 ||
deviceConnectionString.indexOf("SymmetricKey") === -1
) {
return "The format of the DPS credentials is invalid.";
}
return;
}
@ -365,8 +408,8 @@ export class AZ3166Device extends ArduinoDeviceBase {
private getUDSStringOptions(): vscode.InputBoxOptions {
function generateRandomHex(): string {
const chars = '0123456789abcdef'.split('');
let hexNum = '';
const chars = "0123456789abcdef".split("");
let hexNum = "";
for (let i = 0; i < 64; i++) {
hexNum += chars[Math.floor(Math.random() * 16)];
}
@ -379,20 +422,22 @@ export class AZ3166Device extends ArduinoDeviceBase {
ignoreFocusOut: true,
validateInput: (UDS: string) => {
if (/^([0-9a-f]){64}$/i.test(UDS) === false) {
return 'The format of the UDS is invalid. Please provide a valid UDS.';
return "The format of the UDS is invalid. Please provide a valid UDS.";
}
return '';
return "";
}
};
return option;
}
async setDeviceConfig(configValue: string, option: number): Promise<boolean> {
// Try to close serial monitor
try {
await vscode.commands.executeCommand(
ArduinoCommands.CloseSerialMonitor, null, false);
ArduinoCommands.CloseSerialMonitor,
null,
false
);
} catch (ignore) {
// Ignore error if fail to close serial monitor
}
@ -406,15 +451,18 @@ export class AZ3166Device extends ArduinoDeviceBase {
}
}
async flushDeviceConfigUnixAndMac(configValue: string, option: number):
Promise<boolean> {
async flushDeviceConfigUnixAndMac(
configValue: string,
option: number
): Promise<boolean> {
return new Promise(
// eslint-disable-next-line no-async-promise-executor
async (
resolve: (value: boolean) => void,
reject: (value: Error) => void) => {
let comPort = '';
let command = '';
reject: (value: Error) => void
) => {
let comPort = "";
let command = "";
try {
// Choose COM port that AZ3166 is connected
comPort = await this.chooseCOM();
@ -423,11 +471,11 @@ export class AZ3166Device extends ArduinoDeviceBase {
reject(error);
}
if (option === ConfigDeviceOptions.ConnectionString) {
command = 'set_az_iothub';
command = "set_az_iothub";
} else if (option === ConfigDeviceOptions.DPS) {
command = 'set_az_iotdps';
command = "set_az_iotdps";
} else {
command = 'set_dps_uds';
command = "set_dps_uds";
}
let errorRejected = false;
@ -435,7 +483,8 @@ export class AZ3166Device extends ArduinoDeviceBase {
if (!az3166) {
return reject(
new Error('IoT DevKit is not found in the board list.'));
new Error("IoT DevKit is not found in the board list.")
);
}
const port = new AZ3166Device.serialport(comPort, {
@ -444,7 +493,7 @@ export class AZ3166Device extends ArduinoDeviceBase {
stopBits: 1,
xon: false,
xoff: false,
parity: 'none'
parity: "none"
});
const rejectIfError = (err: Error): boolean => {
@ -489,34 +538,39 @@ export class AZ3166Device extends ArduinoDeviceBase {
};
// Configure serial port callbacks
port.on('open', async () => {
port.on("open", async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await vscode.window.showInformationMessage(
'Please hold down button A and then push and release the reset button to enter configuration mode. After enter configuration mode, click OK.',
'OK');
"Please hold down button A and then push and release the reset button to enter configuration mode. After enter configuration mode, click OK.",
"OK"
);
executeSetAzIoTHub()
.then(() => resolve(true))
.catch((error) => reject(error));
.catch(error => reject(error));
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
port.on('error', (error: any) => {
port.on("error", (error: any) => {
if (errorRejected) return;
console.log(error);
rejectIfError(error);
});
});
}
);
}
async flushDeviceConfig(configValue: string, option: number):
Promise<boolean> {
async flushDeviceConfig(
configValue: string,
option: number
): Promise<boolean> {
return new Promise(
// eslint-disable-next-line no-async-promise-executor
async (
resolve: (value: boolean) => void,
reject: (value: Error) => void) => {
let comPort = '';
let command = '';
reject: (value: Error) => void
) => {
let comPort = "";
let command = "";
try {
// Choose COM port that AZ3166 is connected
comPort = await this.chooseCOM();
@ -525,11 +579,11 @@ export class AZ3166Device extends ArduinoDeviceBase {
reject(error);
}
if (option === ConfigDeviceOptions.ConnectionString) {
command = 'set_az_iothub';
command = "set_az_iothub";
} else if (option === ConfigDeviceOptions.DPS) {
command = 'set_az_iotdps';
command = "set_az_iotdps";
} else {
command = 'set_dps_uds';
command = "set_dps_uds";
}
let configMode = false;
let errorRejected = false;
@ -540,7 +594,8 @@ export class AZ3166Device extends ArduinoDeviceBase {
if (!az3166) {
return reject(
new Error('IoT DevKit is not found in the board list.'));
new Error("IoT DevKit is not found in the board list.")
);
}
const port = new AZ3166Device.serialport(comPort, {
@ -549,7 +604,7 @@ export class AZ3166Device extends ArduinoDeviceBase {
stopBits: 1,
xon: false,
xoff: false,
parity: 'none'
parity: "none"
});
const rejectIfError = (err: Error): boolean => {
@ -572,11 +627,12 @@ export class AZ3166Device extends ArduinoDeviceBase {
const data = `${command} "${configValue}"\r\n`;
const maxDataLength = 256;
await this.sendDataViaSerialPort(
port, data.slice(0, maxDataLength));
port,
data.slice(0, maxDataLength)
);
if (data.length > maxDataLength) {
await delay(1000);
await this.sendDataViaSerialPort(
port, data.slice(maxDataLength));
await this.sendDataViaSerialPort(port, data.slice(maxDataLength));
}
await delay(1000);
@ -593,37 +649,38 @@ export class AZ3166Device extends ArduinoDeviceBase {
};
// Configure serial port callbacks
port.on('open', () => {
port.on("open", () => {
port.write(
'\r\nhelp\r\n',
"\r\nhelp\r\n",
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(error: any) => {
if (rejectIfError(error)) return;
});
}
);
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
port.on('data', (data: any) => {
port.on("data", (data: any) => {
gotData = true;
const output = data.toString().trim();
if (commandExecuted) return;
if (output.includes('set_')) {
if (output.includes("set_")) {
commandExecuted = true;
configMode = true;
executeSetAzIoTHub()
.then(() => resolve(true))
.catch((error) => reject(error));
.catch(error => reject(error));
} else {
configMode = false;
}
if (configMode) {
forEach(output.split('\n'), line => {
forEach(output.split("\n"), line => {
if (line) {
line = trimStart(line.trim(), '#').trim();
line = trimStart(line.trim(), "#").trim();
if (line && line.length) {
console.log('SerialOutput', line);
console.log("SerialOutput", line);
}
}
});
@ -631,7 +688,7 @@ export class AZ3166Device extends ArduinoDeviceBase {
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
port.on('error', (error: any) => {
port.on("error", (error: any) => {
if (errorRejected) return;
console.log(error);
rejectIfError(error);
@ -644,25 +701,30 @@ export class AZ3166Device extends ArduinoDeviceBase {
if (!gotData || !configMode) {
vscode.window
.showInformationMessage(
'Please hold down button A and then push and release the reset button to enter configuration mode.')
"Please hold down button A and then push and release the reset button to enter configuration mode."
)
.then(() => {
port.write(
'\r\nhelp\r\n',
"\r\nhelp\r\n",
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(error: any) => {
rejectIfError(error);
});
}
);
});
}
}, 10000);
});
}
);
}
private getComList(): Promise<SerialPortInfo[]> {
return new Promise(
(resolve: (value: SerialPortInfo[]) => void,
reject: (error: Error) => void) => {
(
resolve: (value: SerialPortInfo[]) => void,
reject: (error: Error) => void
) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
AZ3166Device.serialport.list((e: any, ports: SerialPortInfo[]) => {
if (e) {
@ -671,7 +733,8 @@ export class AZ3166Device extends ArduinoDeviceBase {
resolve(ports);
}
});
});
}
);
}
private async chooseCOM(): Promise<string> {
@ -679,20 +742,25 @@ export class AZ3166Device extends ArduinoDeviceBase {
// eslint-disable-next-line no-async-promise-executor
async (
resolve: (value: string) => void,
reject: (reason: Error) => void) => {
reject: (reason: Error) => void
) => {
const comList = await this.getComList();
const az3166 = this.board;
if (!az3166) {
return reject(new Error('AZ3166 is not found in the board list.'));
return reject(new Error("AZ3166 is not found in the board list."));
}
const list = _.filter(comList, com => {
if (com.vendorId && com.productId && az3166.vendorId &&
az3166.productId &&
com.vendorId.toLowerCase().endsWith(az3166.vendorId) &&
com.productId.toLowerCase().endsWith(az3166.productId)) {
if (
com.vendorId &&
com.productId &&
az3166.vendorId &&
az3166.productId &&
com.vendorId.toLowerCase().endsWith(az3166.vendorId) &&
com.productId.toLowerCase().endsWith(az3166.productId)
) {
return true;
} else {
return false;
@ -708,19 +776,22 @@ export class AZ3166Device extends ArduinoDeviceBase {
}
if (!comPort) {
reject(new Error('No avalible COM port.'));
reject(new Error("No avalible COM port."));
}
resolve(comPort);
} else {
reject(new Error('No AZ3166 board connected.'));
reject(new Error("No AZ3166 board connected."));
}
});
}
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private async sendDataViaSerialPort(port: any, data: string):
Promise<boolean> {
private async sendDataViaSerialPort(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
port: any,
data: string
): Promise<boolean> {
return new Promise(
(resolve: (value: boolean) => void, reject: (value: Error) => void) => {
try {
@ -733,11 +804,13 @@ export class AZ3166Device extends ArduinoDeviceBase {
} else {
port.drain(() => resolve(true));
}
});
}
);
} catch (err) {
reject(err);
}
});
}
);
}
private async stlinkDriverInstalled(): Promise<boolean> {
@ -747,8 +820,9 @@ export class AZ3166Device extends ArduinoDeviceBase {
// The STlink driver would write to the following registry.
const pathString = await getRegistryValues(
WinReg.HKLM,
'\\SYSTEM\\ControlSet001\\Control\\Class\\{88bae032-5a81-49f0-bc3d-a4ff138216d6}',
'Class');
"\\SYSTEM\\ControlSet001\\Control\\Class\\{88bae032-5a81-49f0-bc3d-a4ff138216d6}",
"Class"
);
if (pathString) {
return true;
} else {
@ -766,14 +840,15 @@ export class AZ3166Device extends ArduinoDeviceBase {
const arduinoPackagePath = this.getArduinoPackagePath();
function getHashMacAsync(): Promise<unknown> {
return new Promise((resolve) => {
return new Promise(resolve => {
getmac.getMac((err, macAddress) => {
if (err) {
reject(err);
}
const hashMacAddress = crypto.createHash('sha256')
.update(macAddress, 'utf8')
.digest('hex');
const hashMacAddress = crypto
.createHash("sha256")
.update(macAddress, "utf8")
.digest("hex");
resolve(hashMacAddress);
});
});
@ -781,25 +856,28 @@ export class AZ3166Device extends ArduinoDeviceBase {
if (!fs.existsSync(arduinoPackagePath)) {
throw new Error(
'Unable to locate Arduino IDE. Please install it from https://www.arduino.cc/en/main/software and use "Arduino: Board Manager" to install your device packages. Restart VS Code to apply to changes.');
'Unable to locate Arduino IDE. Please install it from https://www.arduino.cc/en/main/software and use "Arduino: Board Manager" to install your device packages. Restart VS Code to apply to changes.'
);
}
const files = fs.readdirSync(arduinoPackagePath);
for (let i = files.length - 1; i >= 0; i--) {
if (files[i] === '.DS_Store') {
if (files[i] === ".DS_Store") {
files.splice(i, 1);
}
}
if (files.length === 0 || files.length > 1) {
throw new Error(
'There are unexpected files or folders under Arduino package installation path. Please clear the folder and reinstall the package for Devkit.');
"There are unexpected files or folders under Arduino package installation path. Please clear the folder and reinstall the package for Devkit."
);
}
const directoryName = path.join(arduinoPackagePath, files[0]);
if (!fs.isDirectorySync(directoryName)) {
throw new Error(
'The Arduino package for MXChip IoT Devkit is not installed. Please follow the guide to install it');
"The Arduino package for MXChip IoT Devkit is not installed. Please follow the guide to install it"
);
}
const fileName = path.join(directoryName, constants.platformLocalFileName);
@ -808,15 +886,15 @@ export class AZ3166Device extends ArduinoDeviceBase {
const hashMacAddress = await getHashMacAsync();
// Create the file of platform.local.txt
const targetFileName =
path.join(directoryName, constants.platformLocalFileName);
const targetFileName = path.join(
directoryName,
constants.platformLocalFileName
);
const content = `${constants.cExtraFlag}${hashMacAddress}" ${
constants.traceExtraFlag}${enableTrace}\r\n` +
`${constants.cppExtraFlag}${hashMacAddress}" ${
constants.traceExtraFlag}${enableTrace}\r\n`;
const content =
`${constants.cExtraFlag}${hashMacAddress}" ${constants.traceExtraFlag}${enableTrace}\r\n` +
`${constants.cppExtraFlag}${hashMacAddress}" ${constants.traceExtraFlag}${enableTrace}\r\n`;
fs.writeFileSync(targetFileName, content);
}
}
@ -824,19 +902,28 @@ export class AZ3166Device extends ArduinoDeviceBase {
const platform = os.platform();
// TODO: Currently, we do not support portable Arduino installation.
let arduinoPackagePath = '';
let arduinoPackagePath = "";
const homeDir = os.homedir();
if (platform === OSPlatform.WIN32) {
arduinoPackagePath =
path.join(homeDir, 'AppData', 'Local', 'Arduino15', 'packages');
arduinoPackagePath = path.join(
homeDir,
"AppData",
"Local",
"Arduino15",
"packages"
);
} else if (platform === OSPlatform.DARWIN) {
arduinoPackagePath =
path.join(homeDir, 'Library', 'Arduino15', 'packages');
arduinoPackagePath = path.join(
homeDir,
"Library",
"Arduino15",
"packages"
);
} else if (platform === OSPlatform.LINUX) {
arduinoPackagePath = path.join(homeDir, '.arduino15', 'packages');
arduinoPackagePath = path.join(homeDir, ".arduino15", "packages");
}
return path.join(arduinoPackagePath, 'AZ3166', 'hardware', 'stm32f4');
return path.join(arduinoPackagePath, "AZ3166", "hardware", "stm32f4");
}
}

Просмотреть файл

@ -1,33 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as vscode from 'vscode';
import * as vscode from "vscode";
import { AzureAccount } from '../azure-account.api';
import { AzureAccountCommands } from '../common/Commands';
import { AzureAccount } from "../azure-account.api";
import { AzureAccountCommands } from "../common/Commands";
import { ExtensionName } from './Interfaces/Api';
import { ExtensionName } from "./Interfaces/Api";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getExtension(name: ExtensionName): any {
switch (name) {
case ExtensionName.Toolkit:{
const toolkit =
vscode.extensions.getExtension('vsciot-vscode.azure-iot-toolkit');
return toolkit ? toolkit.exports : undefined;
}
case ExtensionName.AzureAccount:{
const azureAccount = vscode.extensions.getExtension<AzureAccount>(
'ms-vscode.azure-account');
return azureAccount ? azureAccount.exports : undefined;
}
case ExtensionName.DigitalTwins:{
const digitalTwins =
vscode.extensions.getExtension('vsciot-vscode.azure-digital-twins');
return digitalTwins ? digitalTwins.exports : undefined;
}
default:
return undefined;
case ExtensionName.Toolkit: {
const toolkit = vscode.extensions.getExtension(
"vsciot-vscode.azure-iot-toolkit"
);
return toolkit ? toolkit.exports : undefined;
}
case ExtensionName.AzureAccount: {
const azureAccount = vscode.extensions.getExtension<AzureAccount>(
"ms-vscode.azure-account"
);
return azureAccount ? azureAccount.exports : undefined;
}
case ExtensionName.DigitalTwins: {
const digitalTwins = vscode.extensions.getExtension(
"vsciot-vscode.azure-digital-twins"
);
return digitalTwins ? digitalTwins.exports : undefined;
}
default:
return undefined;
}
}
@ -35,13 +38,14 @@ export async function checkAzureLogin(): Promise<boolean> {
const azureAccount = getExtension(ExtensionName.AzureAccount);
if (!azureAccount) {
throw new Error(
'Azure account extension is not found. Please install it from Marketplace.');
"Azure account extension is not found. Please install it from Marketplace."
);
}
// Sign in Azure
if (azureAccount.status !== 'LoggedIn') {
if (azureAccount.status !== "LoggedIn") {
await vscode.commands.executeCommand(AzureAccountCommands.Login);
}
return true;
}
}

Просмотреть файл

@ -1,37 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as fs from 'fs-plus';
import * as path from 'path';
import * as vscode from 'vscode';
import * as fs from "fs-plus";
import * as path from "path";
import * as vscode from "vscode";
import { VscodeCommands } from '../common/Commands';
import { ConfigHandler } from '../configHandler';
import { ConfigKey, DependentExtensions, FileNames, OperationType, OSPlatform, PlatformType, ScaffoldType } from '../constants';
import { FileUtility } from '../FileUtility';
import { TelemetryContext } from '../telemetry';
import * as utils from '../utils';
import { VscodeCommands } from "../common/Commands";
import { ConfigHandler } from "../configHandler";
import {
ConfigKey,
DependentExtensions,
FileNames,
OperationType,
OSPlatform,
PlatformType,
ScaffoldType
} from "../constants";
import { FileUtility } from "../FileUtility";
import { TelemetryContext } from "../telemetry";
import * as utils from "../utils";
import { Board } from './Interfaces/Board';
import { ComponentType } from './Interfaces/Component';
import { Device, DeviceType } from './Interfaces/Device';
import { TemplateFileInfo } from './Interfaces/ProjectTemplate';
import { OTA } from './OTA';
import { Board } from "./Interfaces/Board";
import { ComponentType } from "./Interfaces/Component";
import { Device, DeviceType } from "./Interfaces/Device";
import { TemplateFileInfo } from "./Interfaces/ProjectTemplate";
import { OTA } from "./OTA";
const constants = {
defaultSketchFileName: 'device.ino',
arduinoJsonFileName: 'arduino.json',
cppPropertiesFileName: 'c_cpp_properties.json',
cppPropertiesFileNameMac: 'c_cpp_properties_macos.json',
cppPropertiesFileNameLinux: 'c_cpp_properties_linux.json',
cppPropertiesFileNameWin: 'c_cpp_properties_win32.json',
outputPath: './.build',
compileTaskName: 'Arduino Compile',
uploadTaskName: 'Arduino Upload',
environmentTemplateFolderName: 'Arduino Task'
defaultSketchFileName: "device.ino",
arduinoJsonFileName: "arduino.json",
cppPropertiesFileName: "c_cpp_properties.json",
cppPropertiesFileNameMac: "c_cpp_properties_macos.json",
cppPropertiesFileNameLinux: "c_cpp_properties_linux.json",
cppPropertiesFileNameWin: "c_cpp_properties_win32.json",
outputPath: "./.build",
compileTaskName: "Arduino Compile",
uploadTaskName: "Arduino Upload",
environmentTemplateFolderName: "Arduino Task"
};
export abstract class ArduinoDeviceBase implements Device {
protected deviceType: DeviceType;
protected componentType: ComponentType;
@ -44,20 +51,26 @@ export abstract class ArduinoDeviceBase implements Device {
abstract name: string;
abstract id: string;
abstract board: Board|undefined;
abstract board: Board | undefined;
constructor(
context: vscode.ExtensionContext, devicePath: string,
channel: vscode.OutputChannel, telemetryContext: TelemetryContext,
deviceType: DeviceType) {
context: vscode.ExtensionContext,
devicePath: string,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext,
deviceType: DeviceType
) {
this.deviceType = deviceType;
this.componentType = ComponentType.Device;
this.deviceFolder = devicePath;
this.extensionContext = context;
this.vscodeFolderPath =
path.join(this.deviceFolder, FileNames.vscodeSettingsFolderName);
this.boardFolderPath = context.asAbsolutePath(path.join(
FileNames.resourcesFolderName, FileNames.templatesFolderName));
this.vscodeFolderPath = path.join(
this.deviceFolder,
FileNames.vscodeSettingsFolderName
);
this.boardFolderPath = context.asAbsolutePath(
path.join(FileNames.resourcesFolderName, FileNames.templatesFolderName)
);
this.telemetryContext = telemetryContext;
this.channel = channel;
}
@ -73,13 +86,15 @@ export abstract class ArduinoDeviceBase implements Device {
static async isAvailable(): Promise<boolean> {
if (!vscode.extensions.getExtension(DependentExtensions.arduino)) {
const choice = await vscode.window.showInformationMessage(
'Arduino extension is required for the current project. Do you want to install it from marketplace?',
'Yes', 'No');
if (choice === 'Yes') {
"Arduino extension is required for the current project. Do you want to install it from marketplace?",
"Yes",
"No"
);
if (choice === "Yes") {
vscode.commands.executeCommand(
VscodeCommands.VscodeOpen,
vscode.Uri.parse(
'vscode:extension/' + DependentExtensions.arduino));
vscode.Uri.parse("vscode:extension/" + DependentExtensions.arduino)
);
}
return false;
}
@ -102,9 +117,14 @@ export abstract class ArduinoDeviceBase implements Device {
}
await utils.fetchAndExecuteTask(
this.extensionContext, this.channel, this.telemetryContext,
this.deviceFolder, OperationType.Compile, PlatformType.Arduino,
constants.compileTaskName);
this.extensionContext,
this.channel,
this.telemetryContext,
this.deviceFolder,
OperationType.Compile,
PlatformType.Arduino,
constants.compileTaskName
);
return true;
}
@ -114,26 +134,34 @@ export abstract class ArduinoDeviceBase implements Device {
return false;
}
await utils.fetchAndExecuteTask(
this.extensionContext, this.channel, this.telemetryContext,
this.deviceFolder, OperationType.Upload, PlatformType.Arduino,
constants.uploadTaskName);
this.extensionContext,
this.channel,
this.telemetryContext,
this.deviceFolder,
OperationType.Upload,
PlatformType.Arduino,
constants.uploadTaskName
);
return true;
}
abstract async configDeviceSettings(): Promise<boolean>;
async load(): Promise<boolean> {
const deviceFolderPath = this.deviceFolder;
const loadTimeScaffoldType = ScaffoldType.Workspace;
if (!await FileUtility.directoryExists(
loadTimeScaffoldType, deviceFolderPath)) {
throw new Error('Unable to find the device folder inside the project.');
if (
!(await FileUtility.directoryExists(
loadTimeScaffoldType,
deviceFolderPath
))
) {
throw new Error("Unable to find the device folder inside the project.");
}
if (!this.board) {
throw new Error('Unable to find the board in the config file.');
throw new Error("Unable to find the board in the config file.");
}
await this.generateCppPropertiesFile(loadTimeScaffoldType, this.board);
@ -142,12 +170,18 @@ export abstract class ArduinoDeviceBase implements Device {
abstract async create(): Promise<void>;
async createCore(board: Board|undefined, templateFiles: TemplateFileInfo[]):
Promise<void> {
async createCore(
board: Board | undefined,
templateFiles: TemplateFileInfo[]
): Promise<void> {
// Generate template files
const createTimeScaffoldType = ScaffoldType.Local;
if (!await FileUtility.directoryExists(
createTimeScaffoldType, this.deviceFolder)) {
if (
!(await FileUtility.directoryExists(
createTimeScaffoldType,
this.deviceFolder
))
) {
throw new Error(`Internal error: Couldn't find the template folder.`);
}
if (!board) {
@ -156,14 +190,19 @@ export abstract class ArduinoDeviceBase implements Device {
for (const fileInfo of templateFiles) {
await utils.generateTemplateFile(
this.deviceFolder, createTimeScaffoldType, fileInfo);
this.deviceFolder,
createTimeScaffoldType,
fileInfo
);
}
await this.generateCppPropertiesFile(createTimeScaffoldType, board);
// Configurate device environment
await this.configDeviceEnvironment(
this.deviceFolder, createTimeScaffoldType);
this.deviceFolder,
createTimeScaffoldType
);
}
// Backward compatibility: Check configuration
@ -174,9 +213,14 @@ export abstract class ArduinoDeviceBase implements Device {
abstract get version(): string;
private async writeCppPropertiesFile(
boardId: string, type: ScaffoldType, platform: string): Promise<void> {
const cppPropertiesFilePath =
path.join(this.vscodeFolderPath, constants.cppPropertiesFileName);
boardId: string,
type: ScaffoldType,
platform: string
): Promise<void> {
const cppPropertiesFilePath = path.join(
this.vscodeFolderPath,
constants.cppPropertiesFileName
);
if (await FileUtility.fileExists(type, cppPropertiesFilePath)) {
return;
@ -186,7 +230,7 @@ export abstract class ArduinoDeviceBase implements Device {
let changeRootPath = false;
let rootPath: string = await utils.getHomeDir();
if (platform === OSPlatform.WIN32) {
rootPath = path.join(rootPath, 'AppData', 'Local').replace(/\\/g, '\\\\');
rootPath = path.join(rootPath, "AppData", "Local").replace(/\\/g, "\\\\");
cppPropertiesTemplateFileName = constants.cppPropertiesFileNameWin;
changeRootPath = true;
} else if (platform === OSPlatform.LINUX) {
@ -198,12 +242,18 @@ export abstract class ArduinoDeviceBase implements Device {
cppPropertiesTemplateFileName = constants.cppPropertiesFileNameMac;
}
const cppPropertiesTemplateFilePath =
this.extensionContext.asAbsolutePath(path.join(
FileNames.resourcesFolderName, FileNames.templatesFolderName,
boardId, cppPropertiesTemplateFileName));
const propertiesContent =
await FileUtility.readFile(type, cppPropertiesTemplateFilePath);
const cppPropertiesTemplateFilePath = this.extensionContext.asAbsolutePath(
path.join(
FileNames.resourcesFolderName,
FileNames.templatesFolderName,
boardId,
cppPropertiesTemplateFileName
)
);
const propertiesContent = await FileUtility.readFile(
type,
cppPropertiesTemplateFilePath
);
const propertiesContentString = propertiesContent.toString();
const versionPattern = /{VERSION}/g;
@ -217,9 +267,11 @@ export abstract class ArduinoDeviceBase implements Device {
await FileUtility.writeFile(type, cppPropertiesFilePath, content);
}
async generateCppPropertiesFile(type: ScaffoldType, board: Board):
Promise<void> {
if (!await FileUtility.directoryExists(type, this.vscodeFolderPath)) {
async generateCppPropertiesFile(
type: ScaffoldType,
board: Board
): Promise<void> {
if (!(await FileUtility.directoryExists(type, this.vscodeFolderPath))) {
await FileUtility.mkdirRecursively(type, this.vscodeFolderPath);
}
@ -233,9 +285,13 @@ export abstract class ArduinoDeviceBase implements Device {
}
async generateCrc(channel: vscode.OutputChannel): Promise<boolean> {
if (!(vscode.workspace.workspaceFolders &&
vscode.workspace.workspaceFolders.length > 0)) {
const message = 'No workspace opened.';
if (
!(
vscode.workspace.workspaceFolders &&
vscode.workspace.workspaceFolders.length > 0
)
) {
const message = "No workspace opened.";
vscode.window.showWarningMessage(message);
utils.channelShowAndAppendLine(channel, message);
return false;
@ -243,32 +299,35 @@ export abstract class ArduinoDeviceBase implements Device {
const devicePath = ConfigHandler.get<string>(ConfigKey.devicePath);
if (!devicePath) {
const message = 'No device path found in workspace configuration.';
const message = "No device path found in workspace configuration.";
vscode.window.showWarningMessage(message);
utils.channelShowAndAppendLine(channel, message);
return false;
}
const deviceBuildLocation = path.join(
vscode.workspace.workspaceFolders[0].uri.fsPath, '..', devicePath,
'.build');
vscode.workspace.workspaceFolders[0].uri.fsPath,
"..",
devicePath,
".build"
);
if (!deviceBuildLocation) {
const message = 'No device compile output folder found.';
const message = "No device compile output folder found.";
vscode.window.showWarningMessage(message);
utils.channelShowAndAppendLine(channel, message);
return false;
}
const binFiles = fs.listSync(deviceBuildLocation, ['bin']);
const binFiles = fs.listSync(deviceBuildLocation, ["bin"]);
if (!binFiles || !binFiles.length) {
const message =
'No bin file found. Please run the command of Device Compile first.';
"No bin file found. Please run the command of Device Compile first.";
vscode.window.showWarningMessage(message);
utils.channelShowAndAppendLine(channel, message);
return false;
}
let binFilePath = '';
let binFilePath = "";
if (binFiles.length === 1) {
binFilePath = binFiles[0];
@ -283,7 +342,7 @@ export abstract class ArduinoDeviceBase implements Device {
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select bin file',
placeHolder: "Select bin file"
});
if (!choice || !choice.description) {
@ -299,37 +358,43 @@ export abstract class ArduinoDeviceBase implements Device {
const res = OTA.generateCrc(binFilePath);
vscode.window.showInformationMessage('Generate CRC succeeded.');
vscode.window.showInformationMessage("Generate CRC succeeded.");
channel.show();
channel.appendLine('========== CRC Information ==========');
channel.appendLine('');
channel.appendLine('fwPath: ' + binFilePath);
channel.appendLine('fwPackageCheckValue: ' + res.crc);
channel.appendLine('fwSize: ' + res.size);
channel.appendLine('');
channel.appendLine('======================================');
channel.appendLine("========== CRC Information ==========");
channel.appendLine("");
channel.appendLine("fwPath: " + binFilePath);
channel.appendLine("fwPackageCheckValue: " + res.crc);
channel.appendLine("fwSize: " + res.size);
channel.appendLine("");
channel.appendLine("======================================");
return true;
}
async configDeviceEnvironment(
deviceRootPath: string, scaffoldType: ScaffoldType): Promise<void> {
deviceRootPath: string,
scaffoldType: ScaffoldType
): Promise<void> {
if (!deviceRootPath) {
throw new Error(
'Unable to find the project device path, please open the folder and initialize project again.');
"Unable to find the project device path, please open the folder and initialize project again."
);
}
const templateFilesInfo = await utils.getEnvTemplateFilesAndAskOverwrite(
this.extensionContext, this.deviceFolder, scaffoldType,
constants.environmentTemplateFolderName);
this.extensionContext,
this.deviceFolder,
scaffoldType,
constants.environmentTemplateFolderName
);
// Configure project environment with template files
for (const fileInfo of templateFilesInfo) {
await utils.generateTemplateFile(deviceRootPath, scaffoldType, fileInfo);
}
const message = 'Arduino device configuration done.';
const message = "Arduino device configuration done.";
utils.channelShowAndAppendLine(this.channel, message);
}
}
}

Просмотреть файл

@ -1,13 +1,15 @@
import * as path from 'path';
import * as path from "path";
import { AzureComponentsStorage, ScaffoldType } from '../constants';
import { FileUtility } from '../FileUtility';
import { AzureComponentsStorage, ScaffoldType } from "../constants";
import { FileUtility } from "../FileUtility";
import { Component } from './Interfaces/Component';
import { ComponentType } from './Interfaces/Component';
import { Component } from "./Interfaces/Component";
import { ComponentType } from "./Interfaces/Component";
// TODO: need to check what value should be included here
export interface ComponentInfo { values: {[key: string]: string} }
export interface ComponentInfo {
values: { [key: string]: string };
}
export enum DependencyType {
Other,
@ -29,7 +31,9 @@ export interface AzureComponentConfig {
componentInfo?: ComponentInfo;
}
export interface AzureConfigs { componentConfigs: AzureComponentConfig[] }
export interface AzureConfigs {
componentConfigs: AzureComponentConfig[];
}
export interface Dependency {
component: Component;
@ -43,36 +47,49 @@ export class AzureConfigFileHandler {
constructor(projectRoot: string) {
this.projectRootPath = projectRoot;
this.configFilePath = path.join(
this.projectRootPath, AzureComponentsStorage.folderName,
AzureComponentsStorage.fileName);
this.projectRootPath,
AzureComponentsStorage.folderName,
AzureComponentsStorage.fileName
);
}
async createIfNotExists(type: ScaffoldType): Promise<void> {
const azureConfigs: AzureConfigs = { componentConfigs: [] };
const azureConfigFolderPath =
path.join(this.projectRootPath, AzureComponentsStorage.folderName);
if (!await FileUtility.directoryExists(type, azureConfigFolderPath)) {
const azureConfigFolderPath = path.join(
this.projectRootPath,
AzureComponentsStorage.folderName
);
if (!(await FileUtility.directoryExists(type, azureConfigFolderPath))) {
try {
await FileUtility.mkdirRecursively(type, azureConfigFolderPath);
} catch (error) {
throw new Error(`Failed to create azure config folder. Error message: ${
error.message}`);
throw new Error(
`Failed to create azure config folder. Error message: ${error.message}`
);
}
}
const azureConfigFilePath =
path.join(azureConfigFolderPath, AzureComponentsStorage.fileName);
const azureConfigFilePath = path.join(
azureConfigFolderPath,
AzureComponentsStorage.fileName
);
if (!await FileUtility.fileExists(type, azureConfigFilePath)) {
if (!(await FileUtility.fileExists(type, azureConfigFilePath))) {
await FileUtility.writeJsonFile(type, azureConfigFilePath, azureConfigs);
}
}
async getSortedComponents(type: ScaffoldType): Promise<AzureComponentConfig[]> {
async getSortedComponents(
type: ScaffoldType
): Promise<AzureComponentConfig[]> {
try {
const azureConfigContent =
await FileUtility.readFile(type, this.configFilePath, 'utf8');
const azureConfigs =
JSON.parse(azureConfigContent as string) as AzureConfigs;
const azureConfigContent = await FileUtility.readFile(
type,
this.configFilePath,
"utf8"
);
const azureConfigs = JSON.parse(
azureConfigContent as string
) as AzureConfigs;
const components: AzureComponentConfig[] = [];
const componentConfigs = azureConfigs.componentConfigs;
const sortedComponentIds: string[] = [];
@ -100,71 +117,101 @@ export class AzureConfigFileHandler {
sortedComponentIds.push(componentConfig.id);
components.push(componentConfig);
}
} while (lastSortedCount < componentConfigs.length &&
lastSortedCount < components.length);
} while (
lastSortedCount < componentConfigs.length &&
lastSortedCount < components.length
);
return components;
} catch (error) {
throw new Error('Invalid azure components config file.');
throw new Error("Invalid azure components config file.");
}
}
async getComponentIndexById(type: ScaffoldType, id: string): Promise<number> {
try {
const azureConfigContent =
await FileUtility.readFile(type, this.configFilePath, 'utf8');
const azureConfigs =
JSON.parse(azureConfigContent as string) as AzureConfigs;
const componentIndex =
azureConfigs.componentConfigs.findIndex(config => config.id === (id));
const azureConfigContent = await FileUtility.readFile(
type,
this.configFilePath,
"utf8"
);
const azureConfigs = JSON.parse(
azureConfigContent as string
) as AzureConfigs;
const componentIndex = azureConfigs.componentConfigs.findIndex(
config => config.id === id
);
return componentIndex;
} catch (error) {
throw new Error('Invalid azure components config file.');
throw new Error("Invalid azure components config file.");
}
}
async getComponentById(type: ScaffoldType, id: string): Promise<AzureComponentConfig | undefined> {
async getComponentById(
type: ScaffoldType,
id: string
): Promise<AzureComponentConfig | undefined> {
try {
const azureConfigContent =
await FileUtility.readFile(type, this.configFilePath, 'utf8');
const azureConfigs =
JSON.parse(azureConfigContent as string) as AzureConfigs;
const componentConfig =
azureConfigs.componentConfigs.find(config => config.id === (id));
const azureConfigContent = await FileUtility.readFile(
type,
this.configFilePath,
"utf8"
);
const azureConfigs = JSON.parse(
azureConfigContent as string
) as AzureConfigs;
const componentConfig = azureConfigs.componentConfigs.find(
config => config.id === id
);
return componentConfig;
} catch (error) {
throw new Error('Invalid azure components config file.');
throw new Error("Invalid azure components config file.");
}
}
async appendComponent(type: ScaffoldType, component: AzureComponentConfig): Promise<AzureConfigs> {
async appendComponent(
type: ScaffoldType,
component: AzureComponentConfig
): Promise<AzureConfigs> {
try {
const azureConfigContent =
await FileUtility.readFile(type, this.configFilePath, 'utf8');
const azureConfigs =
JSON.parse(azureConfigContent as string) as AzureConfigs;
const azureConfigContent = await FileUtility.readFile(
type,
this.configFilePath,
"utf8"
);
const azureConfigs = JSON.parse(
azureConfigContent as string
) as AzureConfigs;
azureConfigs.componentConfigs.push(component);
await FileUtility.writeJsonFile(type, this.configFilePath, azureConfigs);
return azureConfigs;
} catch (error) {
throw new Error('Invalid azure components config file.');
throw new Error("Invalid azure components config file.");
}
}
async updateComponent(type: ScaffoldType, index: number, componentInfo: ComponentInfo): Promise<AzureConfigs> {
async updateComponent(
type: ScaffoldType,
index: number,
componentInfo: ComponentInfo
): Promise<AzureConfigs> {
try {
const azureConfigContent =
await FileUtility.readFile(type, this.configFilePath, 'utf8');
const azureConfigs =
JSON.parse(azureConfigContent as string) as AzureConfigs;
const azureConfigContent = await FileUtility.readFile(
type,
this.configFilePath,
"utf8"
);
const azureConfigs = JSON.parse(
azureConfigContent as string
) as AzureConfigs;
const component = azureConfigs.componentConfigs[index];
if (!component) {
throw new Error('Invalid index of componet list.');
throw new Error("Invalid index of componet list.");
}
component.componentInfo = componentInfo;
await FileUtility.writeJsonFile(type, this.configFilePath, azureConfigs);
return azureConfigs;
} catch (error) {
throw new Error('Invalid azure components config file.');
throw new Error("Invalid azure components config file.");
}
}
}
}

Просмотреть файл

@ -1,44 +1,58 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
"use strict";
import * as fs from 'fs-plus';
import * as path from 'path';
import * as vscode from 'vscode';
import * as fs from "fs-plus";
import * as path from "path";
import * as vscode from "vscode";
import * as utils from '../utils';
import WebSiteManagementClient = require('azure-arm-website');
import { Component, ComponentType } from './Interfaces/Component';
import { Provisionable } from './Interfaces/Provisionable';
import { Deployable } from './Interfaces/Deployable';
import * as utils from "../utils";
import WebSiteManagementClient = require("azure-arm-website");
import { Component, ComponentType } from "./Interfaces/Component";
import { Provisionable } from "./Interfaces/Provisionable";
import { Deployable } from "./Interfaces/Deployable";
import { ConfigHandler } from '../configHandler';
import { ConfigKey, AzureFunctionsLanguage, AzureComponentsStorage, DependentExtensions, ScaffoldType } from '../constants';
import { ConfigHandler } from "../configHandler";
import {
ConfigKey,
AzureFunctionsLanguage,
AzureComponentsStorage,
DependentExtensions,
ScaffoldType
} from "../constants";
import { ServiceClientCredentials } from 'ms-rest';
import { AzureAccount, AzureResourceFilter } from '../azure-account.api';
import { StringDictionary } from 'azure-arm-website/lib/models';
import { getExtension } from './Apis';
import { ExtensionName } from './Interfaces/Api';
import { Guid } from 'guid-typescript';
import { AzureComponentConfig, AzureConfigs, ComponentInfo, DependencyConfig, Dependency } from './AzureComponentConfig';
import { FileUtility } from '../FileUtility';
import { VscodeCommands, AzureFunctionsCommands } from '../common/Commands';
import { CancelOperationError } from '../CancelOperationError';
import { ServiceClientCredentials } from "ms-rest";
import { AzureAccount, AzureResourceFilter } from "../azure-account.api";
import { StringDictionary } from "azure-arm-website/lib/models";
import { getExtension } from "./Apis";
import { ExtensionName } from "./Interfaces/Api";
import { Guid } from "guid-typescript";
import {
AzureComponentConfig,
AzureConfigs,
ComponentInfo,
DependencyConfig,
Dependency
} from "./AzureComponentConfig";
import { FileUtility } from "../FileUtility";
import { VscodeCommands, AzureFunctionsCommands } from "../common/Commands";
import { CancelOperationError } from "../CancelOperationError";
const impor = require('impor')(__dirname);
const azureUtilityModule =
impor('./AzureUtility') as typeof import('./AzureUtility');
const impor = require("impor")(__dirname);
const azureUtilityModule = impor(
"./AzureUtility"
) as typeof import("./AzureUtility");
export class AzureFunctions implements Component, Provisionable, Deployable {
dependencies: DependencyConfig[] = [];
private componentType: ComponentType;
private channel: vscode.OutputChannel;
private azureFunctionsPath: string;
private azureAccountExtension: AzureAccount|undefined =
getExtension(ExtensionName.AzureAccount);
private functionLanguage: string|null;
private azureAccountExtension: AzureAccount | undefined = getExtension(
ExtensionName.AzureAccount
);
private functionLanguage: string | null;
private functionFolder: string;
private componentId: string;
@ -46,18 +60,19 @@ export class AzureFunctions implements Component, Provisionable, Deployable {
return this.componentId;
}
private async getCredentialFromSubscriptionId(subscriptionId: string):
Promise<ServiceClientCredentials|undefined> {
private async getCredentialFromSubscriptionId(
subscriptionId: string
): Promise<ServiceClientCredentials | undefined> {
if (!this.azureAccountExtension) {
throw new Error('Azure account extension is not found.');
throw new Error("Azure account extension is not found.");
}
if (!subscriptionId) {
throw new Error('Subscription ID is required.');
throw new Error("Subscription ID is required.");
}
const subscriptions: AzureResourceFilter[] =
this.azureAccountExtension.filters;
const subscriptions: AzureResourceFilter[] = this.azureAccountExtension
.filters;
for (let i = 0; i < subscriptions.length; i++) {
const subscription: AzureResourceFilter = subscriptions[i];
if (subscription.subscription.subscriptionId === subscriptionId) {
@ -69,9 +84,12 @@ export class AzureFunctions implements Component, Provisionable, Deployable {
}
constructor(
azureFunctionsPath: string, functionFolder: string,
channel: vscode.OutputChannel, language: string|null = null,
dependencyComponents: Dependency[]|null = null) {
azureFunctionsPath: string,
functionFolder: string,
channel: vscode.OutputChannel,
language: string | null = null,
dependencyComponents: Dependency[] | null = null
) {
this.componentType = ComponentType.AzureFunctions;
this.channel = channel;
this.azureFunctionsPath = azureFunctionsPath;
@ -79,13 +97,16 @@ export class AzureFunctions implements Component, Provisionable, Deployable {
this.functionFolder = functionFolder;
this.componentId = Guid.create().toString();
if (dependencyComponents && dependencyComponents.length > 0) {
dependencyComponents.forEach(
dependency => this.dependencies.push(
{ id: dependency.component.id.toString(), type: dependency.type }));
dependencyComponents.forEach(dependency =>
this.dependencies.push({
id: dependency.component.id.toString(),
type: dependency.type
})
);
}
}
name = 'Azure Functions';
name = "Azure Functions";
getComponentType(): ComponentType {
return this.componentType;
@ -94,13 +115,17 @@ export class AzureFunctions implements Component, Provisionable, Deployable {
static async isAvailable(): Promise<boolean> {
if (!vscode.extensions.getExtension(DependentExtensions.azureFunctions)) {
const choice = await vscode.window.showInformationMessage(
'Azure Functions extension is required for the current project. Do you want to install it from marketplace?',
'Yes', 'No');
if (choice === 'Yes') {
"Azure Functions extension is required for the current project. Do you want to install it from marketplace?",
"Yes",
"No"
);
if (choice === "Yes") {
vscode.commands.executeCommand(
VscodeCommands.VscodeOpen,
vscode.Uri.parse(
'vscode:extension/' + DependentExtensions.azureFunctions));
"vscode:extension/" + DependentExtensions.azureFunctions
)
);
}
return false;
}
@ -118,8 +143,11 @@ export class AzureFunctions implements Component, Provisionable, Deployable {
async load(): Promise<boolean> {
const azureConfigFilePath = path.join(
this.azureFunctionsPath, '..', AzureComponentsStorage.folderName,
AzureComponentsStorage.fileName);
this.azureFunctionsPath,
"..",
AzureComponentsStorage.folderName,
AzureComponentsStorage.fileName
);
if (!fs.existsSync(azureConfigFilePath)) {
return false;
@ -128,19 +156,20 @@ export class AzureFunctions implements Component, Provisionable, Deployable {
let azureConfigs: AzureConfigs;
try {
azureConfigs = JSON.parse(fs.readFileSync(azureConfigFilePath, 'utf8'));
azureConfigs = JSON.parse(fs.readFileSync(azureConfigFilePath, "utf8"));
} catch (error) {
return false;
}
const azureFunctionsConfig = azureConfigs.componentConfigs.find(
config => config.folder === this.functionFolder);
config => config.folder === this.functionFolder
);
if (azureFunctionsConfig) {
this.componentId = azureFunctionsConfig.id;
this.dependencies = azureFunctionsConfig.dependencies;
if (azureFunctionsConfig.componentInfo) {
this.functionLanguage =
azureFunctionsConfig.componentInfo.values.functionLanguage;
azureFunctionsConfig.componentInfo.values.functionLanguage;
}
// Load other information from config file.
@ -153,62 +182,81 @@ export class AzureFunctions implements Component, Provisionable, Deployable {
const azureFunctionsPath = this.azureFunctionsPath;
console.log(azureFunctionsPath);
if (!await FileUtility.directoryExists(scaffoldType, azureFunctionsPath)) {
if (
!(await FileUtility.directoryExists(scaffoldType, azureFunctionsPath))
) {
throw new Error(
'Unable to find the Azure Functions folder inside the project.');
"Unable to find the Azure Functions folder inside the project."
);
}
if (!this.functionLanguage) {
const picks: vscode.QuickPickItem[] = [
{ label: AzureFunctionsLanguage.CSharpScript, description: '' },
{ label: AzureFunctionsLanguage.JavaScript, description: '' },
{ label: AzureFunctionsLanguage.CSharpLibrary, description: '' }
{ label: AzureFunctionsLanguage.CSharpScript, description: "" },
{ label: AzureFunctionsLanguage.JavaScript, description: "" },
{ label: AzureFunctionsLanguage.CSharpLibrary, description: "" }
];
const languageSelection = await vscode.window.showQuickPick(picks, {
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select a language for Azure Functions',
placeHolder: "Select a language for Azure Functions"
});
if (!languageSelection) {
throw new CancelOperationError(
'Unable to get the language for Azure Functions. Creating project for Azure Functions cancelled.');
"Unable to get the language for Azure Functions. Creating project for Azure Functions cancelled."
);
}
this.functionLanguage = languageSelection.label;
}
const templateName =
utils.getScriptTemplateNameFromLanguage(this.functionLanguage);
const templateName = utils.getScriptTemplateNameFromLanguage(
this.functionLanguage
);
if (!templateName) {
throw new CancelOperationError(
'Unable to get the template for Azure Functions.Creating project for Azure Functions cancelled.');
"Unable to get the template for Azure Functions.Creating project for Azure Functions cancelled."
);
}
if (this.functionLanguage === AzureFunctionsLanguage.CSharpLibrary) {
await vscode.commands.executeCommand(
AzureFunctionsCommands.CreateNewProject, azureFunctionsPath,
this.functionLanguage, '~2', false /* openFolder */, templateName,
'IoTHubTrigger1', {
connection: 'eventHubConnectionString',
path: '%eventHubConnectionPath%',
consumerGroup: '$Default',
namespace: 'IoTWorkbench'
});
AzureFunctionsCommands.CreateNewProject,
azureFunctionsPath,
this.functionLanguage,
"~2",
false /* openFolder */,
templateName,
"IoTHubTrigger1",
{
connection: "eventHubConnectionString",
path: "%eventHubConnectionPath%",
consumerGroup: "$Default",
namespace: "IoTWorkbench"
}
);
} else {
await vscode.commands.executeCommand(
AzureFunctionsCommands.CreateNewProject, azureFunctionsPath,
this.functionLanguage, '~1', false /* openFolder */, templateName,
'IoTHubTrigger1', {
connection: 'eventHubConnectionString',
path: '%eventHubConnectionPath%',
consumerGroup: '$Default'
});
AzureFunctionsCommands.CreateNewProject,
azureFunctionsPath,
this.functionLanguage,
"~1",
false /* openFolder */,
templateName,
"IoTHubTrigger1",
{
connection: "eventHubConnectionString",
path: "%eventHubConnectionPath%",
consumerGroup: "$Default"
}
);
}
await this.updateConfigSettings(
scaffoldType, { values: { functionLanguage: this.functionLanguage } });
await this.updateConfigSettings(scaffoldType, {
values: { functionLanguage: this.functionLanguage }
});
}
async provision(): Promise<boolean> {
@ -222,97 +270,125 @@ export class AzureFunctions implements Component, Provisionable, Deployable {
return false;
}
const functionAppId: string|undefined =
await vscode.commands.executeCommand<string>(
AzureFunctionsCommands.CreateFunctionApp, subscriptionId,
resourceGroup);
const functionAppId:
| string
| undefined = await vscode.commands.executeCommand<string>(
AzureFunctionsCommands.CreateFunctionApp,
subscriptionId,
resourceGroup
);
if (functionAppId) {
await ConfigHandler.update(ConfigKey.functionAppId, functionAppId);
const eventHubConnectionString =
ConfigHandler.get<string>(ConfigKey.eventHubConnectionString);
const eventHubConnectionPath =
ConfigHandler.get<string>(ConfigKey.eventHubConnectionPath);
const iotHubConnectionString =
ConfigHandler.get<string>(ConfigKey.iotHubConnectionString);
const eventHubConnectionString = ConfigHandler.get<string>(
ConfigKey.eventHubConnectionString
);
const eventHubConnectionPath = ConfigHandler.get<string>(
ConfigKey.eventHubConnectionPath
);
const iotHubConnectionString = ConfigHandler.get<string>(
ConfigKey.iotHubConnectionString
);
if (!eventHubConnectionString || !eventHubConnectionPath) {
throw new Error('No event hub path or connection string found.');
throw new Error("No event hub path or connection string found.");
}
const credential =
await this.getCredentialFromSubscriptionId(subscriptionId);
const credential = await this.getCredentialFromSubscriptionId(
subscriptionId
);
if (!credential) {
throw new Error('Unable to get credential for the subscription.');
throw new Error("Unable to get credential for the subscription.");
}
const resourceGroupMatches =
functionAppId.match(/\/resourceGroups\/([^/]*)/);
const resourceGroupMatches = functionAppId.match(
/\/resourceGroups\/([^/]*)/
);
if (!resourceGroupMatches || resourceGroupMatches.length < 2) {
throw new Error('Cannot parse resource group from function app ID.');
throw new Error("Cannot parse resource group from function app ID.");
}
const resourceGroup = resourceGroupMatches[1];
const siteNameMatches = functionAppId.match(/\/sites\/([^/]*)/);
if (!siteNameMatches || siteNameMatches.length < 2) {
throw new Error('Cannot parse function app name from function app ID.');
throw new Error("Cannot parse function app name from function app ID.");
}
const siteName = siteNameMatches[1];
const client = new WebSiteManagementClient(credential, subscriptionId);
console.log(resourceGroup, siteName);
const appSettings: StringDictionary =
await client.webApps.listApplicationSettings(resourceGroup, siteName);
const appSettings: StringDictionary = await client.webApps.listApplicationSettings(
resourceGroup,
siteName
);
console.log(appSettings);
appSettings.properties = appSettings.properties || {};
// for c# library, use the default setting of ~2.
if (this.functionLanguage !==
AzureFunctionsLanguage.CSharpLibrary as string) {
appSettings.properties['FUNCTIONS_EXTENSION_VERSION'] = '~1';
if (
this.functionLanguage !==
(AzureFunctionsLanguage.CSharpLibrary as string)
) {
appSettings.properties["FUNCTIONS_EXTENSION_VERSION"] = "~1";
} else {
appSettings.properties['FUNCTIONS_EXTENSION_VERSION'] = '~2';
appSettings.properties["FUNCTIONS_EXTENSION_VERSION"] = "~2";
}
appSettings.properties['eventHubConnectionString'] =
eventHubConnectionString || '';
appSettings.properties['eventHubConnectionPath'] =
eventHubConnectionPath || '';
appSettings.properties['iotHubConnectionString'] =
iotHubConnectionString || '';
appSettings.properties["eventHubConnectionString"] =
eventHubConnectionString || "";
appSettings.properties["eventHubConnectionPath"] =
eventHubConnectionPath || "";
appSettings.properties["iotHubConnectionString"] =
iotHubConnectionString || "";
// see detail:
// https://github.com/Microsoft/vscode-iot-workbench/issues/436
appSettings.properties['WEBSITE_RUN_FROM_PACKAGE'] = '0';
appSettings.properties["WEBSITE_RUN_FROM_PACKAGE"] = "0";
await client.webApps.updateApplicationSettings(
resourceGroup, siteName, appSettings);
resourceGroup,
siteName,
appSettings
);
return true;
} else {
throw new Error(
'Creating Azure Functions application failed. Please check the error log in output window.');
"Creating Azure Functions application failed. Please check the error log in output window."
);
}
}
async deploy(): Promise<boolean> {
let deployPending: NodeJS.Timer|null = null;
let deployPending: NodeJS.Timer | null = null;
if (this.channel) {
utils.channelShowAndAppendLine(
this.channel, 'Deploying Azure Functions App...');
this.channel,
"Deploying Azure Functions App..."
);
deployPending = setInterval(() => {
this.channel.append('.');
this.channel.append(".");
}, 1000);
}
try {
const azureFunctionsPath = this.azureFunctionsPath;
const functionAppId = ConfigHandler.get(ConfigKey.functionAppId);
if (this.functionLanguage !==
AzureFunctionsLanguage.CSharpLibrary as string) {
if (
this.functionLanguage !==
(AzureFunctionsLanguage.CSharpLibrary as string)
) {
await vscode.commands.executeCommand(
AzureFunctionsCommands.Deploy, azureFunctionsPath, functionAppId);
AzureFunctionsCommands.Deploy,
azureFunctionsPath,
functionAppId
);
} else {
const subPath =
path.join(azureFunctionsPath, 'bin/Release/netcoreapp2.1/publish');
const subPath = path.join(
azureFunctionsPath,
"bin/Release/netcoreapp2.1/publish"
);
await vscode.commands.executeCommand(
AzureFunctionsCommands.Deploy, subPath, functionAppId);
AzureFunctionsCommands.Deploy,
subPath,
functionAppId
);
}
console.log(azureFunctionsPath, functionAppId);
@ -320,37 +396,46 @@ export class AzureFunctions implements Component, Provisionable, Deployable {
} finally {
if (this.channel && deployPending) {
clearInterval(deployPending);
utils.channelShowAndAppendLine(this.channel, '.');
utils.channelShowAndAppendLine(this.channel, ".");
}
}
}
async updateConfigSettings(type: ScaffoldType, componentInfo?: ComponentInfo):
Promise<void> {
async updateConfigSettings(
type: ScaffoldType,
componentInfo?: ComponentInfo
): Promise<void> {
const azureConfigFilePath = path.join(
this.azureFunctionsPath, '..', AzureComponentsStorage.folderName,
AzureComponentsStorage.fileName);
this.azureFunctionsPath,
"..",
AzureComponentsStorage.folderName,
AzureComponentsStorage.fileName
);
let azureConfigs: AzureConfigs = { componentConfigs: [] };
try {
const azureConfigContent =
await FileUtility.readFile(type, azureConfigFilePath, 'utf8');
const azureConfigContent = await FileUtility.readFile(
type,
azureConfigFilePath,
"utf8"
);
azureConfigs = JSON.parse(azureConfigContent as string) as AzureConfigs;
} catch (error) {
const e = new Error('Invalid azure components config file.');
const e = new Error("Invalid azure components config file.");
throw e;
}
const azureFunctionsConfig =
azureConfigs.componentConfigs.find(config => config.id === (this.id));
const azureFunctionsConfig = azureConfigs.componentConfigs.find(
config => config.id === this.id
);
if (azureFunctionsConfig) {
// TODO: update the existing setting for the provision result
} else {
const newAzureFunctionsConfig: AzureComponentConfig = {
id: this.id,
folder: this.functionFolder,
name: '',
name: "",
dependencies: this.dependencies,
type: this.componentType,
componentInfo
@ -360,4 +445,4 @@ export class AzureFunctions implements Component, Provisionable, Deployable {
utils.channelPrintJsonObject(this.channel, azureConfigs);
}
}
}
}

Просмотреть файл

@ -1,30 +1,39 @@
import { ResourceManagementClient, ResourceModels, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource';
import * as fs from 'fs-plus';
import { HttpMethods, WebResource } from 'ms-rest';
import * as path from 'path';
import * as vscode from 'vscode';
import {
ResourceManagementClient,
ResourceModels,
SubscriptionClient,
SubscriptionModels
} from "azure-arm-resource";
import * as fs from "fs-plus";
import { HttpMethods, WebResource } from "ms-rest";
import * as path from "path";
import * as vscode from "vscode";
import { channelPrintJsonObject, channelShowAndAppendLine } from '../utils';
import { channelPrintJsonObject, channelShowAndAppendLine } from "../utils";
import request = require('request-promise');
import rq = require('request');
import request = require("request-promise");
import rq = require("request");
import { AzureAccount, AzureResourceFilter, AzureSession } from '../azure-account.api';
import { ConfigHandler } from '../configHandler';
import {
AzureAccount,
AzureResourceFilter,
AzureSession
} from "../azure-account.api";
import { ConfigHandler } from "../configHandler";
import { getExtension } from './Apis';
import { ExtensionName } from './Interfaces/Api';
import { TelemetryWorker } from '../telemetry';
import { EventNames } from '../constants';
import { getExtension } from "./Apis";
import { ExtensionName } from "./Interfaces/Api";
import { TelemetryWorker } from "../telemetry";
import { EventNames } from "../constants";
export interface ARMParameters {
[key: string]: {value: string|number|boolean|null};
[key: string]: { value: string | number | boolean | null };
}
export interface ARMParameterTemplateValue {
type: string;
defaultValue?: string|number|boolean|{}|Array<{}>|null;
allowedValues?: Array<string|number|boolean|null>;
defaultValue?: string | number | boolean | {} | Array<{}> | null;
allowedValues?: Array<string | number | boolean | null>;
minValue?: number;
maxValue?: number;
minLength?: number;
@ -35,21 +44,24 @@ export interface ARMParameterTemplate {
[key: string]: ARMParameterTemplateValue;
}
export interface ARMTemplate {
export interface ARMTemplate {
parameters: ARMParameterTemplate;
}
export class AzureUtility {
private static _context: vscode.ExtensionContext;
private static _channel: vscode.OutputChannel|undefined;
private static _subscriptionId: string|undefined;
private static _resourceGroup: string|undefined;
private static _azureAccountExtension: AzureAccount|undefined =
getExtension(ExtensionName.AzureAccount);
private static _channel: vscode.OutputChannel | undefined;
private static _subscriptionId: string | undefined;
private static _resourceGroup: string | undefined;
private static _azureAccountExtension:
| AzureAccount
| undefined = getExtension(ExtensionName.AzureAccount);
static init(
context: vscode.ExtensionContext, channel?: vscode.OutputChannel,
subscriptionId?: string): void {
context: vscode.ExtensionContext,
channel?: vscode.OutputChannel,
subscriptionId?: string
): void {
AzureUtility._context = context;
AzureUtility._channel = channel;
AzureUtility._subscriptionId = subscriptionId;
@ -58,7 +70,7 @@ export class AzureUtility {
private static async _getSubscriptionList(): Promise<vscode.QuickPickItem[]> {
const subscriptionList: vscode.QuickPickItem[] = [];
if (!AzureUtility._azureAccountExtension) {
throw new Error('Azure account extension is not found.');
throw new Error("Azure account extension is not found.");
}
const subscriptions = AzureUtility._azureAccountExtension.filters;
@ -71,26 +83,28 @@ export class AzureUtility {
if (subscriptionList.length === 0) {
subscriptionList.push({
label: 'No subscription found',
description: '',
label: "No subscription found",
description: "",
detail:
'Click Azure account at bottom left corner and choose Select All'
"Click Azure account at bottom left corner and choose Select All"
} as vscode.QuickPickItem);
}
return subscriptionList;
}
private static _getSessionBySubscriptionId(subscriptionId: string):
AzureSession|undefined {
private static _getSessionBySubscriptionId(
subscriptionId: string
): AzureSession | undefined {
if (!AzureUtility._azureAccountExtension) {
throw new Error('Azure account extension is not found.');
throw new Error("Azure account extension is not found.");
}
const subscriptions: AzureResourceFilter[] =
AzureUtility._azureAccountExtension.filters;
AzureUtility._azureAccountExtension.filters;
const subscription = subscriptions.find(
sub => sub.subscription.subscriptionId === subscriptionId);
sub => sub.subscription.subscriptionId === subscriptionId
);
if (subscription) {
return subscription.session;
}
@ -98,7 +112,7 @@ export class AzureUtility {
return undefined;
}
private static async _getSession(): Promise<AzureSession|undefined> {
private static async _getSession(): Promise<AzureSession | undefined> {
AzureUtility._subscriptionId = await AzureUtility._getSubscription();
if (!AzureUtility._subscriptionId) {
@ -106,10 +120,13 @@ export class AzureUtility {
}
return AzureUtility._getSessionBySubscriptionId(
AzureUtility._subscriptionId);
AzureUtility._subscriptionId
);
}
private static async _getResourceClient(): Promise<ResourceManagementClient | undefined> {
private static async _getResourceClient(): Promise<
ResourceManagementClient | undefined
> {
AzureUtility._subscriptionId = await AzureUtility._getSubscription();
if (!AzureUtility._subscriptionId) {
@ -120,37 +137,49 @@ export class AzureUtility {
if (session) {
const credential = session.credentials;
const client = new ResourceManagementClient(
credential, AzureUtility._subscriptionId,
session.environment.resourceManagerEndpointUrl);
credential,
AzureUtility._subscriptionId,
session.environment.resourceManagerEndpointUrl
);
return client;
}
return undefined;
}
private static _getSubscriptionClientBySubscriptionId(substriptionId: string): ResourceManagementClient | undefined {
private static _getSubscriptionClientBySubscriptionId(
substriptionId: string
): ResourceManagementClient | undefined {
const session = AzureUtility._getSessionBySubscriptionId(substriptionId);
if (session) {
const credential = session.credentials;
const client = new ResourceManagementClient(
credential, substriptionId,
session.environment.resourceManagerEndpointUrl);
credential,
substriptionId,
session.environment.resourceManagerEndpointUrl
);
return client;
}
return undefined;
}
private static async _getSubscriptionClient(): Promise<SubscriptionClient | undefined> {
private static async _getSubscriptionClient(): Promise<
SubscriptionClient | undefined
> {
const session = await AzureUtility._getSession();
if (session) {
const credential = session.credentials;
const client = new SubscriptionClient(
credential, session.environment.resourceManagerEndpointUrl);
credential,
session.environment.resourceManagerEndpointUrl
);
return client;
}
return undefined;
}
private static async _getLocations(): Promise<SubscriptionModels.LocationListResult | undefined> {
private static async _getLocations(): Promise<
SubscriptionModels.LocationListResult | undefined
> {
AzureUtility._subscriptionId = await AzureUtility._getSubscription();
if (!AzureUtility._subscriptionId) {
@ -162,8 +191,9 @@ export class AzureUtility {
return undefined;
}
const locations =
await client.subscriptions.listLocations(AzureUtility._subscriptionId);
const locations = await client.subscriptions.listLocations(
AzureUtility._subscriptionId
);
return locations;
}
@ -174,19 +204,19 @@ export class AzureUtility {
}
const resourceGroupName = await vscode.window.showInputBox({
prompt: 'Input resouce group name',
prompt: "Input resouce group name",
ignoreFocusOut: true,
validateInput: async (name: string) => {
if (!/^[a-z0-9_\-.]*[a-z0-9_-]+$/.test(name)) {
return 'Resource group names only allow alphanumeric characters, periods, underscores, hyphens and parenthesis and cannot end in a period.';
return "Resource group names only allow alphanumeric characters, periods, underscores, hyphens and parenthesis and cannot end in a period.";
}
const exist = await client.resourceGroups.checkExistence(name);
if (exist) {
return 'Azure name is unavailable';
return "Azure name is unavailable";
}
return '';
return "";
}
});
@ -208,77 +238,95 @@ export class AzureUtility {
const resourceGroupLocation = await vscode.window.showQuickPick(
locationList,
{ placeHolder: 'Select Resource Group Location', ignoreFocusOut: true });
{ placeHolder: "Select Resource Group Location", ignoreFocusOut: true }
);
if (!resourceGroupLocation || !resourceGroupLocation.description) {
return undefined;
}
const resourceGroup = await client.resourceGroups.createOrUpdate(
resourceGroupName, { location: resourceGroupLocation.description });
resourceGroupName,
{ location: resourceGroupLocation.description }
);
return resourceGroup.name;
}
private static _commonParameterCheck(_value: string, parameter: ARMParameterTemplateValue): string {
let value: string|number|boolean|null = null;
private static _commonParameterCheck(
_value: string,
parameter: ARMParameterTemplateValue
): string {
let value: string | number | boolean | null = null;
switch (parameter.type.toLocaleLowerCase()) {
case 'string':
value = _value;
break;
case 'int':
value = Number(_value);
break;
case 'bool':
value = _value.toLocaleLowerCase() === 'true';
break;
default:
break;
case "string":
value = _value;
break;
case "int":
value = Number(_value);
break;
case "bool":
value = _value.toLocaleLowerCase() === "true";
break;
default:
break;
}
if (value === null) {
return '';
return "";
}
if (typeof value === 'string' && parameter.minLength !== undefined &&
parameter.minLength > value.length) {
return `The value does't meet requirement: minLength ${
parameter.minLength}.`;
if (
typeof value === "string" &&
parameter.minLength !== undefined &&
parameter.minLength > value.length
) {
return `The value does't meet requirement: minLength ${parameter.minLength}.`;
}
if (typeof value === 'string' && parameter.maxLength !== undefined &&
parameter.maxLength < value.length) {
return `The value does't meet requirement: maxLength ${
parameter.maxLength}.`;
if (
typeof value === "string" &&
parameter.maxLength !== undefined &&
parameter.maxLength < value.length
) {
return `The value does't meet requirement: maxLength ${parameter.maxLength}.`;
}
if (typeof value === 'number' && parameter.minValue !== undefined &&
parameter.minValue > value) {
return `The value does't meet requirement: minValue ${
parameter.minValue}.`;
if (
typeof value === "number" &&
parameter.minValue !== undefined &&
parameter.minValue > value
) {
return `The value does't meet requirement: minValue ${parameter.minValue}.`;
}
if (typeof value === 'number' && parameter.maxValue !== undefined &&
parameter.maxValue < value) {
return `The value does't meet requirement: maxValue ${
parameter.maxValue}.`;
if (
typeof value === "number" &&
parameter.maxValue !== undefined &&
parameter.maxValue < value
) {
return `The value does't meet requirement: maxValue ${parameter.maxValue}.`;
}
if (typeof value === 'number' && isNaN(value)) {
if (typeof value === "number" && isNaN(value)) {
return `The value is not a valid number.`;
}
return '';
return "";
}
private static _getKeyDisplayName(key: string): string {
key = key.replace(/^\$*/, '');
const keyDisplayName = key.replace(/([A-Z][^A-Z])/g, ' $1')
.replace(/([a-z])([A-Z])/g, '$1 $2');
key = key.replace(/^\$*/, "");
const keyDisplayName = key
.replace(/([A-Z][^A-Z])/g, " $1")
.replace(/([a-z])([A-Z])/g, "$1 $2");
return keyDisplayName.substr(0, 1).toUpperCase() + keyDisplayName.substr(1);
}
private static async _getARMParameters(parameterTemplate: ARMParameterTemplate, parameters?: ARMParameters): Promise<ARMParameters|undefined> {
parameters = parameters || {} as ARMParameters;
private static async _getARMParameters(
parameterTemplate: ARMParameterTemplate,
parameters?: ARMParameters
): Promise<ARMParameters | undefined> {
parameters = parameters || ({} as ARMParameters);
for (const key of Object.keys(parameterTemplate)) {
if (Object.prototype.hasOwnProperty.call(parameters, key)) {
continue;
@ -286,14 +334,14 @@ export class AzureUtility {
const keyDisplayName = AzureUtility._getKeyDisplayName(key);
const parameter = parameterTemplate[key];
let value: string|number|boolean|null = null;
let inputValue = '';
let value: string | number | boolean | null = null;
let inputValue = "";
if (parameter.allowedValues) {
const values: vscode.QuickPickItem[] = [];
for (const value of parameter.allowedValues) {
if (value !== null) {
values.push({ label: value.toString(), description: '' });
values.push({ label: value.toString(), description: "" });
}
}
@ -306,87 +354,99 @@ export class AzureUtility {
}
inputValue = _value.label;
} else if (key.substr(0, 2) === '$$') {
} else if (key.substr(0, 2) === "$$") {
// Read value from file
if (!(vscode.workspace.workspaceFolders &&
vscode.workspace.workspaceFolders.length > 0)) {
inputValue = '';
if (
!(
vscode.workspace.workspaceFolders &&
vscode.workspace.workspaceFolders.length > 0
)
) {
inputValue = "";
} else {
const _key = key.substr(2);
const filePath = path.join(
vscode.workspace.workspaceFolders[0].uri.fsPath, '..', _key);
vscode.workspace.workspaceFolders[0].uri.fsPath,
"..",
_key
);
AzureUtility._context.asAbsolutePath(_key);
if (fs.existsSync(filePath)) {
inputValue = fs.readFileSync(filePath, 'utf8');
inputValue = fs.readFileSync(filePath, "utf8");
} else {
inputValue = '';
inputValue = "";
}
}
} else if (key.substr(0, 1) === '$') {
} else if (key.substr(0, 1) === "$") {
// Read value from workspace config
const _key = key.substr(1);
const iothubConnectionString =
ConfigHandler.get<string>('iothubConnectionString');
const iothubConnectionString = ConfigHandler.get<string>(
"iothubConnectionString"
);
switch (_key) {
case 'iotHubName':
if (!iothubConnectionString) {
inputValue = '';
} else {
const iotHubNameMatches =
iothubConnectionString.match(/HostName=(.*?)\./);
if (!iotHubNameMatches) {
inputValue = '';
case "iotHubName":
if (!iothubConnectionString) {
inputValue = "";
} else {
inputValue = iotHubNameMatches[1];
const iotHubNameMatches = iothubConnectionString.match(
/HostName=(.*?)\./
);
if (!iotHubNameMatches) {
inputValue = "";
} else {
inputValue = iotHubNameMatches[1];
}
}
break;
case "iotHubKeyName":
if (!iothubConnectionString) {
inputValue = "";
} else {
const iotHubKeyNameMatches = iothubConnectionString.match(
/SharedAccessKeyName=(.*?)(;|$)/
);
if (!iotHubKeyNameMatches) {
inputValue = "";
} else {
inputValue = iotHubKeyNameMatches[1];
}
}
break;
case "iotHubKey":
if (!iothubConnectionString) {
inputValue = "";
} else {
const iotHubKeyMatches = iothubConnectionString.match(
/SharedAccessKey=(.*?)(;|$)/
);
if (!iotHubKeyMatches) {
inputValue = "";
} else {
inputValue = iotHubKeyMatches[1];
}
}
break;
case "subscription":
inputValue = AzureUtility._subscriptionId || "";
break;
default: {
const _value = ConfigHandler.get<string>(_key);
if (!_value) {
inputValue = "";
} else {
inputValue = _value;
}
}
break;
case 'iotHubKeyName':
if (!iothubConnectionString) {
inputValue = '';
} else {
const iotHubKeyNameMatches = iothubConnectionString.match(
/SharedAccessKeyName=(.*?)(;|$)/);
if (!iotHubKeyNameMatches) {
inputValue = '';
} else {
inputValue = iotHubKeyNameMatches[1];
}
}
break;
case 'iotHubKey':
if (!iothubConnectionString) {
inputValue = '';
} else {
const iotHubKeyMatches =
iothubConnectionString.match(/SharedAccessKey=(.*?)(;|$)/);
if (!iotHubKeyMatches) {
inputValue = '';
} else {
inputValue = iotHubKeyMatches[1];
}
}
break;
case 'subscription':
inputValue = AzureUtility._subscriptionId || '';
break;
default:{
const _value = ConfigHandler.get<string>(_key);
if (!_value) {
inputValue = '';
} else {
inputValue = _value;
}
}
}
} else {
const _value = await vscode.window.showInputBox({
prompt: `Input value for ${keyDisplayName}`,
ignoreFocusOut: true,
value: parameter.defaultValue ? parameter.defaultValue.toString() :
'',
value: parameter.defaultValue
? parameter.defaultValue.toString()
: "",
validateInput: async (value: string) => {
return AzureUtility._commonParameterCheck(value, parameter);
}
@ -400,17 +460,17 @@ export class AzureUtility {
}
switch (parameter.type.toLocaleLowerCase()) {
case 'string':
value = inputValue;
break;
case 'int':
value = Number(inputValue);
break;
case 'bool':
value = inputValue.toLocaleLowerCase() === 'true';
break;
default:
break;
case "string":
value = inputValue;
break;
case "int":
value = Number(inputValue);
break;
case "bool":
value = inputValue.toLocaleLowerCase() === "true";
break;
default:
break;
}
parameters[key] = { value };
@ -426,7 +486,8 @@ export class AzureUtility {
const subscription = await vscode.window.showQuickPick(
AzureUtility._getSubscriptionList(),
{ placeHolder: 'Select Subscription', ignoreFocusOut: true });
{ placeHolder: "Select Subscription", ignoreFocusOut: true }
);
if (!subscription || !subscription.description) {
return undefined;
}
@ -437,22 +498,27 @@ export class AzureUtility {
try {
telemetryWorker.sendEvent(
EventNames.selectSubscription, telemetryContext);
EventNames.selectSubscription,
telemetryContext
);
} catch {
// If sending telemetry failed, skip the error to avoid blocking user.
}
return subscription.description;
}
private static async _getResourceGroupItems(): Promise<vscode.QuickPickItem[]> {
private static async _getResourceGroupItems(): Promise<
vscode.QuickPickItem[]
> {
const client = await AzureUtility._getResourceClient();
if (!client) {
return [];
}
const resourceGrouplist: vscode.QuickPickItem[] =
[{ label: '$(plus) Create Resource Group', description: '', detail: '' }];
const resourceGrouplist: vscode.QuickPickItem[] = [
{ label: "$(plus) Create Resource Group", description: "", detail: "" }
];
const resourceGroups = await client.resourceGroups.list();
@ -460,7 +526,7 @@ export class AzureUtility {
resourceGrouplist.push({
label: resourceGroup.name as string,
description: resourceGroup.location,
detail: ''
detail: ""
});
}
@ -477,14 +543,15 @@ export class AzureUtility {
const choice = await vscode.window.showQuickPick(
AzureUtility._getResourceGroupItems(),
{ placeHolder: 'Select Resource Group', ignoreFocusOut: true });
{ placeHolder: "Select Resource Group", ignoreFocusOut: true }
);
if (!choice) {
AzureUtility._resourceGroup = undefined;
return undefined;
}
if (choice.description === '') {
if (choice.description === "") {
const resourceGroup = await AzureUtility._createResouceGroup();
AzureUtility._resourceGroup = resourceGroup;
return resourceGroup;
@ -494,7 +561,10 @@ export class AzureUtility {
}
}
static async deployARMTemplate(template: ARMTemplate, parameters?: ARMParameters): Promise<ResourceModels.DeploymentExtended | undefined> {
static async deployARMTemplate(
template: ARMTemplate,
parameters?: ARMParameters
): Promise<ResourceModels.DeploymentExtended | undefined> {
const client = await AzureUtility._getResourceClient();
if (!client) {
return undefined;
@ -504,61 +574,67 @@ export class AzureUtility {
return undefined;
}
parameters =
await AzureUtility._getARMParameters(template.parameters, parameters);
parameters = await AzureUtility._getARMParameters(
template.parameters,
parameters
);
if (!parameters) {
return undefined;
}
let deployPending: NodeJS.Timer|null = null;
let deployPending: NodeJS.Timer | null = null;
if (AzureUtility._channel) {
deployPending = setInterval(() => {
if (AzureUtility._channel) {
channelShowAndAppendLine(AzureUtility._channel, '.');
channelShowAndAppendLine(AzureUtility._channel, ".");
}
}, 1000);
}
const mode = 'Incremental';
const deploymentParameters:
ResourceModels.Deployment = { properties: { parameters, template, mode } };
const mode = "Incremental";
const deploymentParameters: ResourceModels.Deployment = {
properties: { parameters, template, mode }
};
try {
const deployment = await client.deployments.createOrUpdate(
AzureUtility._resourceGroup,
`IoTWorkbecnhDeploy${new Date().getTime()}`, deploymentParameters);
`IoTWorkbecnhDeploy${new Date().getTime()}`,
deploymentParameters
);
if (AzureUtility._channel && deployPending) {
clearInterval(deployPending);
channelShowAndAppendLine(AzureUtility._channel, '.');
channelShowAndAppendLine(AzureUtility._channel, ".");
channelPrintJsonObject(AzureUtility._channel, deployment);
}
return deployment;
} catch (error) {
if (AzureUtility._channel && deployPending) {
clearInterval(deployPending);
channelShowAndAppendLine(AzureUtility._channel, '.');
channelShowAndAppendLine(AzureUtility._channel, ".");
channelShowAndAppendLine(AzureUtility._channel, error);
}
return undefined;
}
}
static get subscriptionId(): string|undefined {
static get subscriptionId(): string | undefined {
return AzureUtility._subscriptionId;
}
static get resourceGroup(): string|undefined {
static get resourceGroup(): string | undefined {
return AzureUtility._resourceGroup;
}
static getClient(): ResourceManagementClient |undefined {
static getClient(): ResourceManagementClient | undefined {
if (!AzureUtility._subscriptionId) {
return undefined;
}
const client = AzureUtility._getSubscriptionClientBySubscriptionId(
AzureUtility._subscriptionId);
AzureUtility._subscriptionId
);
if (!client) {
return undefined;
}
@ -566,8 +642,12 @@ export class AzureUtility {
return client;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static async request(method: HttpMethods, resource: string, body: any = null): Promise<unknown> {
static async request(
method: HttpMethods,
resource: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
body: any = null
): Promise<unknown> {
const session = await AzureUtility._getSession();
if (!session) {
return undefined;
@ -576,18 +656,18 @@ export class AzureUtility {
const credential = session.credentials;
const httpRequest = new WebResource();
httpRequest.method = method;
httpRequest.url = 'https://management.azure.com' + resource;
httpRequest.url = "https://management.azure.com" + resource;
httpRequest.body = body;
if (method === 'GET' || method === 'DELETE') {
if (method === "GET" || method === "DELETE") {
delete httpRequest.body;
}
const httpRequestOption: (rq.UrlOptions&request.RequestPromiseOptions) =
httpRequest;
const httpRequestOption: rq.UrlOptions &
request.RequestPromiseOptions = httpRequest;
httpRequestOption.simple = false;
httpRequestOption.json = true;
return new Promise((resolve) => {
return new Promise(resolve => {
credential.signRequest(httpRequest, async err => {
if (!err) {
const res = await request(httpRequestOption);
@ -599,12 +679,15 @@ export class AzureUtility {
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static async postRequest(resource: string, body: any = null): Promise<unknown> {
return AzureUtility.request('POST', resource, body);
static async postRequest(
resource: string,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
body: any = null
): Promise<unknown> {
return AzureUtility.request("POST", resource, body);
}
static async getRequest(resource: string): Promise<unknown> {
return AzureUtility.request('GET', resource);
return AzureUtility.request("GET", resource);
}
}

Просмотреть файл

@ -1,25 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { Guid } from 'guid-typescript';
import * as path from 'path';
import * as vscode from 'vscode';
import { Guid } from "guid-typescript";
import * as path from "path";
import * as vscode from "vscode";
import { CancelOperationError } from '../CancelOperationError';
import { FileNames, OperationType, PlatformType, ScaffoldType, TemplateTag } from '../constants';
import { DigitalTwinConstants } from '../DigitalTwin/DigitalTwinConstants';
import { FileUtility } from '../FileUtility';
import { TelemetryContext } from '../telemetry';
import * as utils from '../utils';
import { CancelOperationError } from "../CancelOperationError";
import {
FileNames,
OperationType,
PlatformType,
ScaffoldType,
TemplateTag
} from "../constants";
import { DigitalTwinConstants } from "../DigitalTwin/DigitalTwinConstants";
import { FileUtility } from "../FileUtility";
import { TelemetryContext } from "../telemetry";
import * as utils from "../utils";
import { ComponentType } from './Interfaces/Component';
import { Device, DeviceType } from './Interfaces/Device';
import { ProjectTemplate, TemplateFileInfo, TemplatesType } from './Interfaces/ProjectTemplate';
import { RemoteExtension } from './RemoteExtension';
import { ComponentType } from "./Interfaces/Component";
import { Device, DeviceType } from "./Interfaces/Device";
import {
ProjectTemplate,
TemplateFileInfo,
TemplatesType
} from "./Interfaces/ProjectTemplate";
import { RemoteExtension } from "./RemoteExtension";
const constants = {
configFile: 'config.json',
compileTaskName: 'default compile script'
configFile: "config.json",
compileTaskName: "default compile script"
};
export abstract class ContainerDeviceBase implements Device {
@ -36,13 +46,16 @@ export abstract class ContainerDeviceBase implements Device {
protected outputPath: string;
name = 'container base';
name = "container base";
constructor(
context: vscode.ExtensionContext, projectPath: string,
channel: vscode.OutputChannel, telemetryContext: TelemetryContext,
context: vscode.ExtensionContext,
projectPath: string,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext,
deviceType: DeviceType,
protected templateFilesInfo: TemplateFileInfo[] = []) {
protected templateFilesInfo: TemplateFileInfo[] = []
) {
this.deviceType = deviceType;
this.componentType = ComponentType.Device;
this.channel = channel;
@ -68,8 +81,10 @@ export abstract class ContainerDeviceBase implements Device {
async load(): Promise<boolean> {
// ScaffoldType is Workspace when loading a project
const scaffoldType = ScaffoldType.Workspace;
if (!await FileUtility.directoryExists(scaffoldType, this.projectFolder)) {
throw new Error('Unable to find the project folder.');
if (
!(await FileUtility.directoryExists(scaffoldType, this.projectFolder))
) {
throw new Error("Unable to find the project folder.");
}
return true;
@ -78,23 +93,34 @@ export abstract class ContainerDeviceBase implements Device {
async create(): Promise<void> {
// ScaffoldType is local when creating a project
const createTimeScaffoldType = ScaffoldType.Local;
if (!await FileUtility.directoryExists(
createTimeScaffoldType, this.projectFolder)) {
throw new Error('Unable to find the project folder.');
if (
!(await FileUtility.directoryExists(
createTimeScaffoldType,
this.projectFolder
))
) {
throw new Error("Unable to find the project folder.");
}
await this.generateTemplateFiles(
createTimeScaffoldType, this.projectFolder, this.templateFilesInfo);
createTimeScaffoldType,
this.projectFolder,
this.templateFilesInfo
);
await this.configDeviceEnvironment(
this.projectFolder, createTimeScaffoldType);
this.projectFolder,
createTimeScaffoldType
);
}
async generateTemplateFiles(
type: ScaffoldType, projectPath: string,
templateFilesInfo: TemplateFileInfo[]): Promise<boolean> {
type: ScaffoldType,
projectPath: string,
templateFilesInfo: TemplateFileInfo[]
): Promise<boolean> {
if (!templateFilesInfo) {
throw new Error('No template file provided.');
throw new Error("No template file provided.");
}
if (!projectPath) {
@ -108,8 +134,10 @@ export abstract class ContainerDeviceBase implements Device {
const pattern = /{project_name}/g;
const projectName = path.basename(projectPath);
if (fileInfo.fileContent) {
fileInfo.fileContent =
fileInfo.fileContent.replace(pattern, projectName);
fileInfo.fileContent = fileInfo.fileContent.replace(
pattern,
projectName
);
}
}
await utils.generateTemplateFile(this.projectFolder, type, fileInfo);
@ -122,14 +150,21 @@ export abstract class ContainerDeviceBase implements Device {
const isRemote = RemoteExtension.isRemote(this.extensionContext);
if (!isRemote) {
await utils.askAndOpenInRemote(
OperationType.Compile, this.telemetryContext);
OperationType.Compile,
this.telemetryContext
);
return false;
}
await utils.fetchAndExecuteTask(
this.extensionContext, this.channel, this.telemetryContext,
this.projectFolder, OperationType.Compile, PlatformType.EmbeddedLinux,
constants.compileTaskName);
this.extensionContext,
this.channel,
this.telemetryContext,
this.projectFolder,
OperationType.Compile,
PlatformType.EmbeddedLinux,
constants.compileTaskName
);
return true;
}
@ -138,22 +173,31 @@ export abstract class ContainerDeviceBase implements Device {
abstract async configDeviceSettings(): Promise<boolean>;
async configDeviceEnvironment(
projectPath: string, scaffoldType: ScaffoldType): Promise<void> {
projectPath: string,
scaffoldType: ScaffoldType
): Promise<void> {
if (!projectPath) {
throw new Error(
'Unable to find the project path, please open the folder and initialize project again.');
"Unable to find the project path, please open the folder and initialize project again."
);
}
// Get template list json object
const templateJsonFilePath = this.extensionContext.asAbsolutePath(path.join(
FileNames.resourcesFolderName, FileNames.templatesFolderName,
FileNames.templateFileName));
const templateJsonFileString =
await FileUtility.readFile(
scaffoldType, templateJsonFilePath, 'utf8') as string;
const templateJsonFilePath = this.extensionContext.asAbsolutePath(
path.join(
FileNames.resourcesFolderName,
FileNames.templatesFolderName,
FileNames.templateFileName
)
);
const templateJsonFileString = (await FileUtility.readFile(
scaffoldType,
templateJsonFilePath,
"utf8"
)) as string;
const templateJson = JSON.parse(templateJsonFileString);
if (!templateJson) {
throw new Error('Fail to load template list.');
throw new Error("Fail to load template list.");
}
// Select container
@ -164,11 +208,16 @@ export abstract class ContainerDeviceBase implements Device {
const templateName = containerSelection.label;
if (!templateName) {
throw new Error(
`Internal Error: Cannot get template name from template property.`);
`Internal Error: Cannot get template name from template property.`
);
}
const templateFilesInfo = await utils.getEnvTemplateFilesAndAskOverwrite(
this.extensionContext, this.projectFolder, scaffoldType, templateName);
this.extensionContext,
this.projectFolder,
scaffoldType,
templateName
);
if (templateFilesInfo.length === 0) {
throw new Error(`Internal Error: template files info is empty.`);
}
@ -176,44 +225,51 @@ export abstract class ContainerDeviceBase implements Device {
// Configure project environment with template files
for (const fileInfo of templateFilesInfo) {
// Replace binary name in tasks.json to project name
if (fileInfo.fileName === 'tasks.json') {
const pattern = '${project_name}';
if (fileInfo.fileName === "tasks.json") {
const pattern = "${project_name}";
const projectName = path.basename(projectPath);
if (fileInfo.fileContent) {
fileInfo.fileContent =
fileInfo.fileContent.replace(pattern, projectName);
fileInfo.fileContent = fileInfo.fileContent.replace(
pattern,
projectName
);
}
}
await utils.generateTemplateFile(projectPath, scaffoldType, fileInfo);
}
const message = 'Container device configuration done.';
const message = "Container device configuration done.";
utils.channelShowAndAppendLine(this.channel, message);
}
private async selectContainer(templateListJson: TemplatesType):
Promise<vscode.QuickPickItem|undefined> {
const containerTemplates =
templateListJson.templates.filter((template: ProjectTemplate) => {
return (
template.tag === TemplateTag.DevelopmentEnvironment &&
template.platform === PlatformType.EmbeddedLinux);
});
private async selectContainer(
templateListJson: TemplatesType
): Promise<vscode.QuickPickItem | undefined> {
const containerTemplates = templateListJson.templates.filter(
(template: ProjectTemplate) => {
return (
template.tag === TemplateTag.DevelopmentEnvironment &&
template.platform === PlatformType.EmbeddedLinux
);
}
);
const containerList: vscode.QuickPickItem[] = [];
containerTemplates.forEach((container: ProjectTemplate) => {
containerList.push({ label: container.name, detail: container.detail });
});
const containerSelection =
await vscode.window.showQuickPick(containerList, {
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select a toolchain container for your device platform',
});
const containerSelection = await vscode.window.showQuickPick(
containerList,
{
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: "Select a toolchain container for your device platform"
}
);
return containerSelection;
}
}
}

Просмотреть файл

@ -1,19 +1,27 @@
import * as crypto from 'crypto';
import * as fs from 'fs-plus';
import { Guid } from 'guid-typescript';
import * as path from 'path';
import * as vscode from 'vscode';
import * as crypto from "crypto";
import * as fs from "fs-plus";
import { Guid } from "guid-typescript";
import * as path from "path";
import * as vscode from "vscode";
import request = require('request-promise');
import rq = require('request');
import request = require("request-promise");
import rq = require("request");
import { AzureComponentsStorage, FileNames, ScaffoldType } from '../constants';
import { AzureComponentsStorage, FileNames, ScaffoldType } from "../constants";
import { AzureComponentConfig, AzureConfigFileHandler, AzureConfigs, ComponentInfo, Dependency, DependencyConfig, DependencyType } from './AzureComponentConfig';
import { ARMTemplate, AzureUtility } from './AzureUtility';
import { Component, ComponentType } from './Interfaces/Component';
import { Provisionable } from './Interfaces/Provisionable';
import { channelShowAndAppendLine, channelPrintJsonObject } from '../utils';
import {
AzureComponentConfig,
AzureConfigFileHandler,
AzureConfigs,
ComponentInfo,
Dependency,
DependencyConfig,
DependencyType
} from "./AzureComponentConfig";
import { ARMTemplate, AzureUtility } from "./AzureUtility";
import { Component, ComponentType } from "./Interfaces/Component";
import { Provisionable } from "./Interfaces/Provisionable";
import { channelShowAndAppendLine, channelPrintJsonObject } from "../utils";
export class CosmosDB implements Component, Provisionable {
dependencies: DependencyConfig[] = [];
@ -23,15 +31,17 @@ export class CosmosDB implements Component, Provisionable {
private componentId: string;
private azureConfigHandler: AzureConfigFileHandler;
private extensionContext: vscode.ExtensionContext;
private catchedCosmosDbList: Array<{name: string}> = [];
private catchedCosmosDbList: Array<{ name: string }> = [];
get id(): string {
return this.componentId;
}
constructor(
context: vscode.ExtensionContext, projectRoot: string,
context: vscode.ExtensionContext,
projectRoot: string,
channel: vscode.OutputChannel,
dependencyComponents: Dependency[]|null = null) {
dependencyComponents: Dependency[] | null = null
) {
this.componentType = ComponentType.CosmosDB;
this.channel = channel;
this.componentId = Guid.create().toString();
@ -39,13 +49,16 @@ export class CosmosDB implements Component, Provisionable {
this.azureConfigHandler = new AzureConfigFileHandler(projectRoot);
this.extensionContext = context;
if (dependencyComponents && dependencyComponents.length > 0) {
dependencyComponents.forEach(
dependency => this.dependencies.push(
{ id: dependency.component.id, type: dependency.type }));
dependencyComponents.forEach(dependency =>
this.dependencies.push({
id: dependency.component.id,
type: dependency.type
})
);
}
}
name = 'Cosmos DB';
name = "Cosmos DB";
getComponentType(): ComponentType {
return this.componentType;
@ -57,8 +70,10 @@ export class CosmosDB implements Component, Provisionable {
async load(): Promise<boolean> {
const azureConfigFilePath = path.join(
this.projectRootPath, AzureComponentsStorage.folderName,
AzureComponentsStorage.fileName);
this.projectRootPath,
AzureComponentsStorage.folderName,
AzureComponentsStorage.fileName
);
if (!fs.existsSync(azureConfigFilePath)) {
return false;
@ -67,9 +82,10 @@ export class CosmosDB implements Component, Provisionable {
let azureConfigs: AzureConfigs;
try {
azureConfigs = JSON.parse(fs.readFileSync(azureConfigFilePath, 'utf8'));
azureConfigs = JSON.parse(fs.readFileSync(azureConfigFilePath, "utf8"));
const cosmosDBConfig = azureConfigs.componentConfigs.find(
config => config.type === this.componentType);
config => config.type === this.componentType
);
if (cosmosDBConfig) {
this.componentId = cosmosDBConfig.id;
this.dependencies = cosmosDBConfig.dependencies;
@ -85,21 +101,28 @@ export class CosmosDB implements Component, Provisionable {
await this.updateConfigSettings(ScaffoldType.Local);
}
async updateConfigSettings(type: ScaffoldType, componentInfo?: ComponentInfo):
Promise<void> {
const cosmosDBComponentIndex =
await this.azureConfigHandler.getComponentIndexById(type, this.id);
async updateConfigSettings(
type: ScaffoldType,
componentInfo?: ComponentInfo
): Promise<void> {
const cosmosDBComponentIndex = await this.azureConfigHandler.getComponentIndexById(
type,
this.id
);
if (cosmosDBComponentIndex > -1) {
if (!componentInfo) {
return;
}
await this.azureConfigHandler.updateComponent(
type, cosmosDBComponentIndex, componentInfo);
type,
cosmosDBComponentIndex,
componentInfo
);
} else {
const newCosmosDBConfig: AzureComponentConfig = {
id: this.id,
folder: '',
name: '',
folder: "",
name: "",
dependencies: this.dependencies,
type: this.componentType
};
@ -109,39 +132,48 @@ export class CosmosDB implements Component, Provisionable {
async provision(): Promise<boolean> {
const cosmosDbList = this.getCosmosDbInResourceGroup();
const cosmosDbNameChoose = await vscode.window.showQuickPick(
cosmosDbList, { placeHolder: 'Select Cosmos DB', ignoreFocusOut: true });
const cosmosDbNameChoose = await vscode.window.showQuickPick(cosmosDbList, {
placeHolder: "Select Cosmos DB",
ignoreFocusOut: true
});
if (!cosmosDbNameChoose) {
return false;
}
let cosmosDbName = '';
let cosmosDbKey = '';
let cosmosDbName = "";
let cosmosDbKey = "";
const scaffoldType = ScaffoldType.Workspace;
if (!cosmosDbNameChoose.description) {
if (this.channel) {
channelShowAndAppendLine(this.channel, 'Creating Cosmos DB...');
channelShowAndAppendLine(this.channel, "Creating Cosmos DB...");
}
const cosmosDBArmTemplatePath = this.extensionContext.asAbsolutePath(
path.join(FileNames.resourcesFolderName, 'arm', 'cosmosdb.json'));
const cosmosDBArmTemplate =
JSON.parse(fs.readFileSync(cosmosDBArmTemplatePath, 'utf8')) as
ARMTemplate;
path.join(FileNames.resourcesFolderName, "arm", "cosmosdb.json")
);
const cosmosDBArmTemplate = JSON.parse(
fs.readFileSync(cosmosDBArmTemplatePath, "utf8")
) as ARMTemplate;
const cosmosDBDeploy =
await AzureUtility.deployARMTemplate(cosmosDBArmTemplate);
if (!cosmosDBDeploy || !cosmosDBDeploy.properties ||
!cosmosDBDeploy.properties.outputs ||
!cosmosDBDeploy.properties.outputs.cosmosDBAccountName ||
!cosmosDBDeploy.properties.outputs.cosmosDBAccountKey) {
throw new Error('Provision Cosmos DB failed.');
const cosmosDBDeploy = await AzureUtility.deployARMTemplate(
cosmosDBArmTemplate
);
if (
!cosmosDBDeploy ||
!cosmosDBDeploy.properties ||
!cosmosDBDeploy.properties.outputs ||
!cosmosDBDeploy.properties.outputs.cosmosDBAccountName ||
!cosmosDBDeploy.properties.outputs.cosmosDBAccountKey
) {
throw new Error("Provision Cosmos DB failed.");
}
channelPrintJsonObject(this.channel, cosmosDBDeploy);
for (const dependency of this.dependencies) {
const componentConfig = await this.azureConfigHandler.getComponentById(
scaffoldType, dependency.id);
scaffoldType,
dependency.id
);
if (!componentConfig) {
throw new Error(`Cannot find component with id ${dependency.id}.`);
}
@ -153,11 +185,11 @@ export class CosmosDB implements Component, Provisionable {
}
cosmosDbName =
cosmosDBDeploy.properties.outputs.cosmosDBAccountName.value;
cosmosDBDeploy.properties.outputs.cosmosDBAccountName.value;
cosmosDbKey = cosmosDBDeploy.properties.outputs.cosmosDBAccountKey.value;
} else {
if (this.channel) {
channelShowAndAppendLine(this.channel, 'Creating Cosmos DB...');
channelShowAndAppendLine(this.channel, "Creating Cosmos DB...");
}
cosmosDbName = cosmosDbNameChoose.label;
@ -169,13 +201,15 @@ export class CosmosDB implements Component, Provisionable {
}
const databaseList = this.getDatabases(cosmosDbName, cosmosDbKey);
const databaseChoose = await vscode.window.showQuickPick(
databaseList, { placeHolder: 'Select Database', ignoreFocusOut: true });
const databaseChoose = await vscode.window.showQuickPick(databaseList, {
placeHolder: "Select Database",
ignoreFocusOut: true
});
if (!databaseChoose) {
return false;
}
let database: string|undefined = '';
let database: string | undefined = "";
if (!databaseChoose.description) {
database = await vscode.window.showInputBox({
@ -184,7 +218,7 @@ export class CosmosDB implements Component, Provisionable {
validateInput: async (value: string) => {
value = value.trim();
if (!value) {
return 'Please fill this field.';
return "Please fill this field.";
}
if (!/^[^\\/#?]+/.test(value)) {
return 'May not end with space nor contain "\\", "/", "#", "?".';
@ -197,25 +231,32 @@ export class CosmosDB implements Component, Provisionable {
return false;
}
database = database.trim();
const cosmosDBApiRes =
await this.ensureDatabase(cosmosDbName, cosmosDbKey, database);
const cosmosDBApiRes = await this.ensureDatabase(
cosmosDbName,
cosmosDbKey,
database
);
if (!cosmosDBApiRes) {
throw new Error('Error occurred when create database.');
throw new Error("Error occurred when create database.");
}
} else {
database = databaseChoose.label;
}
const collectionList =
this.getCollections(cosmosDbName, cosmosDbKey, database);
const collectionChoose = await vscode.window.showQuickPick(
collectionList,
{ placeHolder: 'Select Collection', ignoreFocusOut: true });
const collectionList = this.getCollections(
cosmosDbName,
cosmosDbKey,
database
);
const collectionChoose = await vscode.window.showQuickPick(collectionList, {
placeHolder: "Select Collection",
ignoreFocusOut: true
});
if (!collectionChoose) {
return false;
}
let collection: string|undefined = '';
let collection: string | undefined = "";
if (!collectionChoose.description) {
collection = await vscode.window.showInputBox({
@ -224,7 +265,7 @@ export class CosmosDB implements Component, Provisionable {
validateInput: async (value: string) => {
value = value.trim();
if (!value) {
return 'Please fill this field.';
return "Please fill this field.";
}
if (!/^[^\\/#?]+/.test(value)) {
return 'May not end with space nor contain "\\", "/", "#", "?".';
@ -238,9 +279,13 @@ export class CosmosDB implements Component, Provisionable {
}
collection = collection.trim();
const cosmosDBApiRes = await this.ensureCollection(
cosmosDbName, cosmosDbKey, database, collection);
cosmosDbName,
cosmosDbKey,
database,
collection
);
if (!cosmosDBApiRes) {
throw new Error('Error occurred when create collection.');
throw new Error("Error occurred when create collection.");
}
} else {
collection = collectionChoose.label;
@ -258,50 +303,76 @@ export class CosmosDB implements Component, Provisionable {
});
if (this.channel) {
channelShowAndAppendLine(this.channel, 'Cosmos DB provision succeeded.');
channelShowAndAppendLine(this.channel, "Cosmos DB provision succeeded.");
}
return true;
}
private _getCosmosDBAuthorizationToken(key: string, verb: string, date: string, resourceType: string, resourceId: string): string {
const _key = Buffer.from(key, 'base64');
const stringToSign =
(`${verb}\n${resourceType}\n${resourceId}\n${date}\n\n`).toLowerCase();
private _getCosmosDBAuthorizationToken(
key: string,
verb: string,
date: string,
resourceType: string,
resourceId: string
): string {
const _key = Buffer.from(key, "base64");
const stringToSign = `${verb}\n${resourceType}\n${resourceId}\n${date}\n\n`.toLowerCase();
const body = Buffer.from(stringToSign, 'utf8');
const signature =
crypto.createHmac('sha256', _key).update(body).digest('base64');
const body = Buffer.from(stringToSign, "utf8");
const signature = crypto
.createHmac("sha256", _key)
.update(body)
.digest("base64");
const masterToken = 'master';
const tokenVersion = '1.0';
const masterToken = "master";
const tokenVersion = "1.0";
return encodeURIComponent(
`type=${masterToken}&ver=${tokenVersion}&sig=${signature}`);
`type=${masterToken}&ver=${tokenVersion}&sig=${signature}`
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _getRestHeaders(key: string, verb: string, resourceType: string, resourceId: string): any {
private _getRestHeaders(
key: string,
verb: string,
resourceType: string,
resourceId: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): any {
const date = new Date().toUTCString();
const authorization = this._getCosmosDBAuthorizationToken(
key, verb, date, resourceType, resourceId);
key,
verb,
date,
resourceType,
resourceId
);
const headers = {
'Authorization': authorization,
'Content-Type': 'application/json',
'x-ms-date': date,
'x-ms-version': '2017-02-22'
Authorization: authorization,
"Content-Type": "application/json",
"x-ms-date": date,
"x-ms-version": "2017-02-22"
};
return headers;
}
private async _apiRequest(account: string, key: string, verb: string, path: string, resourceType: string, resourceId: string, body: {id: string}|null = null): Promise<rq.Response> {
private async _apiRequest(
account: string,
key: string,
verb: string,
path: string,
resourceType: string,
resourceId: string,
body: { id: string } | null = null
): Promise<rq.Response> {
const apiUrl = `https://${account}.documents.azure.com/${path}`;
const headers = this._getRestHeaders(key, verb, resourceType, resourceId);
const apiRes: rq.Response = await request({
method: verb,
uri: apiUrl,
headers,
encoding: 'utf8',
encoding: "utf8",
body,
json: true,
resolveWithFullResponse: true,
@ -314,12 +385,24 @@ export class CosmosDB implements Component, Provisionable {
return apiRes;
}
async getDatabases(account: string, key: string): Promise<vscode.QuickPickItem[]> {
const getDatabasesRes =
await this._apiRequest(account, key, 'GET', 'dbs', 'dbs', '');
const listRes = getDatabasesRes.body as {Databases: Array<{id: string}>};
const databaseList: vscode.QuickPickItem[] =
[{ label: '$(plus) Create New Database', description: '' }];
async getDatabases(
account: string,
key: string
): Promise<vscode.QuickPickItem[]> {
const getDatabasesRes = await this._apiRequest(
account,
key,
"GET",
"dbs",
"dbs",
""
);
const listRes = getDatabasesRes.body as {
Databases: Array<{ id: string }>;
};
const databaseList: vscode.QuickPickItem[] = [
{ label: "$(plus) Create New Database", description: "" }
];
for (const item of listRes.Databases) {
databaseList.push({ label: item.id, description: account });
}
@ -327,15 +410,32 @@ export class CosmosDB implements Component, Provisionable {
return databaseList;
}
async ensureDatabase(account: string, key: string, database: string): Promise<boolean> {
async ensureDatabase(
account: string,
key: string,
database: string
): Promise<boolean> {
const getDatabaseRes = await this._apiRequest(
account, key, 'GET', `dbs/${database}`, 'dbs', `dbs/${database}`);
account,
key,
"GET",
`dbs/${database}`,
"dbs",
`dbs/${database}`
);
if (getDatabaseRes.statusCode === 200) {
return true;
}
const createDatabaseRes = await this._apiRequest(
account, key, 'POST', 'dbs', 'dbs', '', { id: database });
account,
key,
"POST",
"dbs",
"dbs",
"",
{ id: database }
);
if (createDatabaseRes.statusCode === 201) {
return true;
}
@ -343,33 +443,62 @@ export class CosmosDB implements Component, Provisionable {
return false;
}
async getCollections(account: string, key: string, database: string): Promise<vscode.QuickPickItem[]> {
async getCollections(
account: string,
key: string,
database: string
): Promise<vscode.QuickPickItem[]> {
const getDCollectionsRes = await this._apiRequest(
account, key, 'GET', `dbs/${database}/colls`, 'colls',
`dbs/${database}`);
const listRes =
getDCollectionsRes.body as {DocumentCollections: Array<{id: string}>};
const collectionList: vscode.QuickPickItem[] =
[{ label: '$(plus) Create New Collection', description: '' }];
account,
key,
"GET",
`dbs/${database}/colls`,
"colls",
`dbs/${database}`
);
const listRes = getDCollectionsRes.body as {
DocumentCollections: Array<{ id: string }>;
};
const collectionList: vscode.QuickPickItem[] = [
{ label: "$(plus) Create New Collection", description: "" }
];
for (const item of listRes.DocumentCollections) {
collectionList.push(
{ label: item.id, description: `${account}/${database}` });
collectionList.push({
label: item.id,
description: `${account}/${database}`
});
}
return collectionList;
}
async ensureCollection(account: string, key: string, database: string, collection: string): Promise<boolean> {
async ensureCollection(
account: string,
key: string,
database: string,
collection: string
): Promise<boolean> {
const getCollectionRes = await this._apiRequest(
account, key, 'GET', `dbs/${database}/colls/${collection}`, 'colls',
`dbs/${database}/colls/${collection}`);
account,
key,
"GET",
`dbs/${database}/colls/${collection}`,
"colls",
`dbs/${database}/colls/${collection}`
);
if (getCollectionRes.statusCode === 200) {
return true;
}
const creatCollectionRes = await this._apiRequest(
account, key, 'POST', `dbs/${database}/colls`, 'colls',
`dbs/${database}`, { id: collection });
account,
key,
"POST",
`dbs/${database}/colls`,
"colls",
`dbs/${database}`,
{ id: collection }
);
if (creatCollectionRes.statusCode === 201) {
return true;
}
@ -377,19 +506,21 @@ export class CosmosDB implements Component, Provisionable {
return false;
}
private getCosmosDbByNameFromCache(name: string): {name: string}|undefined {
private getCosmosDbByNameFromCache(
name: string
): { name: string } | undefined {
return this.catchedCosmosDbList.find(item => item.name === name);
}
private async getCosmosDbInResourceGroup(): Promise<vscode.QuickPickItem[]> {
const resource = `/subscriptions/${
AzureUtility.subscriptionId}/resourceGroups/${
AzureUtility
.resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts?api-version=2015-04-08`;
const cosmosDbListRes = await AzureUtility.getRequest(resource) as
{value: Array<{name: string; location: string}>};
const cosmosDbList: vscode.QuickPickItem[] =
[{ label: '$(plus) Create New Cosmos DB', description: '' }];
const resource = `/subscriptions/${AzureUtility.subscriptionId}/resourceGroups\
/${AzureUtility.resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts?api-version=2015-04-08`;
const cosmosDbListRes = (await AzureUtility.getRequest(resource)) as {
value: Array<{ name: string; location: string }>;
};
const cosmosDbList: vscode.QuickPickItem[] = [
{ label: "$(plus) Create New Cosmos DB", description: "" }
];
for (const item of cosmosDbListRes.value) {
cosmosDbList.push({ label: item.name, description: item.location });
}
@ -398,13 +529,11 @@ export class CosmosDB implements Component, Provisionable {
}
private async getCosmosDbKey(name: string): Promise<string> {
const resource = `/subscriptions/${
AzureUtility.subscriptionId}/resourceGroups/${
AzureUtility
.resourceGroup}/providers/Microsoft.DocumentDB/databaseAccounts/${
name}/listKeys?api-version=2015-04-08`;
const cosmosDbKeyListRes =
await AzureUtility.postRequest(resource) as {primaryMasterKey: string};
const resource = `/subscriptions/${AzureUtility.subscriptionId}/resourceGroups/${AzureUtility.resourceGroup}\
/providers/Microsoft.DocumentDB/databaseAccounts/${name}/listKeys?api-version=2015-04-08`;
const cosmosDbKeyListRes = (await AzureUtility.postRequest(resource)) as {
primaryMasterKey: string;
};
return cosmosDbKeyListRes.primaryMasterKey;
}
}
}

Просмотреть файл

@ -1,26 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as clipboardy from 'clipboardy';
import * as fs from 'fs-plus';
import { Guid } from 'guid-typescript';
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import * as clipboardy from "clipboardy";
import * as fs from "fs-plus";
import { Guid } from "guid-typescript";
import * as os from "os";
import * as path from "path";
import * as vscode from "vscode";
import { BoardProvider } from '../boardProvider';
import { ConfigHandler } from '../configHandler';
import { ConfigKey, OSPlatform } from '../constants';
import { TelemetryContext } from '../telemetry';
import { BoardProvider } from "../boardProvider";
import { ConfigHandler } from "../configHandler";
import { ConfigKey, OSPlatform } from "../constants";
import { TelemetryContext } from "../telemetry";
import { ArduinoDeviceBase } from './ArduinoDeviceBase';
import { DeviceType } from './Interfaces/Device';
import { TemplateFileInfo } from './Interfaces/ProjectTemplate';
import { Board } from './Interfaces/Board';
import { ArduinoDeviceBase } from "./ArduinoDeviceBase";
import { DeviceType } from "./Interfaces/Device";
import { TemplateFileInfo } from "./Interfaces/ProjectTemplate";
import { Board } from "./Interfaces/Board";
export class Esp32Device extends ArduinoDeviceBase {
private templateFiles: TemplateFileInfo[] = [];
private static _boardId = 'esp32';
private static _boardId = "esp32";
private componentId: string;
get id(): string {
@ -39,16 +39,22 @@ export class Esp32Device extends ArduinoDeviceBase {
get version(): string {
const platform = os.platform();
let packageRootPath = '';
let version = '0.0.1';
let packageRootPath = "";
let version = "0.0.1";
if (platform === OSPlatform.WIN32) {
const homeDir = os.homedir();
const localAppData: string = path.join(homeDir, 'AppData', 'Local');
const localAppData: string = path.join(homeDir, "AppData", "Local");
packageRootPath = path.join(
localAppData, 'Arduino15', 'packages', 'esp32', 'hardware', 'esp32');
localAppData,
"Arduino15",
"packages",
"esp32",
"hardware",
"esp32"
);
} else {
packageRootPath = '~/Library/Arduino15/packages/esp32/hardware/esp32';
packageRootPath = "~/Library/Arduino15/packages/esp32/hardware/esp32";
}
if (fs.existsSync(packageRootPath)) {
@ -61,14 +67,16 @@ export class Esp32Device extends ArduinoDeviceBase {
return version;
}
name = 'Esp32Arduino';
name = "Esp32Arduino";
constructor(
context: vscode.ExtensionContext, channel: vscode.OutputChannel,
telemetryContext: TelemetryContext, devicePath: string,
templateFiles?: TemplateFileInfo[]) {
super(
context, devicePath, channel, telemetryContext, DeviceType.IoTButton);
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext,
devicePath: string,
templateFiles?: TemplateFileInfo[]
) {
super(context, devicePath, channel, telemetryContext, DeviceType.IoTButton);
this.channel = channel;
this.componentId = Guid.create().toString();
if (templateFiles) {
@ -87,41 +95,44 @@ export class Esp32Device extends ArduinoDeviceBase {
async configDeviceSettings(): Promise<boolean> {
const configSelectionItems: vscode.QuickPickItem[] = [
{
label: 'Copy device connection string',
description: 'Copy device connection string',
detail: 'Copy'
label: "Copy device connection string",
description: "Copy device connection string",
detail: "Copy"
},
{
label: 'Generate CRC for OTA',
label: "Generate CRC for OTA",
description:
'Generate Cyclic Redundancy Check(CRC) code for OTA Update',
detail: 'Config CRC'
"Generate Cyclic Redundancy Check(CRC) code for OTA Update",
detail: "Config CRC"
}
];
const configSelection =
await vscode.window.showQuickPick(configSelectionItems, {
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select an option',
});
const configSelection = await vscode.window.showQuickPick(
configSelectionItems,
{
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: "Select an option"
}
);
if (!configSelection) {
return false;
}
if (configSelection.detail === 'Config CRC') {
const retValue: boolean =
await this.generateCrc(this.channel);
if (configSelection.detail === "Config CRC") {
const retValue: boolean = await this.generateCrc(this.channel);
return retValue;
} else if (configSelection.detail === 'Copy') {
const deviceConnectionString =
ConfigHandler.get<string>(ConfigKey.iotHubDeviceConnectionString);
} else if (configSelection.detail === "Copy") {
const deviceConnectionString = ConfigHandler.get<string>(
ConfigKey.iotHubDeviceConnectionString
);
if (!deviceConnectionString) {
throw new Error(
'Unable to get the device connection string, please invoke the command of Azure Provision first.');
"Unable to get the device connection string, please invoke the command of Azure Provision first."
);
}
clipboardy.writeSync(deviceConnectionString);
return true;

Просмотреть файл

@ -5,4 +5,4 @@ export enum ExtensionName {
Toolkit = 1,
AzureAccount,
DigitalTwins
}
}

Просмотреть файл

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as vscode from 'vscode';
import * as vscode from "vscode";
export interface BoardInstallation {
additionalUrl: string;
@ -22,4 +22,4 @@ export interface Board {
installation?: BoardInstallation;
}
export interface BoardQuickPickItem extends vscode.QuickPickItem, Board {}
export interface BoardQuickPickItem extends vscode.QuickPickItem, Board {}

Просмотреть файл

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as vscode from 'vscode';
import * as vscode from "vscode";
export interface CommandItem extends vscode.QuickPickItem {
/**
@ -18,11 +18,11 @@ export interface CommandItem extends vscode.QuickPickItem {
* workspace configuration contains
* the specific field.
*/
only?: string|string[];
only?: string | string[];
/**
* Show the menu item when only the
* deviceId of current workspace is in
* variable 'deviceIds'
*/
deviceIds?: string[];
}
}

Просмотреть файл

@ -1,4 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
export interface Compilable { compile(): Promise<boolean> }
export interface Compilable {
compile(): Promise<boolean>;
}

Просмотреть файл

@ -2,12 +2,12 @@
// Licensed under the MIT License.
export enum ComponentType {
Device = 'Device',
IoTHub = 'IoTHub',
AzureFunctions = 'AzureFunctions',
IoTHubDevice = 'IoTHubDevice',
StreamAnalyticsJob = 'StreamAnalyticsJob',
CosmosDB = 'CosmosDB'
Device = "Device",
IoTHub = "IoTHub",
AzureFunctions = "AzureFunctions",
IoTHubDevice = "IoTHubDevice",
StreamAnalyticsJob = "StreamAnalyticsJob",
CosmosDB = "CosmosDB"
}
export interface Component {
@ -17,4 +17,4 @@ export interface Component {
create(): Promise<void>;
checkPrerequisites(): Promise<boolean>;
getComponentType(): ComponentType;
}
}

Просмотреть файл

@ -1,4 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
export interface Deployable { deploy(): Promise<boolean> }
export interface Deployable {
deploy(): Promise<boolean>;
}

Просмотреть файл

@ -1,11 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { ScaffoldType } from '../../constants';
import { ScaffoldType } from "../../constants";
import { Compilable } from './Compilable';
import { Component } from './Component';
import { Uploadable } from './Uploadable';
import { Compilable } from "./Compilable";
import { Component } from "./Component";
import { Uploadable } from "./Uploadable";
export enum DeviceType {
MXChipAZ3166 = 1,
@ -17,6 +17,8 @@ export enum DeviceType {
export interface Device extends Component, Compilable, Uploadable {
getDeviceType(): DeviceType;
configDeviceSettings(): Promise<boolean>;
configDeviceEnvironment(deviceRootPath: string, scaffoldType: ScaffoldType):
Promise<void>;
configDeviceEnvironment(
deviceRootPath: string,
scaffoldType: ScaffoldType
): Promise<void>;
}

Просмотреть файл

@ -2,7 +2,7 @@
// Licensed under the MIT License.
export enum ProjectHostType {
Unknown = 'Unknown',
Workspace = 'Workspace',
Container = 'Container'
}
Unknown = "Unknown",
Workspace = "Workspace",
Container = "Container"
}

Просмотреть файл

@ -2,10 +2,10 @@
// Licensed under the MIT License.
export enum ProjectTemplateType {
Basic = 'Basic',
IotHub = 'IotHub',
AzureFunctions = 'AzureFunctions',
StreamAnalytics = 'StreamAnalytics'
Basic = "Basic",
IotHub = "IotHub",
AzureFunctions = "AzureFunctions",
StreamAnalytics = "StreamAnalytics"
}
export interface TemplateFileInfo {
@ -16,7 +16,9 @@ export interface TemplateFileInfo {
overwrite?: boolean;
}
export interface TemplatesType { templates: ProjectTemplate[] }
export interface TemplatesType {
templates: ProjectTemplate[];
}
export interface ProjectTemplate {
platform: string;
name: string;
@ -60,4 +62,4 @@ export interface DeviceConfig {
label: string;
description: string;
detail: string;
}
}

Просмотреть файл

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { ScaffoldType } from '../../constants';
import { ComponentInfo, DependencyConfig } from '../AzureComponentConfig';
import { ScaffoldType } from "../../constants";
import { ComponentInfo, DependencyConfig } from "../AzureComponentConfig";
export interface Provisionable {
dependencies: DependencyConfig[];

Просмотреть файл

@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { QuickPickItem } from 'vscode';
import { QuickPickItem } from "vscode";
export interface PickWithData<T> extends QuickPickItem { data: T }
export interface PickWithData<T> extends QuickPickItem {
data: T;
}

Просмотреть файл

@ -1,4 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
export interface Uploadable { upload(): Promise<boolean> }
export interface Uploadable {
upload(): Promise<boolean>;
}

Просмотреть файл

@ -2,6 +2,6 @@
// Licensed under the MIT License.
export interface Workspace {
folders: Array<{path: string}>;
settings: {[key: string]: string};
}
folders: Array<{ path: string }>;
settings: { [key: string]: string };
}

Просмотреть файл

@ -1,28 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as fs from 'fs-plus';
import { Guid } from 'guid-typescript';
import * as path from 'path';
import * as request from 'request-promise';
import * as vscode from 'vscode';
import * as fs from "fs-plus";
import { Guid } from "guid-typescript";
import * as path from "path";
import * as request from "request-promise";
import * as vscode from "vscode";
import { ConfigHandler } from '../configHandler';
import { ConfigKey, ScaffoldType } from '../constants';
import { FileUtility } from '../FileUtility';
import { generateTemplateFile } from '../utils';
import { ConfigHandler } from "../configHandler";
import { ConfigKey, ScaffoldType } from "../constants";
import { FileUtility } from "../FileUtility";
import { generateTemplateFile } from "../utils";
import { ComponentType } from './Interfaces/Component';
import { Device, DeviceType } from './Interfaces/Device';
import { TemplateFileInfo } from './Interfaces/ProjectTemplate';
import { ComponentType } from "./Interfaces/Component";
import { Device, DeviceType } from "./Interfaces/Device";
import { TemplateFileInfo } from "./Interfaces/ProjectTemplate";
const constants = {
timeout: 10000,
accessEndpoint: 'http://192.168.4.1',
userjsonFilename: 'userdata.json'
accessEndpoint: "http://192.168.4.1",
userjsonFilename: "userdata.json"
};
export class IoTButtonDevice implements Device {
private deviceType: DeviceType;
private componentType: ComponentType;
@ -33,21 +32,23 @@ export class IoTButtonDevice implements Device {
return this.componentId;
}
private static _boardId = 'iotbutton';
private static _boardId = "iotbutton";
static get boardId(): string {
return IoTButtonDevice._boardId;
}
constructor(devicePath: string, private templateFilesInfo: TemplateFileInfo[] = []) {
constructor(
devicePath: string,
private templateFilesInfo: TemplateFileInfo[] = []
) {
this.deviceType = DeviceType.IoTButton;
this.componentType = ComponentType.Device;
this.deviceFolder = devicePath;
this.componentId = Guid.create().toString();
}
name = 'IoTButton';
name = "IoTButton";
getDeviceType(): DeviceType {
return this.deviceType;
@ -65,33 +66,42 @@ export class IoTButtonDevice implements Device {
const deviceFolderPath = this.deviceFolder;
if (!fs.existsSync(deviceFolderPath)) {
throw new Error('Unable to find the device folder inside the project.');
throw new Error("Unable to find the device folder inside the project.");
}
return true;
}
async create(): Promise<void> {
const createTimeScaffoldType = ScaffoldType.Local;
if (!await FileUtility.directoryExists(
createTimeScaffoldType, this.deviceFolder)) {
if (
!(await FileUtility.directoryExists(
createTimeScaffoldType,
this.deviceFolder
))
) {
throw new Error(`Internal error: Couldn't find the template folder.`);
}
for (const fileInfo of this.templateFilesInfo) {
await generateTemplateFile(
this.deviceFolder, createTimeScaffoldType, fileInfo);
this.deviceFolder,
createTimeScaffoldType,
fileInfo
);
}
}
async compile(): Promise<boolean> {
vscode.window.showInformationMessage(
'Congratulations! There is no device code to compile in this project.');
"Congratulations! There is no device code to compile in this project."
);
return true;
}
async upload(): Promise<boolean> {
vscode.window.showInformationMessage(
'Congratulations! There is no device code to upload in this project.');
"Congratulations! There is no device code to upload in this project."
);
return true;
}
@ -100,82 +110,87 @@ export class IoTButtonDevice implements Device {
// connection.
const configSelectionItems: vscode.QuickPickItem[] = [
{
label: 'Config WiFi of IoT button',
description: 'Config WiFi of IoT button',
detail: 'Config WiFi'
label: "Config WiFi of IoT button",
description: "Config WiFi of IoT button",
detail: "Config WiFi"
},
{
label: 'Config connection of IoT Hub Device',
description: 'Config connection of IoT Hub Device',
detail: 'Config IoT Hub Device'
label: "Config connection of IoT Hub Device",
description: "Config connection of IoT Hub Device",
detail: "Config IoT Hub Device"
},
{
label: 'Config time server of IoT button',
description: 'Config time server of IoT button',
detail: 'Config Time Server'
label: "Config time server of IoT button",
description: "Config time server of IoT button",
detail: "Config Time Server"
},
{
label: 'Config JSON data to append to message',
description: 'Config JSON data to append to message',
detail: 'Config User Json Data'
label: "Config JSON data to append to message",
description: "Config JSON data to append to message",
detail: "Config User Json Data"
},
{
label: 'Shutdown IoT button',
description: 'Shutdown IoT button',
detail: 'Shutdown'
label: "Shutdown IoT button",
description: "Shutdown IoT button",
detail: "Shutdown"
}
];
const configSelection =
await vscode.window.showQuickPick(configSelectionItems, {
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select an option',
});
const configSelection = await vscode.window.showQuickPick(
configSelectionItems,
{
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: "Select an option"
}
);
if (!configSelection) {
return false;
}
if (configSelection.detail === 'Config WiFi') {
if (configSelection.detail === "Config WiFi") {
try {
const res = await this.configWifi();
if (res) {
vscode.window.showInformationMessage('Config WiFi successfully.');
vscode.window.showInformationMessage("Config WiFi successfully.");
}
} catch (error) {
vscode.window.showWarningMessage('Config WiFi failed.');
vscode.window.showWarningMessage("Config WiFi failed.");
}
} else if (configSelection.detail === 'Config IoT Hub Device') {
} else if (configSelection.detail === "Config IoT Hub Device") {
try {
const res = await this.configHub();
if (res) {
vscode.window.showInformationMessage(
'Config Azure IoT Hub successfully.');
"Config Azure IoT Hub successfully."
);
}
} catch (error) {
vscode.window.showWarningMessage('Config IoT Hub failed.');
vscode.window.showWarningMessage("Config IoT Hub failed.");
}
} else if (configSelection.detail === 'Config Time Server') {
} else if (configSelection.detail === "Config Time Server") {
try {
const res = await this.configNtp();
if (res) {
vscode.window.showInformationMessage(
'Config time server successfully.');
"Config time server successfully."
);
}
} catch (error) {
vscode.window.showWarningMessage('Config IoT Hub failed.');
vscode.window.showWarningMessage("Config IoT Hub failed.");
}
} else if (configSelection.detail === 'Config User Json Data') {
} else if (configSelection.detail === "Config User Json Data") {
try {
const res = await this.configUserData();
if (res) {
vscode.window.showInformationMessage(
'Config user data successfully.');
"Config user data successfully."
);
}
} catch (error) {
vscode.window.showWarningMessage('Config user data failed.');
vscode.window.showWarningMessage("Config user data failed.");
}
} else {
try {
@ -186,7 +201,7 @@ export class IoTButtonDevice implements Device {
// the action
}
vscode.window.showInformationMessage('Shutdown IoT button completed.');
vscode.window.showInformationMessage("Shutdown IoT button completed.");
return true;
}
@ -195,13 +210,17 @@ export class IoTButtonDevice implements Device {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async setConfig(uri: string, data: {}): Promise<any> {
const option =
{ uri, method: 'POST', timeout: constants.timeout, form: data };
const option = {
uri,
method: "POST",
timeout: constants.timeout,
form: data
};
const res = await request(option);
if (!res) {
throw new Error('Empty response.');
throw new Error("Empty response.");
}
return res;
@ -214,7 +233,7 @@ export class IoTButtonDevice implements Device {
ignoreFocusOut: true,
validateInput: (ssid: string) => {
if (!ssid) {
return 'WiFi SSID cannot be empty.';
return "WiFi SSID cannot be empty.";
} else {
return;
}
@ -225,8 +244,11 @@ export class IoTButtonDevice implements Device {
return false;
}
const password = await vscode.window.showInputBox(
{ prompt: `WiFi Password`, password: true, ignoreFocusOut: true });
const password = await vscode.window.showInputBox({
prompt: `WiFi Password`,
password: true,
ignoreFocusOut: true
});
if (!password) {
return false;
@ -241,20 +263,23 @@ export class IoTButtonDevice implements Device {
}
async configHub(): Promise<boolean> {
let deviceConnectionString =
ConfigHandler.get<string>(ConfigKey.iotHubDeviceConnectionString);
let deviceConnectionString = ConfigHandler.get<string>(
ConfigKey.iotHubDeviceConnectionString
);
let hostName = '';
let deviceId = '';
let hostName = "";
let deviceId = "";
if (deviceConnectionString) {
const hostnameMatches =
deviceConnectionString.match(/HostName=(.*?)(;|$)/);
const hostnameMatches = deviceConnectionString.match(
/HostName=(.*?)(;|$)/
);
if (hostnameMatches) {
hostName = hostnameMatches[0];
}
const deviceIDMatches =
deviceConnectionString.match(/DeviceId=(.*?)(;|$)/);
const deviceIDMatches = deviceConnectionString.match(
/DeviceId=(.*?)(;|$)/
);
if (deviceIDMatches) {
deviceId = deviceIDMatches[0];
}
@ -264,43 +289,47 @@ export class IoTButtonDevice implements Device {
if (deviceId && hostName) {
deviceConnectionStringSelection = [
{
label: 'Select IoT Hub Device Connection String',
description: '',
label: "Select IoT Hub Device Connection String",
description: "",
detail: `Device Information: ${hostName} ${deviceId}`
},
{
label: 'Input IoT Hub Device Connection String',
description: '',
detail: 'Input another...'
label: "Input IoT Hub Device Connection String",
description: "",
detail: "Input another..."
}
];
} else {
deviceConnectionStringSelection = [{
label: 'Input IoT Hub Device Connection String',
description: '',
detail: 'Input another...'
}];
deviceConnectionStringSelection = [
{
label: "Input IoT Hub Device Connection String",
description: "",
detail: "Input another..."
}
];
}
const selection =
await vscode.window.showQuickPick(deviceConnectionStringSelection, {
ignoreFocusOut: true,
placeHolder: 'Choose IoT Hub Device Connection String'
});
const selection = await vscode.window.showQuickPick(
deviceConnectionStringSelection,
{
ignoreFocusOut: true,
placeHolder: "Choose IoT Hub Device Connection String"
}
);
if (!selection) {
return false;
}
if (selection.detail === 'Input another...') {
if (selection.detail === "Input another...") {
const option: vscode.InputBoxOptions = {
value:
'HostName=<Host Name>;DeviceId=<Device Name>;SharedAccessKey=<Device Key>',
"HostName=<Host Name>;DeviceId=<Device Name>;SharedAccessKey=<Device Key>",
prompt: `Please input device connection string here.`,
ignoreFocusOut: true,
validateInput: (connectionString: string) => {
if (!connectionString) {
return 'Connection string cannot be empty.';
return "Connection string cannot be empty.";
} else {
return;
}
@ -312,11 +341,14 @@ export class IoTButtonDevice implements Device {
return false;
}
if ((deviceConnectionString.indexOf('HostName') === -1) ||
(deviceConnectionString.indexOf('DeviceId') === -1) ||
(deviceConnectionString.indexOf('SharedAccessKey') === -1)) {
if (
deviceConnectionString.indexOf("HostName") === -1 ||
deviceConnectionString.indexOf("DeviceId") === -1 ||
deviceConnectionString.indexOf("SharedAccessKey") === -1
) {
throw new Error(
'The format of the IoT Hub Device connection string is invalid. Please provide a valid Device connection string.');
"The format of the IoT Hub Device connection string is invalid. Please provide a valid Device connection string."
);
}
}
@ -327,13 +359,20 @@ export class IoTButtonDevice implements Device {
console.log(deviceConnectionString);
const iothubMatches = deviceConnectionString.match(/HostName=(.*?)(;|$)/);
const iotdevicenameMatches =
deviceConnectionString.match(/DeviceId=(.*?)(;|$)/);
const iotdevicesecretMatches =
deviceConnectionString.match(/SharedAccessKey=(.*?)(;|$)/);
if (!iothubMatches || !iothubMatches[1] || !iotdevicenameMatches ||
!iotdevicenameMatches[1] || !iotdevicesecretMatches ||
!iotdevicesecretMatches[1]) {
const iotdevicenameMatches = deviceConnectionString.match(
/DeviceId=(.*?)(;|$)/
);
const iotdevicesecretMatches = deviceConnectionString.match(
/SharedAccessKey=(.*?)(;|$)/
);
if (
!iothubMatches ||
!iothubMatches[1] ||
!iotdevicenameMatches ||
!iotdevicenameMatches[1] ||
!iotdevicesecretMatches ||
!iotdevicesecretMatches[1]
) {
return false;
}
@ -354,20 +393,22 @@ export class IoTButtonDevice implements Device {
const deviceFolderPath = this.deviceFolder;
if (!fs.existsSync(deviceFolderPath)) {
throw new Error('Unable to find the device folder inside the project.');
throw new Error("Unable to find the device folder inside the project.");
}
const userjsonFilePath =
path.join(deviceFolderPath, constants.userjsonFilename);
const userjsonFilePath = path.join(
deviceFolderPath,
constants.userjsonFilename
);
if (!fs.existsSync(userjsonFilePath)) {
throw new Error('The user json file does not exist.');
throw new Error("The user json file does not exist.");
}
let userjson = {};
try {
userjson = JSON.parse(fs.readFileSync(userjsonFilePath, 'utf8'));
userjson = JSON.parse(fs.readFileSync(userjsonFilePath, "utf8"));
} catch (error) {
userjson = {};
}
@ -383,12 +424,12 @@ export class IoTButtonDevice implements Device {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async configNtp(): Promise<any> {
const timeserver = await vscode.window.showInputBox({
value: 'pool.ntp.org',
value: "pool.ntp.org",
prompt: `Time Server`,
ignoreFocusOut: true,
validateInput: (timeserver: string) => {
if (!timeserver) {
return 'Time Server cannot be empty.';
return "Time Server cannot be empty.";
} else {
return;
}
@ -409,7 +450,7 @@ export class IoTButtonDevice implements Device {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async configSaveAndShutdown(): Promise<any> {
const data = { action: 'shutdown' };
const data = { action: "shutdown" };
const uri = constants.accessEndpoint;
const res = await this.setConfig(uri, data);
@ -417,8 +458,12 @@ export class IoTButtonDevice implements Device {
return res;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async configDeviceEnvironment(_deviceRootPath: string, _scaffoldType: ScaffoldType): Promise<void> {
async configDeviceEnvironment(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_deviceRootPath: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_scaffoldType: ScaffoldType
): Promise<void> {
// Do nothing.
}
}
}

Просмотреть файл

@ -1,40 +1,53 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as fs from 'fs-plus';
import * as path from 'path';
import * as vscode from 'vscode';
import * as fs from "fs-plus";
import * as path from "path";
import * as vscode from "vscode";
import { CancelOperationError } from '../CancelOperationError';
import { RemoteContainersCommands, VscodeCommands } from '../common/Commands';
import { ConfigKey, EventNames, FileNames, ScaffoldType } from '../constants';
import { FileUtility } from '../FileUtility';
import { TelemetryContext, TelemetryWorker } from '../telemetry';
import { getProjectConfig, updateProjectHostTypeConfig } from '../utils';
import { CancelOperationError } from "../CancelOperationError";
import { RemoteContainersCommands, VscodeCommands } from "../common/Commands";
import { ConfigKey, EventNames, FileNames, ScaffoldType } from "../constants";
import { FileUtility } from "../FileUtility";
import { TelemetryContext, TelemetryWorker } from "../telemetry";
import { getProjectConfig, updateProjectHostTypeConfig } from "../utils";
import { Component } from './Interfaces/Component';
import { ProjectHostType } from './Interfaces/ProjectHostType';
import { ProjectTemplateType, TemplateFileInfo } from './Interfaces/ProjectTemplate';
import { IoTWorkbenchProjectBase, OpenScenario } from './IoTWorkbenchProjectBase';
import { RemoteExtension } from './RemoteExtension';
import { Component } from "./Interfaces/Component";
import { ProjectHostType } from "./Interfaces/ProjectHostType";
import {
ProjectTemplateType,
TemplateFileInfo
} from "./Interfaces/ProjectTemplate";
import {
IoTWorkbenchProjectBase,
OpenScenario
} from "./IoTWorkbenchProjectBase";
import { RemoteExtension } from "./RemoteExtension";
const impor = require('impor')(__dirname);
const raspberryPiDeviceModule =
impor('./RaspberryPiDevice') as typeof import('./RaspberryPiDevice');
const impor = require("impor")(__dirname);
const raspberryPiDeviceModule = impor(
"./RaspberryPiDevice"
) as typeof import("./RaspberryPiDevice");
export class IoTContainerizedProject extends IoTWorkbenchProjectBase {
constructor(
context: vscode.ExtensionContext, channel: vscode.OutputChannel,
telemetryContext: TelemetryContext, rootFolderPath: string) {
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext,
rootFolderPath: string
) {
super(context, channel, telemetryContext);
this.projectHostType = ProjectHostType.Container;
if (!rootFolderPath) {
throw new Error(
`Fail to construct iot workspace project: root folder path is empty.`);
`Fail to construct iot workspace project: root folder path is empty.`
);
}
this.projectRootPath = rootFolderPath;
this.iotWorkbenchProjectFilePath =
path.join(this.projectRootPath, FileNames.iotWorkbenchProjectFileName);
this.iotWorkbenchProjectFilePath = path.join(
this.projectRootPath,
FileNames.iotWorkbenchProjectFileName
);
this.telemetryContext.properties.projectHostType = this.projectHostType;
}
@ -43,7 +56,10 @@ export class IoTContainerizedProject extends IoTWorkbenchProjectBase {
// 1. Update iot workbench project file.
await updateProjectHostTypeConfig(
scaffoldType, this.iotWorkbenchProjectFilePath, this.projectHostType);
scaffoldType,
this.iotWorkbenchProjectFilePath,
this.projectHostType
);
// 2. Send load project event telemetry only if the IoT project is loaded
// when VS Code opens.
@ -52,36 +68,54 @@ export class IoTContainerizedProject extends IoTWorkbenchProjectBase {
}
// 3. Init device
const projectConfigJson =
await getProjectConfig(scaffoldType, this.iotWorkbenchProjectFilePath);
const projectConfigJson = await getProjectConfig(
scaffoldType,
this.iotWorkbenchProjectFilePath
);
const boardId = projectConfigJson[`${ConfigKey.boardId}`];
if (!boardId) {
throw new Error(
`Internal Error: Fail to get board id from configuration.`);
`Internal Error: Fail to get board id from configuration.`
);
}
await this.initDevice(boardId, scaffoldType);
}
async create(templateFilesInfo: TemplateFileInfo[], _projectType: ProjectTemplateType, boardId: string, openInNewWindow: boolean): Promise<void> {
async create(
templateFilesInfo: TemplateFileInfo[],
_projectType: ProjectTemplateType,
boardId: string,
openInNewWindow: boolean
): Promise<void> {
// Can only create project locally
await RemoteExtension.checkRemoteExtension();
const createTimeScaffoldType = ScaffoldType.Local;
// Create project root path
if (!await FileUtility.directoryExists(
createTimeScaffoldType, this.projectRootPath)) {
if (
!(await FileUtility.directoryExists(
createTimeScaffoldType,
this.projectRootPath
))
) {
await FileUtility.mkdirRecursively(
createTimeScaffoldType, this.projectRootPath);
createTimeScaffoldType,
this.projectRootPath
);
}
// Update iot workbench project file
await updateProjectHostTypeConfig(
createTimeScaffoldType, this.iotWorkbenchProjectFilePath,
this.projectHostType);
createTimeScaffoldType,
this.iotWorkbenchProjectFilePath,
this.projectHostType
);
const projectConfig = await getProjectConfig(
createTimeScaffoldType, this.iotWorkbenchProjectFilePath);
createTimeScaffoldType,
this.iotWorkbenchProjectFilePath
);
// Step 1: Create device
await this.initDevice(boardId, createTimeScaffoldType, templateFilesInfo);
@ -90,18 +124,22 @@ export class IoTContainerizedProject extends IoTWorkbenchProjectBase {
// Update workspace config to workspace config file
if (!this.iotWorkbenchProjectFilePath) {
throw new Error(
`Workspace config file path is empty. Please initialize the project first.`);
`Workspace config file path is empty. Please initialize the project first.`
);
}
await FileUtility.writeJsonFile(
createTimeScaffoldType, this.iotWorkbenchProjectFilePath,
projectConfig);
createTimeScaffoldType,
this.iotWorkbenchProjectFilePath,
projectConfig
);
// Check components prerequisites
this.componentList.forEach(async item => {
const res = await item.checkPrerequisites();
if (!res) {
throw new Error(
`Failed to create component because prerequisite is not met.`);
`Failed to create component because prerequisite is not met.`
);
}
});
@ -117,7 +155,10 @@ export class IoTContainerizedProject extends IoTWorkbenchProjectBase {
// Open project
await this.openProject(
createTimeScaffoldType, openInNewWindow, OpenScenario.createNewProject);
createTimeScaffoldType,
openInNewWindow,
OpenScenario.createNewProject
);
}
/**
@ -125,25 +166,28 @@ export class IoTContainerizedProject extends IoTWorkbenchProjectBase {
* If yes, open project in container. If not, stay local.
*/
async openProject(
scaffoldType: ScaffoldType, openInNewWindow: boolean,
openScenario: OpenScenario): Promise<void> {
scaffoldType: ScaffoldType,
openInNewWindow: boolean,
openScenario: OpenScenario
): Promise<void> {
this.validateProjectRootPath(scaffoldType);
// 1. Ask to customize
let openInContainer = false;
openInContainer = await this.askToOpenInContainer();
this.telemetryContext.properties.openInContainer =
openInContainer.toString();
this.telemetryContext.properties.openInContainer = openInContainer.toString();
// Send all telemetry data before restart the current window.
if (!openInNewWindow || openInContainer) {
try {
const telemetryWorker =
TelemetryWorker.getInstance(this.extensionContext);
const eventNames = openScenario === OpenScenario.createNewProject ?
EventNames.createNewProjectEvent :
EventNames.configProjectEnvironmentEvent;
const telemetryWorker = TelemetryWorker.getInstance(
this.extensionContext
);
const eventNames =
openScenario === OpenScenario.createNewProject
? EventNames.createNewProjectEvent
: EventNames.configProjectEnvironmentEvent;
telemetryWorker.sendEvent(eventNames, this.telemetryContext);
} catch {
// If sending telemetry failed, skip the error to avoid blocking user.
@ -156,21 +200,26 @@ export class IoTContainerizedProject extends IoTWorkbenchProjectBase {
} else {
await vscode.commands.executeCommand(
VscodeCommands.VscodeOpenFolder,
vscode.Uri.file(this.projectRootPath), openInNewWindow);
vscode.Uri.file(this.projectRootPath),
openInNewWindow
);
// TODO: open install_packages.sh bash script.
}
}
private async openFolderInContainer(folderPath: string): Promise<void> {
if (!await FileUtility.directoryExists(ScaffoldType.Local, folderPath)) {
if (!(await FileUtility.directoryExists(ScaffoldType.Local, folderPath))) {
throw new Error(
`Fail to open folder in container: ${folderPath} does not exist.`);
`Fail to open folder in container: ${folderPath} does not exist.`
);
}
await RemoteExtension.checkRemoteExtension();
await vscode.commands.executeCommand(
RemoteContainersCommands.OpenFolder, vscode.Uri.file(folderPath));
RemoteContainersCommands.OpenFolder,
vscode.Uri.file(folderPath)
);
}
/**
@ -181,15 +230,21 @@ export class IoTContainerizedProject extends IoTWorkbenchProjectBase {
* @param templateFilesInfo template files info to scaffold files for device
*/
private async initDevice(
boardId: string, scaffoldType: ScaffoldType,
templateFilesInfo?: TemplateFileInfo[]): Promise<void> {
boardId: string,
scaffoldType: ScaffoldType,
templateFilesInfo?: TemplateFileInfo[]
): Promise<void> {
this.validateProjectRootPath(scaffoldType);
let device: Component;
if (boardId === raspberryPiDeviceModule.RaspberryPiDevice.boardId) {
device = new raspberryPiDeviceModule.RaspberryPiDevice(
this.extensionContext, this.projectRootPath, this.channel,
this.telemetryContext, templateFilesInfo);
this.extensionContext,
this.projectRootPath,
this.channel,
this.telemetryContext,
templateFilesInfo
);
} else {
throw new Error(`The board ${boardId} is not supported.`);
}
@ -209,24 +264,28 @@ export class IoTContainerizedProject extends IoTWorkbenchProjectBase {
openInContainerOption.push(
{
label: `Yes`,
detail: 'I want to work on this project in container now.'
detail: "I want to work on this project in container now."
},
{
label: `No`,
detail: 'I need to customize my container first locally.'
});
detail: "I need to customize my container first locally."
}
);
const openInContainerSelection =
await vscode.window.showQuickPick(openInContainerOption, {
ignoreFocusOut: true,
placeHolder: `Do you want to open in container?`
});
const openInContainerSelection = await vscode.window.showQuickPick(
openInContainerOption,
{
ignoreFocusOut: true,
placeHolder: `Do you want to open in container?`
}
);
if (!openInContainerSelection) {
throw new CancelOperationError(
`Ask to customize development environment selection cancelled.`);
`Ask to customize development environment selection cancelled.`
);
}
return openInContainerSelection.label === 'Yes';
return openInContainerSelection.label === "Yes";
}
}
}

Просмотреть файл

@ -1,21 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as fs from 'fs-plus';
import { Guid } from 'guid-typescript';
import * as path from 'path';
import * as vscode from 'vscode';
import * as fs from "fs-plus";
import { Guid } from "guid-typescript";
import * as path from "path";
import * as vscode from "vscode";
import { ConfigHandler } from '../configHandler';
import { AzureComponentsStorage, ConfigKey, ScaffoldType } from '../constants';
import { channelPrintJsonObject, channelShowAndAppendLine } from '../utils';
import { ConfigHandler } from "../configHandler";
import { AzureComponentsStorage, ConfigKey, ScaffoldType } from "../constants";
import { channelPrintJsonObject, channelShowAndAppendLine } from "../utils";
import { getExtension } from './Apis';
import { AzureComponentConfig, AzureConfigFileHandler, AzureConfigs, ComponentInfo, DependencyConfig } from './AzureComponentConfig';
import { AzureUtility } from './AzureUtility';
import { ExtensionName } from './Interfaces/Api';
import { Component, ComponentType } from './Interfaces/Component';
import { Provisionable } from './Interfaces/Provisionable';
import { getExtension } from "./Apis";
import {
AzureComponentConfig,
AzureConfigFileHandler,
AzureConfigs,
ComponentInfo,
DependencyConfig
} from "./AzureComponentConfig";
import { AzureUtility } from "./AzureUtility";
import { ExtensionName } from "./Interfaces/Api";
import { Component, ComponentType } from "./Interfaces/Component";
import { Provisionable } from "./Interfaces/Provisionable";
export class IoTHub implements Component, Provisionable {
dependencies: DependencyConfig[] = [];
@ -36,7 +42,7 @@ export class IoTHub implements Component, Provisionable {
this.azureConfigFileHandler = new AzureConfigFileHandler(projectRoot);
}
name = 'IoT Hub';
name = "IoT Hub";
getComponentType(): ComponentType {
return this.componentType;
@ -48,8 +54,10 @@ export class IoTHub implements Component, Provisionable {
async load(): Promise<boolean> {
const azureConfigFilePath = path.join(
this.projectRootPath, AzureComponentsStorage.folderName,
AzureComponentsStorage.fileName);
this.projectRootPath,
AzureComponentsStorage.folderName,
AzureComponentsStorage.fileName
);
if (!fs.existsSync(azureConfigFilePath)) {
return false;
@ -58,9 +66,10 @@ export class IoTHub implements Component, Provisionable {
let azureConfigs: AzureConfigs;
try {
azureConfigs = JSON.parse(fs.readFileSync(azureConfigFilePath, 'utf8'));
azureConfigs = JSON.parse(fs.readFileSync(azureConfigFilePath, "utf8"));
const iotHubConfig = azureConfigs.componentConfigs.find(
config => config.type === this.componentType);
config => config.type === this.componentType
);
if (iotHubConfig) {
this.componentId = iotHubConfig.id;
this.dependencies = iotHubConfig.dependencies;
@ -72,7 +81,6 @@ export class IoTHub implements Component, Provisionable {
return true;
}
async create(): Promise<void> {
await this.updateConfigSettings(ScaffoldType.Local);
}
@ -80,19 +88,20 @@ export class IoTHub implements Component, Provisionable {
async provision(): Promise<boolean> {
const provisionIothubSelection: vscode.QuickPickItem[] = [
{
label: 'Select an existing IoT Hub',
description: 'Select an existing IoT Hub',
detail: 'select'
label: "Select an existing IoT Hub",
description: "Select an existing IoT Hub",
detail: "select"
},
{
label: 'Create a new IoT Hub',
description: 'Create a new IoT Hub',
detail: 'create'
label: "Create a new IoT Hub",
description: "Create a new IoT Hub",
detail: "create"
}
];
const selection = await vscode.window.showQuickPick(
provisionIothubSelection,
{ ignoreFocusOut: true, placeHolder: 'Provision IoT Hub' });
{ ignoreFocusOut: true, placeHolder: "Provision IoT Hub" }
);
if (!selection) {
return false;
@ -101,7 +110,8 @@ export class IoTHub implements Component, Provisionable {
const toolkit = getExtension(ExtensionName.Toolkit);
if (!toolkit) {
throw new Error(
'Azure IoT Hub Toolkit is not installed. Please install it from Marketplace.');
"Azure IoT Hub Toolkit is not installed. Please install it from Marketplace."
);
}
let iothub = null;
@ -109,20 +119,25 @@ export class IoTHub implements Component, Provisionable {
const resourceGroup = AzureUtility.resourceGroup;
switch (selection.detail) {
case 'select':
iothub = await toolkit.azureIoTExplorer.selectIoTHub(
this.channel, subscriptionId);
break;
case 'create':
if (this.channel) {
channelShowAndAppendLine(this.channel, 'Creating new IoT Hub...');
}
case "select":
iothub = await toolkit.azureIoTExplorer.selectIoTHub(
this.channel,
subscriptionId
);
break;
case "create":
if (this.channel) {
channelShowAndAppendLine(this.channel, "Creating new IoT Hub...");
}
iothub = await toolkit.azureIoTExplorer.createIoTHub(
this.channel, subscriptionId, resourceGroup);
break;
default:
break;
iothub = await toolkit.azureIoTExplorer.createIoTHub(
this.channel,
subscriptionId,
resourceGroup
);
break;
default:
break;
}
if (iothub && iothub.iotHubConnectionString) {
@ -130,28 +145,34 @@ export class IoTHub implements Component, Provisionable {
channelPrintJsonObject(this.channel, iothub);
}
const sharedAccessKeyMatches =
iothub.iotHubConnectionString.match(/SharedAccessKey=([^;]*)/);
const sharedAccessKeyMatches = iothub.iotHubConnectionString.match(
/SharedAccessKey=([^;]*)/
);
if (!sharedAccessKeyMatches || sharedAccessKeyMatches.length < 2) {
throw new Error(
'Cannot parse shared access key from IoT Hub connection string. Please retry Azure Provision.');
"Cannot parse shared access key from IoT Hub connection string. Please retry Azure Provision."
);
}
const sharedAccessKey = sharedAccessKeyMatches[1];
const eventHubConnectionString = `Endpoint=${
iothub.properties.eventHubEndpoints.events
.endpoint};SharedAccessKeyName=iothubowner;SharedAccessKey=${
sharedAccessKey}`;
const eventHubConnectionString = `Endpoint=${iothub.properties.eventHubEndpoints.events.endpoint};\
SharedAccessKeyName=iothubowner;SharedAccessKey=${sharedAccessKey}`;
const eventHubConnectionPath =
iothub.properties.eventHubEndpoints.events.path;
iothub.properties.eventHubEndpoints.events.path;
await ConfigHandler.update(
ConfigKey.iotHubConnectionString, iothub.iotHubConnectionString);
ConfigKey.iotHubConnectionString,
iothub.iotHubConnectionString
);
await ConfigHandler.update(
ConfigKey.eventHubConnectionString, eventHubConnectionString);
ConfigKey.eventHubConnectionString,
eventHubConnectionString
);
await ConfigHandler.update(
ConfigKey.eventHubConnectionPath, eventHubConnectionPath);
ConfigKey.eventHubConnectionPath,
eventHubConnectionPath
);
const scaffoldType = ScaffoldType.Workspace;
await this.updateConfigSettings(scaffoldType, {
@ -163,33 +184,41 @@ export class IoTHub implements Component, Provisionable {
});
if (this.channel) {
channelShowAndAppendLine(this.channel, 'IoT Hub provision succeeded.');
channelShowAndAppendLine(this.channel, "IoT Hub provision succeeded.");
}
return true;
} else if (!iothub) {
return false;
} else {
throw new Error(
'IoT Hub provision failed. Please check output window for detail.');
"IoT Hub provision failed. Please check output window for detail."
);
}
}
async updateConfigSettings(type: ScaffoldType, componentInfo?: ComponentInfo):
Promise<void> {
const iotHubComponentIndex =
await this.azureConfigFileHandler.getComponentIndexById(type, this.id);
async updateConfigSettings(
type: ScaffoldType,
componentInfo?: ComponentInfo
): Promise<void> {
const iotHubComponentIndex = await this.azureConfigFileHandler.getComponentIndexById(
type,
this.id
);
if (iotHubComponentIndex > -1) {
if (!componentInfo) {
return;
}
await this.azureConfigFileHandler.updateComponent(
type, iotHubComponentIndex, componentInfo);
type,
iotHubComponentIndex,
componentInfo
);
} else {
const newIoTHubConfig: AzureComponentConfig = {
id: this.id,
folder: '',
name: '',
folder: "",
name: "",
dependencies: [],
type: this.componentType,
componentInfo
@ -197,4 +226,4 @@ export class IoTHub implements Component, Provisionable {
await this.azureConfigFileHandler.appendComponent(type, newIoTHubConfig);
}
}
}
}

Просмотреть файл

@ -1,24 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as iothub from 'azure-iothub';
import { Guid } from 'guid-typescript';
import * as vscode from 'vscode';
import * as iothub from "azure-iothub";
import { Guid } from "guid-typescript";
import * as vscode from "vscode";
import { ConfigHandler } from '../configHandler';
import { ConfigKey, ScaffoldType } from '../constants';
import { ConfigHandler } from "../configHandler";
import { ConfigKey, ScaffoldType } from "../constants";
import { getExtension } from './Apis';
import { ComponentInfo, DependencyConfig } from './AzureComponentConfig';
import { ExtensionName } from './Interfaces/Api';
import { Component, ComponentType } from './Interfaces/Component';
import { Provisionable } from './Interfaces/Provisionable';
import { getExtension } from "./Apis";
import { ComponentInfo, DependencyConfig } from "./AzureComponentConfig";
import { ExtensionName } from "./Interfaces/Api";
import { Component, ComponentType } from "./Interfaces/Component";
import { Provisionable } from "./Interfaces/Provisionable";
async function getDeviceNumber(iotHubConnectionString: string): Promise<number> {
async function getDeviceNumber(
iotHubConnectionString: string
): Promise<number> {
return new Promise(
(resolve: (value: number) => void, reject: (error: Error) => void) => {
const registry: iothub.Registry =
iothub.Registry.fromConnectionString(iotHubConnectionString);
const registry: iothub.Registry = iothub.Registry.fromConnectionString(
iotHubConnectionString
);
registry.list((err, list) => {
if (err) {
return reject(err);
@ -29,32 +32,37 @@ async function getDeviceNumber(iotHubConnectionString: string): Promise<number>
return resolve(list.length);
}
});
});
}
);
}
async function getProvisionIothubDeviceSelection(iotHubConnectionString: string): Promise<vscode.QuickPickItem[]> {
async function getProvisionIothubDeviceSelection(
iotHubConnectionString: string
): Promise<vscode.QuickPickItem[]> {
let provisionIothubDeviceSelection: vscode.QuickPickItem[];
const deviceNumber = await getDeviceNumber(iotHubConnectionString);
if (deviceNumber > 0) {
provisionIothubDeviceSelection = [
{
label: 'Select an existing IoT Hub device',
description: 'Select an existing IoT Hub device',
detail: 'select'
label: "Select an existing IoT Hub device",
description: "Select an existing IoT Hub device",
detail: "select"
},
{
label: 'Create a new IoT Hub device',
description: 'Create a new IoT Hub device',
detail: 'create'
label: "Create a new IoT Hub device",
description: "Create a new IoT Hub device",
detail: "create"
}
];
} else {
provisionIothubDeviceSelection = [{
label: 'Create a new IoT Hub device',
description: 'Create a new IoT Hub device',
detail: 'create'
}];
provisionIothubDeviceSelection = [
{
label: "Create a new IoT Hub device",
description: "Create a new IoT Hub device",
detail: "create"
}
];
}
return provisionIothubDeviceSelection;
}
@ -75,7 +83,7 @@ export class IoTHubDevice implements Component, Provisionable {
this.componentId = Guid.create().toString();
}
name = 'IoT Hub Device';
name = "IoT Hub Device";
getComponentType(): ComponentType {
return this.componentType;
@ -94,16 +102,19 @@ export class IoTHubDevice implements Component, Provisionable {
}
async provision(): Promise<boolean> {
const iotHubConnectionString =
ConfigHandler.get<string>(ConfigKey.iotHubConnectionString);
const iotHubConnectionString = ConfigHandler.get<string>(
ConfigKey.iotHubConnectionString
);
if (!iotHubConnectionString) {
throw new Error(
'Unable to find IoT Hub connection in the project. Please retry Azure Provision.');
"Unable to find IoT Hub connection in the project. Please retry Azure Provision."
);
}
const selection = await vscode.window.showQuickPick(
getProvisionIothubDeviceSelection(iotHubConnectionString),
{ ignoreFocusOut: true, placeHolder: 'Provision IoTHub Device' });
{ ignoreFocusOut: true, placeHolder: "Provision IoTHub Device" }
);
if (!selection) {
return false;
@ -112,40 +123,55 @@ export class IoTHubDevice implements Component, Provisionable {
const toolkit = getExtension(ExtensionName.Toolkit);
if (!toolkit) {
throw new Error(
'Azure IoT Hub Toolkit is not installed. Please install it from Marketplace.');
"Azure IoT Hub Toolkit is not installed. Please install it from Marketplace."
);
}
let device = null;
switch (selection.detail) {
case 'select':
device = await toolkit.azureIoTExplorer.getDevice(
null, iotHubConnectionString, this.channel);
if (!device) {
return false;
} else {
await ConfigHandler.update(
ConfigKey.iotHubDeviceConnectionString, device.connectionString);
}
break;
case "select":
device = await toolkit.azureIoTExplorer.getDevice(
null,
iotHubConnectionString,
this.channel
);
if (!device) {
return false;
} else {
await ConfigHandler.update(
ConfigKey.iotHubDeviceConnectionString,
device.connectionString
);
}
break;
case 'create':
device = await toolkit.azureIoTExplorer.createDevice(
false, iotHubConnectionString, this.channel);
if (!device) {
return false;
} else {
await ConfigHandler.update(
ConfigKey.iotHubDeviceConnectionString, device.connectionString);
}
break;
default:
break;
case "create":
device = await toolkit.azureIoTExplorer.createDevice(
false,
iotHubConnectionString,
this.channel
);
if (!device) {
return false;
} else {
await ConfigHandler.update(
ConfigKey.iotHubDeviceConnectionString,
device.connectionString
);
}
break;
default:
break;
}
return true;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
updateConfigSettings(_type: ScaffoldType, _componentInfo?: ComponentInfo): void {
updateConfigSettings(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_type: ScaffoldType,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_componentInfo?: ComponentInfo
): void {
// Do nothing.
}
}

Просмотреть файл

@ -1,28 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as path from 'path';
import * as vscode from 'vscode';
import * as path from "path";
import * as vscode from "vscode";
import { CancelOperationError } from '../CancelOperationError';
import { ConfigKey, EventNames, FileNames, ScaffoldType } from '../constants';
import { FileUtility } from '../FileUtility';
import { TelemetryContext, TelemetryWorker } from '../telemetry';
import * as utils from '../utils';
import { CancelOperationError } from "../CancelOperationError";
import { ConfigKey, EventNames, FileNames, ScaffoldType } from "../constants";
import { FileUtility } from "../FileUtility";
import { TelemetryContext, TelemetryWorker } from "../telemetry";
import * as utils from "../utils";
import { checkAzureLogin } from './Apis';
import { Compilable } from './Interfaces/Compilable';
import { Component, ComponentType } from './Interfaces/Component';
import { Deployable } from './Interfaces/Deployable';
import { Device } from './Interfaces/Device';
import { ProjectHostType } from './Interfaces/ProjectHostType';
import { ProjectTemplateType, TemplateFileInfo } from './Interfaces/ProjectTemplate';
import { Provisionable } from './Interfaces/Provisionable';
import { Uploadable } from './Interfaces/Uploadable';
import { checkAzureLogin } from "./Apis";
import { Compilable } from "./Interfaces/Compilable";
import { Component, ComponentType } from "./Interfaces/Component";
import { Deployable } from "./Interfaces/Deployable";
import { Device } from "./Interfaces/Device";
import { ProjectHostType } from "./Interfaces/ProjectHostType";
import {
ProjectTemplateType,
TemplateFileInfo
} from "./Interfaces/ProjectTemplate";
import { Provisionable } from "./Interfaces/Provisionable";
import { Uploadable } from "./Interfaces/Uploadable";
const impor = require('impor')(__dirname);
const azureUtilityModule =
impor('./AzureUtility') as typeof import('./AzureUtility');
const impor = require("impor")(__dirname);
const azureUtilityModule = impor(
"./AzureUtility"
) as typeof import("./AzureUtility");
export enum OpenScenario {
createNewProject,
@ -33,8 +37,8 @@ export abstract class IoTWorkbenchProjectBase {
protected channel: vscode.OutputChannel;
protected telemetryContext: TelemetryContext;
protected projectRootPath = '';
protected iotWorkbenchProjectFilePath = '';
protected projectRootPath = "";
protected iotWorkbenchProjectFilePath = "";
protected componentList: Component[];
protected projectHostType: ProjectHostType = ProjectHostType.Unknown;
@ -46,34 +50,47 @@ export abstract class IoTWorkbenchProjectBase {
*/
static async getProjectType(
scaffoldType: ScaffoldType,
projectFileRootPath: string|undefined): Promise<ProjectHostType> {
projectFileRootPath: string | undefined
): Promise<ProjectHostType> {
if (!projectFileRootPath) {
return ProjectHostType.Unknown;
}
const iotWorkbenchProjectFile =
path.join(projectFileRootPath, FileNames.iotWorkbenchProjectFileName);
if (!await FileUtility.fileExists(scaffoldType, iotWorkbenchProjectFile)) {
const iotWorkbenchProjectFile = path.join(
projectFileRootPath,
FileNames.iotWorkbenchProjectFileName
);
if (
!(await FileUtility.fileExists(scaffoldType, iotWorkbenchProjectFile))
) {
return ProjectHostType.Unknown;
}
const iotWorkbenchProjectFileString =
(await FileUtility.readFile(
scaffoldType, iotWorkbenchProjectFile, 'utf8') as string)
.trim();
const iotWorkbenchProjectFileString = ((await FileUtility.readFile(
scaffoldType,
iotWorkbenchProjectFile,
"utf8"
)) as string).trim();
if (iotWorkbenchProjectFileString) {
const projectConfig = JSON.parse(iotWorkbenchProjectFileString);
if (projectConfig &&
projectConfig[`${ConfigKey.projectHostType}`] !== undefined) {
if (
projectConfig &&
projectConfig[`${ConfigKey.projectHostType}`] !== undefined
) {
const projectHostType: ProjectHostType = utils.getEnumKeyByEnumValue(
ProjectHostType, projectConfig[`${ConfigKey.projectHostType}`]);
ProjectHostType,
projectConfig[`${ConfigKey.projectHostType}`]
);
return projectHostType;
}
}
// TODO: For backward compatibility, will remove later
const devcontainerFolderPath =
path.join(projectFileRootPath, FileNames.devcontainerFolderName);
if (await FileUtility.directoryExists(
scaffoldType, devcontainerFolderPath)) {
const devcontainerFolderPath = path.join(
projectFileRootPath,
FileNames.devcontainerFolderName
);
if (
await FileUtility.directoryExists(scaffoldType, devcontainerFolderPath)
) {
return ProjectHostType.Container;
} else {
return ProjectHostType.Workspace;
@ -97,20 +114,27 @@ export abstract class IoTWorkbenchProjectBase {
}
constructor(
context: vscode.ExtensionContext, channel: vscode.OutputChannel,
telemetryContext: TelemetryContext) {
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext
) {
this.componentList = [];
this.extensionContext = context;
this.channel = channel;
this.telemetryContext = telemetryContext;
}
abstract async load(scaffoldType: ScaffoldType, initLoad?: boolean):
Promise<void>;
abstract async load(
scaffoldType: ScaffoldType,
initLoad?: boolean
): Promise<void>;
abstract async create(
templateFilesInfo: TemplateFileInfo[], projectType: ProjectTemplateType,
boardId: string, openInNewWindow: boolean): Promise<void>;
templateFilesInfo: TemplateFileInfo[],
projectType: ProjectTemplateType,
boardId: string,
openInNewWindow: boolean
): Promise<void>;
async compile(): Promise<boolean> {
for (const item of this.componentList) {
@ -123,7 +147,8 @@ export abstract class IoTWorkbenchProjectBase {
const res = await item.compile();
if (!res) {
vscode.window.showErrorMessage(
'Unable to compile the device code, please check output window for detail.');
"Unable to compile the device code, please check output window for detail."
);
}
}
}
@ -141,7 +166,8 @@ export abstract class IoTWorkbenchProjectBase {
const res = await item.upload();
if (!res) {
vscode.window.showErrorMessage(
'Unable to upload the sketch, please check output window for detail.');
"Unable to upload the sketch, please check output window for detail."
);
}
}
}
@ -164,13 +190,14 @@ export abstract class IoTWorkbenchProjectBase {
if (provisionItemList.length === 0) {
// nothing to provision:
vscode.window.showInformationMessage(
'Congratulations! There is no Azure service to provision in this project.');
"Congratulations! There is no Azure service to provision in this project."
);
return false;
}
// Ensure azure login before component provision
let subscriptionId: string|undefined = '';
let resourceGroup: string|undefined = '';
let subscriptionId: string | undefined = "";
let resourceGroup: string | undefined = "";
if (provisionItemList.length > 0) {
await checkAzureLogin();
azureUtilityModule.AzureUtility.init(this.extensionContext, this.channel);
@ -194,12 +221,15 @@ export abstract class IoTWorkbenchProjectBase {
}
}
const selection = await vscode.window.showQuickPick(
[{
label: _provisionItemList.join(' - '),
description: '',
detail: 'Click to continue'
}],
{ ignoreFocusOut: true, placeHolder: 'Provision process' });
[
{
label: _provisionItemList.join(" - "),
description: "",
detail: "Click to continue"
}
],
{ ignoreFocusOut: true, placeHolder: "Provision process" }
);
if (!selection) {
return false;
@ -207,7 +237,7 @@ export abstract class IoTWorkbenchProjectBase {
const res = await item.provision();
if (!res) {
throw new CancelOperationError('Provision cancelled.');
throw new CancelOperationError("Provision cancelled.");
}
}
}
@ -231,7 +261,8 @@ export abstract class IoTWorkbenchProjectBase {
if (deployItemList && deployItemList.length <= 0) {
await vscode.window.showInformationMessage(
'Congratulations! The project does not contain any Azure components to be deployed.');
"Congratulations! The project does not contain any Azure components to be deployed."
);
return;
}
@ -250,12 +281,15 @@ export abstract class IoTWorkbenchProjectBase {
}
}
const selection = await vscode.window.showQuickPick(
[{
label: _deployItemList.join(' - '),
description: '',
detail: 'Click to continue'
}],
{ ignoreFocusOut: true, placeHolder: 'Deploy process' });
[
{
label: _deployItemList.join(" - "),
description: "",
detail: "Click to continue"
}
],
{ ignoreFocusOut: true, placeHolder: "Deploy process" }
);
if (!selection) {
throw new CancelOperationError(`Component deployment cancelled.`);
@ -268,7 +302,7 @@ export abstract class IoTWorkbenchProjectBase {
}
}
vscode.window.showInformationMessage('Azure deploy succeeded.');
vscode.window.showInformationMessage("Azure deploy succeeded.");
}
/**
@ -276,7 +310,9 @@ export abstract class IoTWorkbenchProjectBase {
* template files.
*/
async configureProjectEnvironmentCore(
deviceRootPath: string, scaffoldType: ScaffoldType): Promise<void> {
deviceRootPath: string,
scaffoldType: ScaffoldType
): Promise<void> {
for (const component of this.componentList) {
if (component.getComponentType() === ComponentType.Device) {
const device = component as Device;
@ -286,8 +322,10 @@ export abstract class IoTWorkbenchProjectBase {
}
abstract async openProject(
scaffoldType: ScaffoldType, openInNewWindow: boolean,
openScenario: OpenScenario): Promise<void>;
scaffoldType: ScaffoldType,
openInNewWindow: boolean,
openScenario: OpenScenario
): Promise<void>;
async configDeviceSettings(): Promise<boolean> {
for (const component of this.componentList) {
@ -306,7 +344,9 @@ export abstract class IoTWorkbenchProjectBase {
const telemetryWorker = TelemetryWorker.getInstance(context);
try {
telemetryWorker.sendEvent(
EventNames.projectLoadEvent, this.telemetryContext);
EventNames.projectLoadEvent,
this.telemetryContext
);
} catch {
// If sending telemetry failed, skip the error to avoid blocking user.
}
@ -317,10 +357,12 @@ export abstract class IoTWorkbenchProjectBase {
* @param scaffoldType scaffold type
*/
async validateProjectRootPath(scaffoldType: ScaffoldType): Promise<void> {
if (!await FileUtility.directoryExists(
scaffoldType, this.projectRootPath)) {
throw new Error(`Project root path ${
this.projectRootPath} does not exist. Please initialize the project first.`);
if (
!(await FileUtility.directoryExists(scaffoldType, this.projectRootPath))
) {
throw new Error(
`Project root path ${this.projectRootPath} does not exist. Please initialize the project first.`
);
}
}
}

Просмотреть файл

@ -1,60 +1,77 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as fs from 'fs-plus';
import * as path from 'path';
import * as vscode from 'vscode';
import * as fs from "fs-plus";
import * as path from "path";
import * as vscode from "vscode";
import { IoTCubeCommands } from '../common/Commands';
import { ConfigHandler } from '../configHandler';
import { ConfigKey, EventNames, FileNames, ScaffoldType } from '../constants';
import { FileUtility } from '../FileUtility';
import { TelemetryContext, TelemetryWorker } from '../telemetry';
import { getWorkspaceFile, updateProjectHostTypeConfig } from '../utils';
import { IoTCubeCommands } from "../common/Commands";
import { ConfigHandler } from "../configHandler";
import { ConfigKey, EventNames, FileNames, ScaffoldType } from "../constants";
import { FileUtility } from "../FileUtility";
import { TelemetryContext, TelemetryWorker } from "../telemetry";
import { getWorkspaceFile, updateProjectHostTypeConfig } from "../utils";
import { AzureComponentConfig, Dependency } from './AzureComponentConfig';
import { Component, ComponentType } from './Interfaces/Component';
import { ProjectHostType } from './Interfaces/ProjectHostType';
import { ProjectTemplateType, TemplateFileInfo } from './Interfaces/ProjectTemplate';
import { Workspace } from './Interfaces/Workspace';
import { IoTWorkbenchProjectBase, OpenScenario } from './IoTWorkbenchProjectBase';
import { AzureComponentConfig, Dependency } from "./AzureComponentConfig";
import { Component, ComponentType } from "./Interfaces/Component";
import { ProjectHostType } from "./Interfaces/ProjectHostType";
import {
ProjectTemplateType,
TemplateFileInfo
} from "./Interfaces/ProjectTemplate";
import { Workspace } from "./Interfaces/Workspace";
import {
IoTWorkbenchProjectBase,
OpenScenario
} from "./IoTWorkbenchProjectBase";
const impor = require('impor')(__dirname);
const az3166DeviceModule =
impor('./AZ3166Device') as typeof import('./AZ3166Device');
const azureComponentConfigModule =
impor('./AzureComponentConfig') as typeof import('./AzureComponentConfig');
const azureFunctionsModule =
impor('./AzureFunctions') as typeof import('./AzureFunctions');
const cosmosDBModule = impor('./CosmosDB') as typeof import('./CosmosDB');
const esp32DeviceModule =
impor('./Esp32Device') as typeof import('./Esp32Device');
const ioTButtonDeviceModule =
impor('./IoTButtonDevice') as typeof import('./IoTButtonDevice');
const ioTHubModule = impor('./IoTHub') as typeof import('./IoTHub');
const ioTHubDeviceModule =
impor('./IoTHubDevice') as typeof import('./IoTHubDevice');
const streamAnalyticsJobModule =
impor('./StreamAnalyticsJob') as typeof import('./StreamAnalyticsJob');
const impor = require("impor")(__dirname);
const az3166DeviceModule = impor(
"./AZ3166Device"
) as typeof import("./AZ3166Device");
const azureComponentConfigModule = impor(
"./AzureComponentConfig"
) as typeof import("./AzureComponentConfig");
const azureFunctionsModule = impor(
"./AzureFunctions"
) as typeof import("./AzureFunctions");
const cosmosDBModule = impor("./CosmosDB") as typeof import("./CosmosDB");
const esp32DeviceModule = impor(
"./Esp32Device"
) as typeof import("./Esp32Device");
const ioTButtonDeviceModule = impor(
"./IoTButtonDevice"
) as typeof import("./IoTButtonDevice");
const ioTHubModule = impor("./IoTHub") as typeof import("./IoTHub");
const ioTHubDeviceModule = impor(
"./IoTHubDevice"
) as typeof import("./IoTHubDevice");
const streamAnalyticsJobModule = impor(
"./StreamAnalyticsJob"
) as typeof import("./StreamAnalyticsJob");
const folderName = {
deviceDefaultFolderName: 'Device',
functionDefaultFolderName: 'Functions',
asaFolderName: 'StreamAnalytics'
deviceDefaultFolderName: "Device",
functionDefaultFolderName: "Functions",
asaFolderName: "StreamAnalytics"
};
export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
private deviceRootPath = '';
private workspaceConfigFilePath = '';
private deviceRootPath = "";
private workspaceConfigFilePath = "";
constructor(
context: vscode.ExtensionContext, channel: vscode.OutputChannel,
telemetryContext: TelemetryContext, rootFolderPath: string) {
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext,
rootFolderPath: string
) {
super(context, channel, telemetryContext);
this.projectHostType = ProjectHostType.Workspace;
if (!rootFolderPath) {
throw new Error(
`Fail to construct iot workspace project: root folder path is empty.`);
`Fail to construct iot workspace project: root folder path is empty.`
);
}
this.projectRootPath = rootFolderPath;
this.telemetryContext.properties.projectHostType = this.projectHostType;
@ -67,19 +84,28 @@ export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
const devicePath = ConfigHandler.get<string>(ConfigKey.devicePath);
if (!devicePath) {
throw new Error(
`Internal Error: Fail to get device path from configuration.`);
`Internal Error: Fail to get device path from configuration.`
);
}
this.deviceRootPath = path.join(this.projectRootPath, devicePath);
if (!await FileUtility.directoryExists(scaffoldType, this.deviceRootPath)) {
if (
!(await FileUtility.directoryExists(scaffoldType, this.deviceRootPath))
) {
throw new Error(
`Device root path ${this.deviceRootPath} does not exist.`);
`Device root path ${this.deviceRootPath} does not exist.`
);
}
// Init and update iot workbench project file
this.iotWorkbenchProjectFilePath =
path.join(this.deviceRootPath, FileNames.iotWorkbenchProjectFileName);
this.iotWorkbenchProjectFilePath = path.join(
this.deviceRootPath,
FileNames.iotWorkbenchProjectFileName
);
await updateProjectHostTypeConfig(
scaffoldType, this.iotWorkbenchProjectFilePath, this.projectHostType);
scaffoldType,
this.iotWorkbenchProjectFilePath,
this.projectHostType
);
// Init workspace config file
this.loadAndInitWorkspaceConfigFilePath(scaffoldType);
@ -93,7 +119,8 @@ export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
const boardId = ConfigHandler.get<string>(ConfigKey.boardId);
if (!boardId) {
throw new Error(
`Internal Error: Fail to get board id from configuration.`);
`Internal Error: Fail to get board id from configuration.`
);
}
await this.initDevice(boardId, scaffoldType);
@ -106,31 +133,48 @@ export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
}
async create(
templateFilesInfo: TemplateFileInfo[], projectType: ProjectTemplateType,
boardId: string, openInNewWindow: boolean): Promise<void> {
templateFilesInfo: TemplateFileInfo[],
projectType: ProjectTemplateType,
boardId: string,
openInNewWindow: boolean
): Promise<void> {
const createTimeScaffoldType = ScaffoldType.Local;
// Init device root path
this.deviceRootPath =
path.join(this.projectRootPath, folderName.deviceDefaultFolderName);
if (!await FileUtility.directoryExists(
createTimeScaffoldType, this.deviceRootPath)) {
this.deviceRootPath = path.join(
this.projectRootPath,
folderName.deviceDefaultFolderName
);
if (
!(await FileUtility.directoryExists(
createTimeScaffoldType,
this.deviceRootPath
))
) {
await FileUtility.mkdirRecursively(
createTimeScaffoldType, this.deviceRootPath);
createTimeScaffoldType,
this.deviceRootPath
);
}
// Init iot workbench project file path
this.iotWorkbenchProjectFilePath =
path.join(this.deviceRootPath, FileNames.iotWorkbenchProjectFileName);
this.iotWorkbenchProjectFilePath = path.join(
this.deviceRootPath,
FileNames.iotWorkbenchProjectFileName
);
// Init workspace config file
this.workspaceConfigFilePath = path.join(
this.projectRootPath,
`${path.basename(this.projectRootPath)}${
FileNames.workspaceExtensionName}`);
FileNames.workspaceExtensionName
}`
);
// Update iot workbench project file.
await updateProjectHostTypeConfig(
createTimeScaffoldType, this.iotWorkbenchProjectFilePath,
this.projectHostType);
createTimeScaffoldType,
this.iotWorkbenchProjectFilePath,
this.projectHostType
);
const workspace: Workspace = { folders: [], settings: {} };
@ -139,19 +183,26 @@ export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
workspace.folders.push({ path: folderName.deviceDefaultFolderName });
workspace.settings[`IoTWorkbench.${ConfigKey.boardId}`] = boardId;
workspace.settings[`IoTWorkbench.${ConfigKey.devicePath}`] =
folderName.deviceDefaultFolderName;
folderName.deviceDefaultFolderName;
// Create azure components
await this.createAzureComponentsWithProjectType(
projectType, createTimeScaffoldType, workspace);
projectType,
createTimeScaffoldType,
workspace
);
// Update workspace config to workspace config file
if (!this.workspaceConfigFilePath) {
throw new Error(
`Workspace config file path is empty. Please initialize the project first.`);
`Workspace config file path is empty. Please initialize the project first.`
);
}
await FileUtility.writeJsonFile(
createTimeScaffoldType, this.workspaceConfigFilePath, workspace);
createTimeScaffoldType,
this.workspaceConfigFilePath,
workspace
);
// Check components prerequisites
this.componentList.forEach(async item => {
@ -173,29 +224,41 @@ export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
// Open project
await this.openProject(
createTimeScaffoldType, openInNewWindow, OpenScenario.createNewProject);
createTimeScaffoldType,
openInNewWindow,
OpenScenario.createNewProject
);
}
async openProject(
scaffoldType: ScaffoldType, openInNewWindow: boolean,
openScenario: OpenScenario): Promise<void> {
scaffoldType: ScaffoldType,
openInNewWindow: boolean,
openScenario: OpenScenario
): Promise<void> {
this.loadAndInitWorkspaceConfigFilePath(scaffoldType);
if (!await FileUtility.fileExists(
scaffoldType, this.workspaceConfigFilePath)) {
throw new Error(`Workspace config file ${
this.workspaceConfigFilePath} does not exist. Please initialize the project first.`);
if (
!(await FileUtility.fileExists(
scaffoldType,
this.workspaceConfigFilePath
))
) {
throw new Error(
`Workspace config file ${this.workspaceConfigFilePath} does not exist. Please initialize the project first.`
);
}
if (!openInNewWindow) {
// If open in current window, VSCode will restart. Need to send telemetry
// before VSCode restart to advoid data lost.
try {
const telemetryWorker =
TelemetryWorker.getInstance(this.extensionContext);
const eventNames = openScenario === OpenScenario.createNewProject ?
EventNames.createNewProjectEvent :
EventNames.configProjectEnvironmentEvent;
const telemetryWorker = TelemetryWorker.getInstance(
this.extensionContext
);
const eventNames =
openScenario === OpenScenario.createNewProject
? EventNames.createNewProjectEvent
: EventNames.configProjectEnvironmentEvent;
telemetryWorker.sendEvent(eventNames, this.telemetryContext);
} catch {
// If sending telemetry failed, skip the error to avoid blocking user.
@ -203,8 +266,10 @@ export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
}
vscode.commands.executeCommand(
IoTCubeCommands.OpenLocally, this.workspaceConfigFilePath,
openInNewWindow);
IoTCubeCommands.OpenLocally,
this.workspaceConfigFilePath,
openInNewWindow
);
}
/**
@ -214,23 +279,41 @@ export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
* @param scaffoldType scaffold type
* @param templateFilesInfo template files info to scaffold files for device
*/
private async initDevice(boardId: string, scaffoldType: ScaffoldType, templateFilesInfo?: TemplateFileInfo[]): Promise<void> {
if (!await FileUtility.directoryExists(scaffoldType, this.deviceRootPath)) {
throw new Error(`Device root path ${
this.deviceRootPath} does not exist. Please initialize the project first.`);
private async initDevice(
boardId: string,
scaffoldType: ScaffoldType,
templateFilesInfo?: TemplateFileInfo[]
): Promise<void> {
if (
!(await FileUtility.directoryExists(scaffoldType, this.deviceRootPath))
) {
throw new Error(
`Device root path ${this.deviceRootPath} does not exist. Please initialize the project first.`
);
}
let device: Component;
if (boardId === az3166DeviceModule.AZ3166Device.boardId) {
device = new az3166DeviceModule.AZ3166Device(
this.extensionContext, this.channel, this.telemetryContext,
this.deviceRootPath, templateFilesInfo);
this.extensionContext,
this.channel,
this.telemetryContext,
this.deviceRootPath,
templateFilesInfo
);
} else if (boardId === ioTButtonDeviceModule.IoTButtonDevice.boardId) {
device = new ioTButtonDeviceModule.IoTButtonDevice(this.deviceRootPath, templateFilesInfo);
device = new ioTButtonDeviceModule.IoTButtonDevice(
this.deviceRootPath,
templateFilesInfo
);
} else if (boardId === esp32DeviceModule.Esp32Device.boardId) {
device = new esp32DeviceModule.Esp32Device(
this.extensionContext, this.channel, this.telemetryContext,
this.deviceRootPath, templateFilesInfo);
this.extensionContext,
this.channel,
this.telemetryContext,
this.deviceRootPath,
templateFilesInfo
);
} else {
throw new Error(`The board ${boardId} is not supported.`);
}
@ -247,8 +330,9 @@ export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
* init azure components and update azure config files.
* @param scaffoldType Scaffold type
*/
private async initAzureComponentsWithoutConfig(scaffoldType: ScaffoldType):
Promise<void> {
private async initAzureComponentsWithoutConfig(
scaffoldType: ScaffoldType
): Promise<void> {
this.validateProjectRootPath(scaffoldType);
const iotHub = new ioTHubModule.IoTHub(this.projectRootPath, this.channel);
@ -263,10 +347,17 @@ export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
if (functionPath) {
const functionLocation = path.join(this.projectRootPath, functionPath);
const functionApp = new azureFunctionsModule.AzureFunctions(
functionLocation, functionPath, this.channel, null, [{
component: iotHub,
type: azureComponentConfigModule.DependencyType.Input
}]);
functionLocation,
functionPath,
this.channel,
null,
[
{
component: iotHub,
type: azureComponentConfigModule.DependencyType.Input
}
]
);
await functionApp.updateConfigSettings(scaffoldType);
await functionApp.load();
this.componentList.push(functionApp);
@ -281,84 +372,106 @@ export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
*/
private async initAzureComponentsWithConfig(
scaffoldType: ScaffoldType,
componentConfigs: AzureComponentConfig[]): Promise<void> {
componentConfigs: AzureComponentConfig[]
): Promise<void> {
this.validateProjectRootPath(scaffoldType);
const components: {[key: string]: Component} = {};
const components: { [key: string]: Component } = {};
for (const componentConfig of componentConfigs) {
switch (componentConfig.type) {
case ComponentType.IoTHub: {
const iotHub =
new ioTHubModule.IoTHub(this.projectRootPath, this.channel);
await iotHub.load();
components[iotHub.id] = iotHub;
this.componentList.push(iotHub);
case ComponentType.IoTHub: {
const iotHub = new ioTHubModule.IoTHub(
this.projectRootPath,
this.channel
);
await iotHub.load();
components[iotHub.id] = iotHub;
this.componentList.push(iotHub);
const iothubDevice =
new ioTHubDeviceModule.IoTHubDevice(this.channel);
this.componentList.push(iothubDevice);
const iothubDevice = new ioTHubDeviceModule.IoTHubDevice(
this.channel
);
this.componentList.push(iothubDevice);
break;
}
case ComponentType.AzureFunctions: {
const functionPath =
ConfigHandler.get<string>(ConfigKey.functionPath);
if (!functionPath) {
break;
}
case ComponentType.AzureFunctions: {
const functionPath = ConfigHandler.get<string>(
ConfigKey.functionPath
);
if (!functionPath) {
throw new Error(
`Internal Error: Fail to get function path from configuration.`
);
}
const functionLocation = path.join(
this.projectRootPath,
functionPath
);
if (functionLocation) {
const functionApp = new azureFunctionsModule.AzureFunctions(
functionLocation,
functionPath,
this.channel
);
await functionApp.load();
components[functionApp.id] = functionApp;
this.componentList.push(functionApp);
}
break;
}
case ComponentType.StreamAnalyticsJob: {
const dependencies: Dependency[] = [];
for (const dependent of componentConfig.dependencies) {
const component = components[dependent.id];
if (!component) {
throw new Error(`Cannot find component with id ${dependent}.`);
}
dependencies.push({ component, type: dependent.type });
}
const queryPath = path.join(
this.projectRootPath,
folderName.asaFolderName,
"query.asaql"
);
const asa = new streamAnalyticsJobModule.StreamAnalyticsJob(
queryPath,
this.extensionContext,
this.projectRootPath,
this.channel,
dependencies
);
await asa.load();
components[asa.id] = asa;
this.componentList.push(asa);
break;
}
case ComponentType.CosmosDB: {
const dependencies: Dependency[] = [];
for (const dependent of componentConfig.dependencies) {
const component = components[dependent.id];
if (!component) {
throw new Error(`Cannot find component with id ${dependent}.`);
}
dependencies.push({ component, type: dependent.type });
}
const cosmosDB = new cosmosDBModule.CosmosDB(
this.extensionContext,
this.projectRootPath,
this.channel,
dependencies
);
await cosmosDB.load();
components[cosmosDB.id] = cosmosDB;
this.componentList.push(cosmosDB);
break;
}
default: {
throw new Error(
`Internal Error: Fail to get function path from configuration.`);
`Component not supported with type of ${componentConfig.type}.`
);
}
const functionLocation =
path.join(this.projectRootPath, functionPath);
if (functionLocation) {
const functionApp = new azureFunctionsModule.AzureFunctions(
functionLocation, functionPath, this.channel);
await functionApp.load();
components[functionApp.id] = functionApp;
this.componentList.push(functionApp);
}
break;
}
case ComponentType.StreamAnalyticsJob: {
const dependencies: Dependency[] = [];
for (const dependent of componentConfig.dependencies) {
const component = components[dependent.id];
if (!component) {
throw new Error(`Cannot find component with id ${dependent}.`);
}
dependencies.push({ component, type: dependent.type });
}
const queryPath = path.join(
this.projectRootPath, folderName.asaFolderName, 'query.asaql');
const asa = new streamAnalyticsJobModule.StreamAnalyticsJob(
queryPath, this.extensionContext, this.projectRootPath,
this.channel, dependencies);
await asa.load();
components[asa.id] = asa;
this.componentList.push(asa);
break;
}
case ComponentType.CosmosDB: {
const dependencies: Dependency[] = [];
for (const dependent of componentConfig.dependencies) {
const component = components[dependent.id];
if (!component) {
throw new Error(`Cannot find component with id ${dependent}.`);
}
dependencies.push({ component, type: dependent.type });
}
const cosmosDB = new cosmosDBModule.CosmosDB(
this.extensionContext, this.projectRootPath, this.channel,
dependencies);
await cosmosDB.load();
components[cosmosDB.id] = cosmosDB;
this.componentList.push(cosmosDB);
break;
}
default: {
throw new Error(
`Component not supported with type of ${componentConfig.type}.`);
}
}
}
}
@ -370,13 +483,14 @@ export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
private async initAzureConfig(scaffoldType: ScaffoldType): Promise<void> {
this.validateProjectRootPath(scaffoldType);
const azureConfigFileHandler =
new azureComponentConfigModule.AzureConfigFileHandler(
this.projectRootPath);
const azureConfigFileHandler = new azureComponentConfigModule.AzureConfigFileHandler(
this.projectRootPath
);
await azureConfigFileHandler.createIfNotExists(scaffoldType);
const componentConfigs =
await azureConfigFileHandler.getSortedComponents(scaffoldType);
const componentConfigs = await azureConfigFileHandler.getSortedComponents(
scaffoldType
);
if (componentConfigs.length === 0) {
// Support backward compact
@ -387,111 +501,143 @@ export class IoTWorkspaceProject extends IoTWorkbenchProjectBase {
}
private async createAzureComponentsWithProjectType(
projectType: ProjectTemplateType, scaffoldType: ScaffoldType,
workspaceConfig: Workspace): Promise<void> {
projectType: ProjectTemplateType,
scaffoldType: ScaffoldType,
workspaceConfig: Workspace
): Promise<void> {
this.validateProjectRootPath(scaffoldType);
// initialize the storage for azure component settings
const azureConfigFileHandler =
new azureComponentConfigModule.AzureConfigFileHandler(
this.projectRootPath);
const azureConfigFileHandler = new azureComponentConfigModule.AzureConfigFileHandler(
this.projectRootPath
);
await azureConfigFileHandler.createIfNotExists(scaffoldType);
switch (projectType) {
case ProjectTemplateType.Basic:
// Save data to configFile
break;
case ProjectTemplateType.IotHub: {
const iothub =
new ioTHubModule.IoTHub(this.projectRootPath, this.channel);
this.componentList.push(iothub);
break;
}
case ProjectTemplateType.AzureFunctions: {
const iothub =
new ioTHubModule.IoTHub(this.projectRootPath, this.channel);
const functionDir = path.join(
this.projectRootPath, folderName.functionDefaultFolderName);
if (!await FileUtility.directoryExists(scaffoldType, functionDir)) {
await FileUtility.mkdirRecursively(scaffoldType, functionDir);
case ProjectTemplateType.Basic:
// Save data to configFile
break;
case ProjectTemplateType.IotHub: {
const iothub = new ioTHubModule.IoTHub(
this.projectRootPath,
this.channel
);
this.componentList.push(iothub);
break;
}
const azureFunctions = new azureFunctionsModule.AzureFunctions(
functionDir, folderName.functionDefaultFolderName, this.channel,
null, [{
component: iothub,
type: azureComponentConfigModule.DependencyType.Input
}] /*Dependencies*/);
case ProjectTemplateType.AzureFunctions: {
const iothub = new ioTHubModule.IoTHub(
this.projectRootPath,
this.channel
);
workspaceConfig.folders.push(
{ path: folderName.functionDefaultFolderName });
workspaceConfig.settings[`IoTWorkbench.${ConfigKey.functionPath}`] =
folderName.functionDefaultFolderName;
const functionDir = path.join(
this.projectRootPath,
folderName.functionDefaultFolderName
);
if (!(await FileUtility.directoryExists(scaffoldType, functionDir))) {
await FileUtility.mkdirRecursively(scaffoldType, functionDir);
}
const azureFunctions = new azureFunctionsModule.AzureFunctions(
functionDir,
folderName.functionDefaultFolderName,
this.channel,
null,
[
{
component: iothub,
type: azureComponentConfigModule.DependencyType.Input
}
] /*Dependencies*/
);
this.componentList.push(iothub);
this.componentList.push(azureFunctions);
break;
}
case ProjectTemplateType.StreamAnalytics: {
const iothub =
new ioTHubModule.IoTHub(this.projectRootPath, this.channel);
workspaceConfig.folders.push({
path: folderName.functionDefaultFolderName
});
workspaceConfig.settings[`IoTWorkbench.${ConfigKey.functionPath}`] =
folderName.functionDefaultFolderName;
const cosmosDB = new cosmosDBModule.CosmosDB(
this.extensionContext, this.projectRootPath, this.channel);
const asaDir =
path.join(this.projectRootPath, folderName.asaFolderName);
if (!await FileUtility.directoryExists(scaffoldType, asaDir)) {
await FileUtility.mkdirRecursively(scaffoldType, asaDir);
this.componentList.push(iothub);
this.componentList.push(azureFunctions);
break;
}
const asaFilePath = this.extensionContext.asAbsolutePath(
path.join(FileNames.resourcesFolderName, 'asaql', 'query.asaql'));
const queryPath = path.join(asaDir, 'query.asaql');
const asaQueryContent =
fs.readFileSync(asaFilePath, 'utf8')
.replace(/\[input\]/, `"iothub-${iothub.id}"`)
.replace(/\[output\]/, `"cosmosdb-${cosmosDB.id}"`);
await FileUtility.writeFile(scaffoldType, queryPath, asaQueryContent);
case ProjectTemplateType.StreamAnalytics: {
const iothub = new ioTHubModule.IoTHub(
this.projectRootPath,
this.channel
);
const asa = new streamAnalyticsJobModule.StreamAnalyticsJob(
queryPath, this.extensionContext, this.projectRootPath,
this.channel, [
{
component: iothub,
type: azureComponentConfigModule.DependencyType.Input
},
{
component: cosmosDB,
type: azureComponentConfigModule.DependencyType.Other
}
]);
const cosmosDB = new cosmosDBModule.CosmosDB(
this.extensionContext,
this.projectRootPath,
this.channel
);
workspaceConfig.folders.push({ path: folderName.asaFolderName });
workspaceConfig.settings[`IoTWorkbench.${ConfigKey.asaPath}`] =
folderName.asaFolderName;
const asaDir = path.join(
this.projectRootPath,
folderName.asaFolderName
);
if (!(await FileUtility.directoryExists(scaffoldType, asaDir))) {
await FileUtility.mkdirRecursively(scaffoldType, asaDir);
}
const asaFilePath = this.extensionContext.asAbsolutePath(
path.join(FileNames.resourcesFolderName, "asaql", "query.asaql")
);
const queryPath = path.join(asaDir, "query.asaql");
const asaQueryContent = fs
.readFileSync(asaFilePath, "utf8")
.replace(/\[input\]/, `"iothub-${iothub.id}"`)
.replace(/\[output\]/, `"cosmosdb-${cosmosDB.id}"`);
await FileUtility.writeFile(scaffoldType, queryPath, asaQueryContent);
this.componentList.push(iothub);
this.componentList.push(cosmosDB);
this.componentList.push(asa);
break;
}
default:
break;
const asa = new streamAnalyticsJobModule.StreamAnalyticsJob(
queryPath,
this.extensionContext,
this.projectRootPath,
this.channel,
[
{
component: iothub,
type: azureComponentConfigModule.DependencyType.Input
},
{
component: cosmosDB,
type: azureComponentConfigModule.DependencyType.Other
}
]
);
workspaceConfig.folders.push({ path: folderName.asaFolderName });
workspaceConfig.settings[`IoTWorkbench.${ConfigKey.asaPath}`] =
folderName.asaFolderName;
this.componentList.push(iothub);
this.componentList.push(cosmosDB);
this.componentList.push(asa);
break;
}
default:
break;
}
}
// Init workspace config file path at load time
private async loadAndInitWorkspaceConfigFilePath(scaffoldType: ScaffoldType): Promise<void> {
private async loadAndInitWorkspaceConfigFilePath(
scaffoldType: ScaffoldType
): Promise<void> {
this.validateProjectRootPath(scaffoldType);
const workspaceFile = getWorkspaceFile(this.projectRootPath);
if (workspaceFile) {
this.workspaceConfigFilePath =
path.join(this.projectRootPath, workspaceFile);
this.workspaceConfigFilePath = path.join(
this.projectRootPath,
workspaceFile
);
} else {
throw new Error(
`Fail to init iot project workspace file path: Cannot find workspace file under project root path: ${
this.projectRootPath}.`);
`Fail to init iot project workspace file path: \
Cannot find workspace file under project root path: ${this.projectRootPath}.`
);
}
}
}
}

Просмотреть файл

@ -1,14 +1,16 @@
import { crc16xmodem } from 'crc';
import * as fs from 'fs-plus';
import { crc16xmodem } from "crc";
import * as fs from "fs-plus";
export class OTA {
static generateCrc(filePath: string): {
static generateCrc(
filePath: string
): {
crc: string;
size: number;
} {
} {
const data = fs.readFileSync(filePath);
const size = fs.statSync(filePath).size;
const crc = crc16xmodem(data).toString(16);
return { crc, size };
}
}
}

Просмотреть файл

@ -1,69 +1,92 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as path from 'path';
import * as vscode from 'vscode';
import * as sdk from 'vscode-iot-device-cube-sdk';
import * as path from "path";
import * as vscode from "vscode";
import * as sdk from "vscode-iot-device-cube-sdk";
import { CancelOperationError } from '../CancelOperationError';
import { ConfigHandler } from '../configHandler';
import { ConfigKey, FileNames, OperationType, ScaffoldType } from '../constants';
import { FileUtility } from '../FileUtility';
import { TelemetryContext } from '../telemetry';
import { askAndOpenInRemote, channelShowAndAppendLine, executeCommand } from '../utils';
import { CancelOperationError } from "../CancelOperationError";
import { ConfigHandler } from "../configHandler";
import {
ConfigKey,
FileNames,
OperationType,
ScaffoldType
} from "../constants";
import { FileUtility } from "../FileUtility";
import { TelemetryContext } from "../telemetry";
import {
askAndOpenInRemote,
channelShowAndAppendLine,
executeCommand
} from "../utils";
import { ContainerDeviceBase } from './ContainerDeviceBase';
import { DeviceType } from './Interfaces/Device';
import { TemplateFileInfo } from './Interfaces/ProjectTemplate';
import { RemoteExtension } from './RemoteExtension';
import { ContainerDeviceBase } from "./ContainerDeviceBase";
import { DeviceType } from "./Interfaces/Device";
import { TemplateFileInfo } from "./Interfaces/ProjectTemplate";
import { RemoteExtension } from "./RemoteExtension";
class RaspberryPiUploadConfig {
static host = 'hostname';
static host = "hostname";
static port = 22;
static user = 'username';
static password = 'password';
static projectPath = 'IoTProject';
static user = "username";
static password = "password";
static projectPath = "IoTProject";
static updated = false;
}
export class RaspberryPiDevice extends ContainerDeviceBase {
private static _boardId = 'raspberrypi';
name = 'Raspberry Pi';
private static _boardId = "raspberrypi";
name = "Raspberry Pi";
static get boardId(): string {
return RaspberryPiDevice._boardId;
}
constructor(
context: vscode.ExtensionContext, projectPath: string,
channel: vscode.OutputChannel, telemetryContext: TelemetryContext,
templateFilesInfo: TemplateFileInfo[] = []) {
context: vscode.ExtensionContext,
projectPath: string,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext,
templateFilesInfo: TemplateFileInfo[] = []
) {
super(
context, projectPath, channel, telemetryContext,
DeviceType.RaspberryPi, templateFilesInfo);
context,
projectPath,
channel,
telemetryContext,
DeviceType.RaspberryPi,
templateFilesInfo
);
}
private async getBinaryFileName(): Promise<string|undefined> {
private async getBinaryFileName(): Promise<string | undefined> {
// Parse binary name from CMakeLists.txt file
const cmakeFilePath =
path.join(this.projectFolder, FileNames.cmakeFileName);
if (!await FileUtility.fileExists(ScaffoldType.Workspace, cmakeFilePath)) {
const cmakeFilePath = path.join(
this.projectFolder,
FileNames.cmakeFileName
);
if (
!(await FileUtility.fileExists(ScaffoldType.Workspace, cmakeFilePath))
) {
return;
}
const getBinaryFileNameCmd = `cat ${
cmakeFilePath} | grep 'add_executable' | sed -e 's/^add_executable(//' | awk '{$1=$1};1' | cut -d ' ' -f1 | tr -d '\n'`;
const getBinaryFileNameCmd = `cat ${cmakeFilePath} | grep 'add_executable' \
| sed -e 's/^add_executable(//' | awk '{$1=$1};1' | cut -d ' ' -f1 | tr -d '\n'`;
const binaryName = await executeCommand(getBinaryFileNameCmd);
return binaryName;
}
private async enableBinaryExecutability(ssh: sdk.SSH, binaryName: string): Promise<void> {
private async enableBinaryExecutability(
ssh: sdk.SSH,
binaryName: string
): Promise<void> {
if (!binaryName) {
return;
}
const chmodCmd = `cd ${RaspberryPiUploadConfig.projectPath} && [ -f ${
binaryName} ] && chmod +x ${binaryName}`;
const chmodCmd = `cd ${RaspberryPiUploadConfig.projectPath} && [ -f ${binaryName} ] && chmod +x ${binaryName}`;
await ssh.exec(chmodCmd);
return;
@ -79,18 +102,19 @@ export class RaspberryPiDevice extends ContainerDeviceBase {
try {
const binaryName = await this.getBinaryFileName();
if (!binaryName) {
const message = `No executable file specified in ${
FileNames.cmakeFileName}. Nothing to upload to target machine.`;
const message = `No executable file specified in ${FileNames.cmakeFileName}. \
Nothing to upload to target machine.`;
vscode.window.showWarningMessage(message);
channelShowAndAppendLine(this.channel, message);
return false;
}
const binaryFilePath = path.join(this.outputPath, binaryName);
if (!await FileUtility.fileExists(
ScaffoldType.Workspace, binaryFilePath)) {
const message = `Executable file ${binaryName} does not exist under ${
this.outputPath}. Please compile device code first.`;
if (
!(await FileUtility.fileExists(ScaffoldType.Workspace, binaryFilePath))
) {
const message = `Executable file ${binaryName} does not exist under ${this.outputPath}. \
Please compile device code first.`;
vscode.window.showWarningMessage(message);
channelShowAndAppendLine(this.channel, message);
return false;
@ -105,16 +129,19 @@ export class RaspberryPiDevice extends ContainerDeviceBase {
const ssh = new sdk.SSH();
await ssh.open(
RaspberryPiUploadConfig.host, RaspberryPiUploadConfig.port,
RaspberryPiUploadConfig.user, RaspberryPiUploadConfig.password);
RaspberryPiUploadConfig.host,
RaspberryPiUploadConfig.port,
RaspberryPiUploadConfig.user,
RaspberryPiUploadConfig.password
);
try {
await ssh.uploadFile(
binaryFilePath, RaspberryPiUploadConfig.projectPath);
binaryFilePath,
RaspberryPiUploadConfig.projectPath
);
} catch (error) {
const message =
`SSH traffic is too busy. Please wait a second and retry. Error: ${
error}.`;
const message = `SSH traffic is too busy. Please wait a second and retry. Error: ${error}.`;
channelShowAndAppendLine(this.channel, message);
console.log(error);
throw new Error(message);
@ -124,14 +151,16 @@ export class RaspberryPiDevice extends ContainerDeviceBase {
await this.enableBinaryExecutability(ssh, binaryName);
} catch (error) {
throw new Error(
`Failed to enable binary executability. Error: ${error.message}`);
`Failed to enable binary executability. Error: ${error.message}`
);
}
try {
await ssh.close();
} catch (error) {
throw new Error(
`Failed to close SSH connection. Error: ${error.message}`);
`Failed to close SSH connection. Error: ${error.message}`
);
}
const message = `Successfully deploy compiled files to device board.`;
@ -139,42 +168,46 @@ export class RaspberryPiDevice extends ContainerDeviceBase {
vscode.window.showInformationMessage(message);
} catch (error) {
throw new Error(
`Upload binary file to device ${RaspberryPiUploadConfig.user}@${
RaspberryPiUploadConfig.host} failed. ${error}`);
`Upload binary file to device ${RaspberryPiUploadConfig.user}@${RaspberryPiUploadConfig.host} failed. ${error}`
);
}
return true;
}
async configDeviceSettings(): Promise<boolean> {
const configSelectionItems: vscode.QuickPickItem[] = [{
label: 'Configure SSH to target device',
description: '',
detail:
'Configure SSH (IP, username and password) connection to target device for uploading compiled code'
}];
const configSelectionItems: vscode.QuickPickItem[] = [
{
label: "Configure SSH to target device",
description: "",
detail:
"Configure SSH (IP, username and password) connection to target device for uploading compiled code"
}
];
const configSelection =
await vscode.window.showQuickPick(configSelectionItems, {
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select an option',
});
const configSelection = await vscode.window.showQuickPick(
configSelectionItems,
{
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: "Select an option"
}
);
if (!configSelection) {
return false;
}
if (configSelection.label === 'Configure SSH to target device') {
if (configSelection.label === "Configure SSH to target device") {
try {
const res = await this.configSSH();
if (res) {
vscode.window.showInformationMessage('Config SSH successfully.');
vscode.window.showInformationMessage("Config SSH successfully.");
}
return res;
} catch (error) {
vscode.window.showWarningMessage('Config SSH failed.');
vscode.window.showWarningMessage("Config SSH failed.");
return false;
}
} else {
@ -182,7 +215,7 @@ export class RaspberryPiDevice extends ContainerDeviceBase {
const res = await this.configHub();
return res;
} catch (error) {
vscode.window.showWarningMessage('Config IoT Hub failed.');
vscode.window.showWarningMessage("Config IoT Hub failed.");
return false;
}
}
@ -191,22 +224,23 @@ export class RaspberryPiDevice extends ContainerDeviceBase {
private async autoDiscoverDeviceIp(): Promise<vscode.QuickPickItem[]> {
const sshDevicePickItems: vscode.QuickPickItem[] = [];
const deviceInfos = await sdk.SSH.discover();
deviceInfos.forEach((deviceInfo) => {
deviceInfos.forEach(deviceInfo => {
sshDevicePickItems.push({
label: deviceInfo.ip as string,
description: deviceInfo.host || '<Unknown>'
description: deviceInfo.host || "<Unknown>"
});
});
sshDevicePickItems.push(
{
label: '$(sync) Discover again',
detail: 'Auto discover SSH enabled device in LAN'
label: "$(sync) Discover again",
detail: "Auto discover SSH enabled device in LAN"
},
{
label: '$(gear) Manual setup',
detail: 'Setup device SSH configuration manually'
});
label: "$(gear) Manual setup",
detail: "Setup device SSH configuration manually"
}
);
return sshDevicePickItems;
}
@ -215,46 +249,52 @@ export class RaspberryPiDevice extends ContainerDeviceBase {
// Raspberry Pi host
const sshDiscoverOrInputItems: vscode.QuickPickItem[] = [
{
label: '$(search) Auto discover',
detail: 'Auto discover SSH enabled device in LAN'
label: "$(search) Auto discover",
detail: "Auto discover SSH enabled device in LAN"
},
{
label: '$(gear) Manual setup',
detail: 'Setup device SSH configuration manually'
label: "$(gear) Manual setup",
detail: "Setup device SSH configuration manually"
}
];
const sshDiscoverOrInputChoice =
await vscode.window.showQuickPick(sshDiscoverOrInputItems, {
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select an option',
});
const sshDiscoverOrInputChoice = await vscode.window.showQuickPick(
sshDiscoverOrInputItems,
{
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: "Select an option"
}
);
if (!sshDiscoverOrInputChoice) {
return false;
}
let raspiHost: string|undefined;
let raspiHost: string | undefined;
if (sshDiscoverOrInputChoice.label === '$(search) Auto discover') {
let selectDeviceChoice: vscode.QuickPickItem|undefined;
if (sshDiscoverOrInputChoice.label === "$(search) Auto discover") {
let selectDeviceChoice: vscode.QuickPickItem | undefined;
do {
const selectDeviceItems = this.autoDiscoverDeviceIp();
selectDeviceChoice =
await vscode.window.showQuickPick(selectDeviceItems, {
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select a device',
});
} while (selectDeviceChoice &&
selectDeviceChoice.label === '$(sync) Discover again');
selectDeviceChoice = await vscode.window.showQuickPick(
selectDeviceItems,
{
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: "Select a device"
}
);
} while (
selectDeviceChoice &&
selectDeviceChoice.label === "$(sync) Discover again"
);
if (!selectDeviceChoice) {
return false;
}
if (selectDeviceChoice.label !== '$(gear) Manual setup') {
if (selectDeviceChoice.label !== "$(gear) Manual setup") {
raspiHost = selectDeviceChoice.label;
}
}
@ -282,9 +322,10 @@ export class RaspberryPiDevice extends ContainerDeviceBase {
if (!raspiPortString) {
return false;
}
const raspiPort = raspiPortString && !isNaN(Number(raspiPortString)) ?
Number(raspiPortString) :
RaspberryPiUploadConfig.port;
const raspiPort =
raspiPortString && !isNaN(Number(raspiPortString))
? Number(raspiPortString)
: RaspberryPiUploadConfig.port;
// Raspberry Pi user name
const raspiUserOption: vscode.InputBoxOptions = {
@ -334,37 +375,45 @@ export class RaspberryPiDevice extends ContainerDeviceBase {
async configHub(): Promise<boolean> {
const projectFolderPath = this.projectFolder;
if (!FileUtility.directoryExists(
ScaffoldType.Workspace, projectFolderPath)) {
throw new Error('Unable to find the device folder inside the project.');
if (
!FileUtility.directoryExists(ScaffoldType.Workspace, projectFolderPath)
) {
throw new Error("Unable to find the device folder inside the project.");
}
const deviceConnectionStringSelection: vscode.QuickPickItem[] = [{
label: 'Copy device connection string',
description: 'Copy device connection string',
detail: 'Copy'
}];
const selection =
await vscode.window.showQuickPick(deviceConnectionStringSelection, {
ignoreFocusOut: true,
placeHolder: 'Copy IoT Hub Device Connection String'
});
const deviceConnectionStringSelection: vscode.QuickPickItem[] = [
{
label: "Copy device connection string",
description: "Copy device connection string",
detail: "Copy"
}
];
const selection = await vscode.window.showQuickPick(
deviceConnectionStringSelection,
{
ignoreFocusOut: true,
placeHolder: "Copy IoT Hub Device Connection String"
}
);
if (!selection) {
return false;
}
const deviceConnectionString =
ConfigHandler.get<string>(ConfigKey.iotHubDeviceConnectionString);
const deviceConnectionString = ConfigHandler.get<string>(
ConfigKey.iotHubDeviceConnectionString
);
if (!deviceConnectionString) {
throw new Error(
'Unable to get the device connection string, please invoke the command of Azure Provision first.');
"Unable to get the device connection string, please invoke the command of Azure Provision first."
);
}
const ssh = new sdk.SSH();
await ssh.clipboardCopy(deviceConnectionString);
await ssh.close();
vscode.window.showInformationMessage(
'Device connection string has been copied.');
"Device connection string has been copied."
);
return true;
}
}
}

Просмотреть файл

@ -1,18 +1,17 @@
"use strict";
'use strict';
import * as vscode from 'vscode';
import { DependentExtensions } from '../constants';
import { DialogResponses } from '../DialogResponses';
import { WorkbenchExtension } from '../WorkbenchExtension';
import { VscodeCommands } from '../common/Commands';
import { CancelOperationError } from '../CancelOperationError';
import * as vscode from "vscode";
import { DependentExtensions } from "../constants";
import { DialogResponses } from "../DialogResponses";
import { WorkbenchExtension } from "../WorkbenchExtension";
import { VscodeCommands } from "../common/Commands";
import { CancelOperationError } from "../CancelOperationError";
export class RemoteExtension {
static isRemote(context: vscode.ExtensionContext): boolean {
const extension = WorkbenchExtension.getExtension(context);
if (!extension) {
throw new Error('Fail to get workbench extension.');
throw new Error("Fail to get workbench extension.");
}
return extension.extensionKind === vscode.ExtensionKind.Workspace;
}
@ -26,13 +25,17 @@ export class RemoteExtension {
static async isAvailable(): Promise<boolean> {
if (!vscode.extensions.getExtension(DependentExtensions.remote)) {
const message =
'Remote extension is required for the current project. Do you want to install it from marketplace?';
"Remote extension is required for the current project. Do you want to install it from marketplace?";
const choice = await vscode.window.showInformationMessage(
message, DialogResponses.yes, DialogResponses.no);
message,
DialogResponses.yes,
DialogResponses.no
);
if (choice === DialogResponses.yes) {
vscode.commands.executeCommand(
VscodeCommands.VscodeOpen,
vscode.Uri.parse('vscode:extension/' + DependentExtensions.remote));
vscode.Uri.parse("vscode:extension/" + DependentExtensions.remote)
);
}
return false;
}
@ -43,8 +46,8 @@ export class RemoteExtension {
const res = await RemoteExtension.isAvailable();
if (!res) {
throw new CancelOperationError(
`Remote extension is not available. Please install ${
DependentExtensions.remote} first.`);
`Remote extension is not available. Please install ${DependentExtensions.remote} first.`
);
}
}
@ -54,11 +57,11 @@ export class RemoteExtension {
*/
static checkLocalBeforeRunCommand(context: vscode.ExtensionContext): boolean {
if (RemoteExtension.isRemote(context)) {
const message =
`The command is not supported to be run in a remote environment. Open a new window and run this command again.`;
const message = `The command is not supported to be run in a remote environment. \
Open a new window and run this command again.`;
vscode.window.showWarningMessage(message);
return false;
}
return true;
}
}
}

Просмотреть файл

@ -1,16 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as fs from 'fs-plus';
import * as path from 'path';
import * as ssh2 from 'ssh2';
import * as vscode from 'vscode';
import { channelShowAndAppendLine } from '../utils';
import * as fs from "fs-plus";
import * as path from "path";
import * as ssh2 from "ssh2";
import * as vscode from "vscode";
import { channelShowAndAppendLine } from "../utils";
export class SSH {
private _client: ssh2.Client;
private _connected = false;
private _channel: vscode.OutputChannel|null = null;
private _channel: vscode.OutputChannel | null = null;
constructor(channel?: vscode.OutputChannel) {
this._client = new ssh2.Client();
@ -19,145 +19,161 @@ export class SSH {
}
}
async connect(host: string, port: number, username: string, password: string): Promise<boolean> {
return new Promise(
(resolve: (value: boolean) => void) => {
const conn = this._client;
conn.on('ready',
() => {
this._connected = true;
return resolve(true);
})
.on('end',
() => {
this._connected = false;
return resolve(false);
})
.on('close',
() => {
this._connected = false;
return resolve(false);
})
.connect({ host, port, username, password });
});
async connect(
host: string,
port: number,
username: string,
password: string
): Promise<boolean> {
return new Promise((resolve: (value: boolean) => void) => {
const conn = this._client;
conn
.on("ready", () => {
this._connected = true;
return resolve(true);
})
.on("end", () => {
this._connected = false;
return resolve(false);
})
.on("close", () => {
this._connected = false;
return resolve(false);
})
.connect({ host, port, username, password });
});
}
async upload(filePath: string, remoteRootPath: string): Promise<boolean> {
return new Promise(
(resolve: (value: boolean) => void) => {
if (!this._connected) {
return new Promise((resolve: (value: boolean) => void) => {
if (!this._connected) {
return resolve(false);
}
if (!fs.existsSync(filePath)) {
return resolve(false);
}
filePath = filePath.replace(/[\\/]+/g, "/");
const rootPath = (fs.isDirectorySync(filePath)
? filePath
: path.dirname(filePath)
).replace(/\/$/, "");
const files = fs.listTreeSync(filePath);
if (this._channel) {
channelShowAndAppendLine(this._channel, "");
}
const conn = this._client;
conn.sftp(async (err, sftp) => {
if (err) {
if (this._channel) {
channelShowAndAppendLine(this._channel, `SFTP Error:`);
channelShowAndAppendLine(this._channel, err.message);
}
return resolve(false);
}
if (!fs.existsSync(filePath)) {
return resolve(false);
}
const rootPathExist = await this.isExist(sftp, remoteRootPath);
filePath = filePath.replace(/[\\/]+/g, '/');
const rootPath =
(fs.isDirectorySync(filePath) ? filePath : path.dirname(filePath))
.replace(/\/$/, '');
const files = fs.listTreeSync(filePath);
if (this._channel) {
channelShowAndAppendLine(this._channel, '');
}
const conn = this._client;
conn.sftp(async (err, sftp) => {
if (err) {
if (this._channel) {
channelShowAndAppendLine(this._channel, `SFTP Error:`);
channelShowAndAppendLine(this._channel, err.message);
}
return resolve(false);
}
const rootPathExist = await this.isExist(sftp, remoteRootPath);
if (rootPathExist) {
const overwriteOption =
await vscode.window.showInformationMessage(
`${remoteRootPath} exists, overwrite?`, 'Yes', 'No',
'Cancel');
if (overwriteOption === 'Cancel') {
if (this._channel) {
channelShowAndAppendLine(
this._channel, 'Device upload cancelled.');
}
vscode.window.showWarningMessage('Device upload cancelled.');
return resolve(true);
}
if (overwriteOption === 'No') {
const raspiPathOption: vscode.InputBoxOptions = {
value: 'IoTProject',
prompt: `Please input Raspberry Pi path here.`,
ignoreFocusOut: true
};
let raspiPath =
await vscode.window.showInputBox(raspiPathOption);
if (!raspiPath) {
return false;
}
raspiPath = raspiPath || 'IoTProject';
const res = await this.upload(filePath, raspiPath);
return resolve(res);
}
const rmDirRes = await this.shell(`rm -rf ${remoteRootPath}`);
if (!rmDirRes) {
if (this._channel) {
channelShowAndAppendLine(
this._channel,
`Directory Error: remove ${remoteRootPath} failed.`);
}
return resolve(false);
}
}
const rootPathCreated = await this.ensureDir(sftp, remoteRootPath);
if (!rootPathCreated) {
if (rootPathExist) {
const overwriteOption = await vscode.window.showInformationMessage(
`${remoteRootPath} exists, overwrite?`,
"Yes",
"No",
"Cancel"
);
if (overwriteOption === "Cancel") {
if (this._channel) {
channelShowAndAppendLine(
this._channel, `Directory Error: ${remoteRootPath}`);
channelShowAndAppendLine(this._channel, err);
this._channel,
"Device upload cancelled."
);
}
vscode.window.showWarningMessage("Device upload cancelled.");
return resolve(true);
}
if (overwriteOption === "No") {
const raspiPathOption: vscode.InputBoxOptions = {
value: "IoTProject",
prompt: `Please input Raspberry Pi path here.`,
ignoreFocusOut: true
};
let raspiPath = await vscode.window.showInputBox(raspiPathOption);
if (!raspiPath) {
return false;
}
raspiPath = raspiPath || "IoTProject";
const res = await this.upload(filePath, raspiPath);
return resolve(res);
}
const rmDirRes = await this.shell(`rm -rf ${remoteRootPath}`);
if (!rmDirRes) {
if (this._channel) {
channelShowAndAppendLine(
this._channel,
`Directory Error: remove ${remoteRootPath} failed.`
);
}
return resolve(false);
}
}
for (const file of files) {
const res = await this.uploadSingleFile(
sftp, file, rootPath, remoteRootPath);
if (!res) {
return resolve(false);
}
const rootPathCreated = await this.ensureDir(sftp, remoteRootPath);
if (!rootPathCreated) {
if (this._channel) {
channelShowAndAppendLine(
this._channel,
`Directory Error: ${remoteRootPath}`
);
channelShowAndAppendLine(this._channel, err);
}
return resolve(false);
}
return resolve(true);
});
});
}
private async isExist(sftp: ssh2.SFTPWrapper, remotePath: string): Promise<boolean> {
return new Promise(
(resolve: (value: boolean) => void) => {
sftp.readdir(remotePath, (err) => {
if (err) {
for (const file of files) {
const res = await this.uploadSingleFile(
sftp,
file,
rootPath,
remoteRootPath
);
if (!res) {
return resolve(false);
}
return resolve(true);
});
}
return resolve(true);
});
});
}
private async ensureDir(sftp: ssh2.SFTPWrapper, remotePath: string): Promise<boolean> {
private async isExist(
sftp: ssh2.SFTPWrapper,
remotePath: string
): Promise<boolean> {
return new Promise((resolve: (value: boolean) => void) => {
sftp.readdir(remotePath, err => {
if (err) {
return resolve(false);
}
return resolve(true);
});
});
}
private async ensureDir(
sftp: ssh2.SFTPWrapper,
remotePath: string
): Promise<boolean> {
return new Promise(
// eslint-disable-next-line no-async-promise-executor
async (
resolve: (value: boolean) => void) => {
async (resolve: (value: boolean) => void) => {
const dirExist = await this.isExist(sftp, remotePath);
if (!dirExist) {
sftp.mkdir(remotePath, async err => {
@ -169,31 +185,42 @@ export class SSH {
});
}
return resolve(true);
});
}
);
}
private async uploadSingleFile(sftp: ssh2.SFTPWrapper, filePath: string, rootPath: string, remoteRootPath: string): Promise<boolean> {
private async uploadSingleFile(
sftp: ssh2.SFTPWrapper,
filePath: string,
rootPath: string,
remoteRootPath: string
): Promise<boolean> {
return new Promise(
// eslint-disable-next-line no-async-promise-executor
async (
resolve: (value: boolean) => void) => {
const relativePath =
filePath.replace(/[\\/]+/g, '/').substr(rootPath.length + 1);
if (/(^|\/)node_modules(\/|$)/.test(relativePath) ||
/(^|\/).vscode(\/|$)/.test(relativePath) ||
relativePath === '.iotworkbenchproject') {
async (resolve: (value: boolean) => void) => {
const relativePath = filePath
.replace(/[\\/]+/g, "/")
.substr(rootPath.length + 1);
if (
/(^|\/)node_modules(\/|$)/.test(relativePath) ||
/(^|\/).vscode(\/|$)/.test(relativePath) ||
relativePath === ".iotworkbenchproject"
) {
return resolve(true);
}
const remotePath =
path.join(remoteRootPath, relativePath).replace(/[\\/]+/g, '/');
const remotePath = path
.join(remoteRootPath, relativePath)
.replace(/[\\/]+/g, "/");
if (fs.isDirectorySync(filePath)) {
const pathCreated = await this.ensureDir(sftp, remotePath);
if (!pathCreated) {
if (this._channel) {
channelShowAndAppendLine(
this._channel, `Directory Error: ${relativePath}`);
this._channel,
`Directory Error: ${relativePath}`
);
}
return resolve(false);
}
@ -203,7 +230,9 @@ export class SSH {
if (err) {
if (this._channel) {
channelShowAndAppendLine(
this._channel, `File Error: ${relativePath}`);
this._channel,
`File Error: ${relativePath}`
);
}
return resolve(false);
@ -211,80 +240,82 @@ export class SSH {
if (this._channel) {
channelShowAndAppendLine(
this._channel, `File Uploaded: ${relativePath}`);
this._channel,
`File Uploaded: ${relativePath}`
);
}
return resolve(true);
});
}
});
}
);
}
async shell(command: string, timeout?: number): Promise<boolean> {
return new Promise(
(resolve: (value: boolean) => void) => {
if (!this._connected) {
return new Promise((resolve: (value: boolean) => void) => {
if (!this._connected) {
return resolve(false);
}
let timeoutCounter: NodeJS.Timer;
if (timeout) {
timeoutCounter = setTimeout(() => {
return resolve(false);
}, timeout);
}
const conn = this._client;
conn.shell((err, stream) => {
if (err) {
if (this._channel) {
channelShowAndAppendLine(this._channel, `Shell Error:`);
channelShowAndAppendLine(this._channel, err.message);
}
return resolve(false);
}
let timeoutCounter: NodeJS.Timer;
if (timeout) {
timeoutCounter = setTimeout(() => {
return resolve(false);
}, timeout);
if (this._channel) {
channelShowAndAppendLine(this._channel, "");
}
const conn = this._client;
conn.shell((err, stream) => {
if (err) {
stream
.on("close", () => {
clearTimeout(timeoutCounter);
if (this._channel) {
channelShowAndAppendLine(this._channel, `Shell Error:`);
channelShowAndAppendLine(this._channel, err.message);
channelShowAndAppendLine(this._channel, "");
}
return resolve(false);
}
return resolve(true);
})
.on("data", (data: string | Buffer) => {
if (this._channel) {
const output = data.toString().replace(
// eslint-disable-next-line no-control-regex
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
""
);
this._channel.append(output);
}
})
.stderr.on("data", (data: string | Buffer) => {
if (this._channel) {
const output = data.toString().replace(
// eslint-disable-next-line no-control-regex
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
""
);
this._channel.append(output);
}
});
if (this._channel) {
channelShowAndAppendLine(this._channel, '');
}
stream
.on('close',
() => {
clearTimeout(timeoutCounter);
if (this._channel) {
channelShowAndAppendLine(this._channel, '');
}
return resolve(true);
})
.on('data',
(data: string|Buffer) => {
if (this._channel) {
const output = data.toString().replace(
// eslint-disable-next-line no-control-regex
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
'');
this._channel.append(output);
}
})
.stderr.on('data', (data: string|Buffer) => {
if (this._channel) {
const output = data.toString().replace(
// eslint-disable-next-line no-control-regex
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
'');
this._channel.append(output);
}
});
stream.setWindow(10, 500, 10, 100);
stream.end(command + '\nexit\n');
});
stream.setWindow(10, 500, 10, 100);
stream.end(command + "\nexit\n");
});
});
}
async close(): Promise<boolean> {
this._client.end();
return Promise.resolve(true);
}
}
}

Просмотреть файл

@ -1,25 +1,33 @@
import { ResourceManagementClient } from 'azure-arm-resource';
import * as fs from 'fs-plus';
import { Guid } from 'guid-typescript';
import * as path from 'path';
import * as vscode from 'vscode';
import { ResourceManagementClient } from "azure-arm-resource";
import * as fs from "fs-plus";
import { Guid } from "guid-typescript";
import * as path from "path";
import * as vscode from "vscode";
import { AzureComponentsStorage, FileNames, ScaffoldType } from '../constants';
import { channelPrintJsonObject, channelShowAndAppendLine } from '../utils';
import { AzureComponentsStorage, FileNames, ScaffoldType } from "../constants";
import { channelPrintJsonObject, channelShowAndAppendLine } from "../utils";
import { AzureComponentConfig, AzureConfigFileHandler, AzureConfigs, ComponentInfo, Dependency, DependencyConfig, DependencyType } from './AzureComponentConfig';
import { ARMTemplate, AzureUtility } from './AzureUtility';
import { Component, ComponentType } from './Interfaces/Component';
import { Deployable } from './Interfaces/Deployable';
import { Provisionable } from './Interfaces/Provisionable';
import {
AzureComponentConfig,
AzureConfigFileHandler,
AzureConfigs,
ComponentInfo,
Dependency,
DependencyConfig,
DependencyType
} from "./AzureComponentConfig";
import { ARMTemplate, AzureUtility } from "./AzureUtility";
import { Component, ComponentType } from "./Interfaces/Component";
import { Deployable } from "./Interfaces/Deployable";
import { Provisionable } from "./Interfaces/Provisionable";
enum StreamAnalyticsAction {
Start = 1,
Stop
}
export class StreamAnalyticsJob implements Component, Provisionable,
Deployable {
export class StreamAnalyticsJob
implements Component, Provisionable, Deployable {
dependencies: DependencyConfig[] = [];
private componentType: ComponentType;
private channel: vscode.OutputChannel;
@ -28,23 +36,30 @@ export class StreamAnalyticsJob implements Component, Provisionable,
private azureConfigHandler: AzureConfigFileHandler;
private extensionContext: vscode.ExtensionContext;
private queryPath: string;
private subscriptionId: string|null = null;
private resourceGroup: string|null = null;
private streamAnalyticsJobName: string|null = null;
private azureClient: ResourceManagementClient|null = null;
private catchedStreamAnalyticsList: Array<{name: string}> = [];
private subscriptionId: string | null = null;
private resourceGroup: string | null = null;
private streamAnalyticsJobName: string | null = null;
private azureClient: ResourceManagementClient | null = null;
private catchedStreamAnalyticsList: Array<{ name: string }> = [];
private async initAzureClient(): Promise<ResourceManagementClient> {
if (this.subscriptionId && this.resourceGroup &&
this.streamAnalyticsJobName && this.azureClient) {
if (
this.subscriptionId &&
this.resourceGroup &&
this.streamAnalyticsJobName &&
this.azureClient
) {
return this.azureClient;
}
const componentConfig = await this.azureConfigHandler.getComponentById(
ScaffoldType.Workspace, this.id);
ScaffoldType.Workspace,
this.id
);
if (!componentConfig) {
throw new Error(
`Cannot find Azure Stream Analytics component with id ${this.id}.`);
`Cannot find Azure Stream Analytics component with id ${this.id}.`
);
}
const componentInfo = componentConfig.componentInfo;
@ -58,7 +73,7 @@ export class StreamAnalyticsJob implements Component, Provisionable,
AzureUtility.init(this.extensionContext, this.channel, subscriptionId);
const azureClient = AzureUtility.getClient();
if (!azureClient) {
throw new Error('Initialize Azure client failed.');
throw new Error("Initialize Azure client failed.");
}
this.subscriptionId = subscriptionId;
@ -71,13 +86,15 @@ export class StreamAnalyticsJob implements Component, Provisionable,
private async callAction(action: StreamAnalyticsAction): Promise<unknown> {
const actionResource = `/subscriptions/${
this.subscriptionId}/resourceGroups/${
this.resourceGroup}/providers/Microsoft.StreamAnalytics/streamingjobs/${
this.streamAnalyticsJobName}/${
StreamAnalyticsAction[action].toLowerCase()}?api-version=2015-10-01`;
this.subscriptionId
}/resourceGroups/${
this.resourceGroup
}/providers/Microsoft.StreamAnalytics/streamingjobs/${
this.streamAnalyticsJobName
}/${StreamAnalyticsAction[action].toLowerCase()}?api-version=2015-10-01`;
await AzureUtility.postRequest(actionResource);
return new Promise((resolve) => {
return new Promise(resolve => {
const timeout = setTimeout(() => {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
clearTimeout(timer);
@ -86,9 +103,11 @@ export class StreamAnalyticsJob implements Component, Provisionable,
const timer = setInterval(async () => {
const state: string = await this.getState();
if (action === StreamAnalyticsAction.Start && state === 'Running' ||
action === StreamAnalyticsAction.Stop && state === 'Stopped' ||
action === StreamAnalyticsAction.Stop && state === 'Created') {
if (
(action === StreamAnalyticsAction.Start && state === "Running") ||
(action === StreamAnalyticsAction.Stop && state === "Stopped") ||
(action === StreamAnalyticsAction.Stop && state === "Created")
) {
clearTimeout(timeout);
clearInterval(timer);
return resolve(true);
@ -97,19 +116,23 @@ export class StreamAnalyticsJob implements Component, Provisionable,
});
}
private getStreamAnalyticsByNameFromCache(name: string): {name: string}|undefined {
private getStreamAnalyticsByNameFromCache(
name: string
): { name: string } | undefined {
return this.catchedStreamAnalyticsList.find(item => item.name === name);
}
private async getStreamAnalyticsInResourceGroup(): Promise<vscode.QuickPickItem[]>{
const resource = `/subscriptions/${
AzureUtility.subscriptionId}/resourceGroups/${
AzureUtility
.resourceGroup}/providers/Microsoft.StreamAnalytics/streamingjobs?api-version=2015-10-01`;
const asaListRes = await AzureUtility.getRequest(resource) as
{value: Array<{name: string; properties: {jobState: string}}>};
const asaList: vscode.QuickPickItem[] =
[{ label: '$(plus) Create New Stream Analytics Job', description: '' }];
private async getStreamAnalyticsInResourceGroup(): Promise<
vscode.QuickPickItem[]
> {
const resource = `/subscriptions/${AzureUtility.subscriptionId}/resourceGroups/\
${AzureUtility.resourceGroup}/providers/Microsoft.StreamAnalytics/streamingjobs?api-version=2015-10-01`;
const asaListRes = (await AzureUtility.getRequest(resource)) as {
value: Array<{ name: string; properties: { jobState: string } }>;
};
const asaList: vscode.QuickPickItem[] = [
{ label: "$(plus) Create New Stream Analytics Job", description: "" }
];
for (const item of asaListRes.value) {
asaList.push({ label: item.name, description: item.properties.jobState });
}
@ -123,9 +146,12 @@ export class StreamAnalyticsJob implements Component, Provisionable,
}
constructor(
queryPath: string, context: vscode.ExtensionContext, projectRoot: string,
queryPath: string,
context: vscode.ExtensionContext,
projectRoot: string,
channel: vscode.OutputChannel,
dependencyComponents: Dependency[]|null = null) {
dependencyComponents: Dependency[] | null = null
) {
this.queryPath = queryPath;
this.componentType = ComponentType.StreamAnalyticsJob;
this.channel = channel;
@ -134,13 +160,16 @@ export class StreamAnalyticsJob implements Component, Provisionable,
this.azureConfigHandler = new AzureConfigFileHandler(projectRoot);
this.extensionContext = context;
if (dependencyComponents && dependencyComponents.length > 0) {
dependencyComponents.forEach(
dependency => this.dependencies.push(
{ id: dependency.component.id, type: dependency.type }));
dependencyComponents.forEach(dependency =>
this.dependencies.push({
id: dependency.component.id,
type: dependency.type
})
);
}
}
name = 'Stream Analytics Job';
name = "Stream Analytics Job";
getComponentType(): ComponentType {
return this.componentType;
@ -152,8 +181,10 @@ export class StreamAnalyticsJob implements Component, Provisionable,
async load(): Promise<boolean> {
const azureConfigFilePath = path.join(
this.projectRootPath, AzureComponentsStorage.folderName,
AzureComponentsStorage.fileName);
this.projectRootPath,
AzureComponentsStorage.folderName,
AzureComponentsStorage.fileName
);
if (!fs.existsSync(azureConfigFilePath)) {
return false;
@ -162,9 +193,10 @@ export class StreamAnalyticsJob implements Component, Provisionable,
let azureConfigs: AzureConfigs;
try {
azureConfigs = JSON.parse(fs.readFileSync(azureConfigFilePath, 'utf8'));
azureConfigs = JSON.parse(fs.readFileSync(azureConfigFilePath, "utf8"));
const asaConfig = azureConfigs.componentConfigs.find(
config => config.type === this.componentType);
config => config.type === this.componentType
);
if (asaConfig) {
this.componentId = asaConfig.id;
this.dependencies = asaConfig.dependencies;
@ -180,21 +212,28 @@ export class StreamAnalyticsJob implements Component, Provisionable,
await this.updateConfigSettings(ScaffoldType.Local);
}
async updateConfigSettings(type: ScaffoldType, componentInfo?: ComponentInfo):
Promise<void> {
const asaComponentIndex =
await this.azureConfigHandler.getComponentIndexById(type, this.id);
async updateConfigSettings(
type: ScaffoldType,
componentInfo?: ComponentInfo
): Promise<void> {
const asaComponentIndex = await this.azureConfigHandler.getComponentIndexById(
type,
this.id
);
if (asaComponentIndex > -1) {
if (!componentInfo) {
return;
}
await this.azureConfigHandler.updateComponent(
type, asaComponentIndex, componentInfo);
type,
asaComponentIndex,
componentInfo
);
} else {
const newAsaConfig: AzureComponentConfig = {
id: this.id,
folder: '',
name: '',
folder: "",
name: "",
dependencies: this.dependencies,
type: this.componentType
};
@ -206,44 +245,54 @@ export class StreamAnalyticsJob implements Component, Provisionable,
const scaffoldType = ScaffoldType.Workspace;
const asaList = this.getStreamAnalyticsInResourceGroup();
const asaNameChoose = await vscode.window.showQuickPick(
asaList,
{ placeHolder: 'Select Stream Analytics Job', ignoreFocusOut: true });
const asaNameChoose = await vscode.window.showQuickPick(asaList, {
placeHolder: "Select Stream Analytics Job",
ignoreFocusOut: true
});
if (!asaNameChoose) {
return false;
}
let streamAnalyticsJobName = '';
let streamAnalyticsJobName = "";
if (!asaNameChoose.description) {
if (this.channel) {
channelShowAndAppendLine(
this.channel, 'Creating Stream Analytics Job...');
this.channel,
"Creating Stream Analytics Job..."
);
}
const asaArmTemplatePath = this.extensionContext.asAbsolutePath(path.join(
FileNames.resourcesFolderName, 'arm', 'streamanalytics.json'));
const asaArmTemplate =
JSON.parse(fs.readFileSync(asaArmTemplatePath, 'utf8')) as
ARMTemplate;
const asaArmTemplatePath = this.extensionContext.asAbsolutePath(
path.join(FileNames.resourcesFolderName, "arm", "streamanalytics.json")
);
const asaArmTemplate = JSON.parse(
fs.readFileSync(asaArmTemplatePath, "utf8")
) as ARMTemplate;
const asaDeploy = await AzureUtility.deployARMTemplate(asaArmTemplate);
if (!asaDeploy || !asaDeploy.properties ||
!asaDeploy.properties.outputs ||
!asaDeploy.properties.outputs.streamAnalyticsJobName) {
throw new Error('Provision Stream Analytics Job failed.');
if (
!asaDeploy ||
!asaDeploy.properties ||
!asaDeploy.properties.outputs ||
!asaDeploy.properties.outputs.streamAnalyticsJobName
) {
throw new Error("Provision Stream Analytics Job failed.");
}
channelPrintJsonObject(this.channel, asaDeploy);
streamAnalyticsJobName =
asaDeploy.properties.outputs.streamAnalyticsJobName.value;
asaDeploy.properties.outputs.streamAnalyticsJobName.value;
} else {
if (this.channel) {
channelShowAndAppendLine(
this.channel, 'Creating Stream Analytics Job...');
this.channel,
"Creating Stream Analytics Job..."
);
}
streamAnalyticsJobName = asaNameChoose.label;
const asaDetail =
this.getStreamAnalyticsByNameFromCache(streamAnalyticsJobName);
const asaDetail = this.getStreamAnalyticsByNameFromCache(
streamAnalyticsJobName
);
if (asaDetail) {
channelPrintJsonObject(this.channel, asaDetail);
}
@ -251,114 +300,134 @@ export class StreamAnalyticsJob implements Component, Provisionable,
for (const dependency of this.dependencies) {
const componentConfig = await this.azureConfigHandler.getComponentById(
scaffoldType, dependency.id);
scaffoldType,
dependency.id
);
if (!componentConfig) {
throw new Error(`Cannot find component with id ${dependency.id}.`);
}
if (dependency.type === DependencyType.Input) {
switch (componentConfig.type) {
case 'IoTHub': {
if (!componentConfig.componentInfo) {
return false;
}
const iotHubConnectionString =
componentConfig.componentInfo.values.iotHubConnectionString;
let iotHubName = '';
let iotHubKeyName = '';
let iotHubKey = '';
const iotHubNameMatches =
iotHubConnectionString.match(/HostName=(.*?)\./);
const iotHubKeyMatches =
iotHubConnectionString.match(/SharedAccessKey=(.*?)(;|$)/);
const iotHubKeyNameMatches =
iotHubConnectionString.match(/SharedAccessKeyName=(.*?)(;|$)/);
if (iotHubNameMatches) {
iotHubName = iotHubNameMatches[1];
}
if (iotHubKeyMatches) {
iotHubKey = iotHubKeyMatches[1];
}
if (iotHubKeyNameMatches) {
iotHubKeyName = iotHubKeyNameMatches[1];
}
case "IoTHub": {
if (!componentConfig.componentInfo) {
return false;
}
const iotHubConnectionString =
componentConfig.componentInfo.values.iotHubConnectionString;
let iotHubName = "";
let iotHubKeyName = "";
let iotHubKey = "";
const iotHubNameMatches = iotHubConnectionString.match(
/HostName=(.*?)\./
);
const iotHubKeyMatches = iotHubConnectionString.match(
/SharedAccessKey=(.*?)(;|$)/
);
const iotHubKeyNameMatches = iotHubConnectionString.match(
/SharedAccessKeyName=(.*?)(;|$)/
);
if (iotHubNameMatches) {
iotHubName = iotHubNameMatches[1];
}
if (iotHubKeyMatches) {
iotHubKey = iotHubKeyMatches[1];
}
if (iotHubKeyNameMatches) {
iotHubKeyName = iotHubKeyNameMatches[1];
}
if (!iotHubName || !iotHubKeyName || !iotHubKey) {
throw new Error('Cannot parse IoT Hub connection string.');
if (!iotHubName || !iotHubKeyName || !iotHubKey) {
throw new Error("Cannot parse IoT Hub connection string.");
}
const asaIoTHubArmTemplatePath = this.extensionContext.asAbsolutePath(
path.join(
FileNames.resourcesFolderName,
"arm",
"streamanalytics-input-iothub.json"
)
);
const asaIoTHubArmTemplate = JSON.parse(
fs.readFileSync(asaIoTHubArmTemplatePath, "utf8")
) as ARMTemplate;
const asaIotHubArmParameters = {
streamAnalyticsJobName: { value: streamAnalyticsJobName },
inputName: { value: `iothub-${componentConfig.id}` },
iotHubName: { value: iotHubName },
iotHubKeyName: { value: iotHubKeyName },
iotHubKey: { value: iotHubKey }
};
const asaInputDeploy = await AzureUtility.deployARMTemplate(
asaIoTHubArmTemplate,
asaIotHubArmParameters
);
if (!asaInputDeploy) {
throw new Error("Provision Stream Analytics Job failed.");
}
break;
}
const asaIoTHubArmTemplatePath =
this.extensionContext.asAbsolutePath(path.join(
FileNames.resourcesFolderName, 'arm',
'streamanalytics-input-iothub.json'));
const asaIoTHubArmTemplate =
JSON.parse(fs.readFileSync(asaIoTHubArmTemplatePath, 'utf8')) as
ARMTemplate;
const asaIotHubArmParameters = {
streamAnalyticsJobName: { value: streamAnalyticsJobName },
inputName: { value: `iothub-${componentConfig.id}` },
iotHubName: { value: iotHubName },
iotHubKeyName: { value: iotHubKeyName },
iotHubKey: { value: iotHubKey }
};
const asaInputDeploy = await AzureUtility.deployARMTemplate(
asaIoTHubArmTemplate, asaIotHubArmParameters);
if (!asaInputDeploy) {
throw new Error('Provision Stream Analytics Job failed.');
default: {
throw new Error(
`Not supported ASA input type: ${componentConfig.type}.`
);
}
break;
}
default: {
throw new Error(
`Not supported ASA input type: ${componentConfig.type}.`);
}
}
} else {
switch (componentConfig.type) {
case 'CosmosDB': {
if (!componentConfig.componentInfo) {
return false;
}
const cosmosDBAccountName =
componentConfig.componentInfo.values.cosmosDBAccountName;
const cosmosDBDatabase =
componentConfig.componentInfo.values.cosmosDBDatabase;
const cosmosDBCollection =
componentConfig.componentInfo.values.cosmosDBCollection;
if (!cosmosDBAccountName || !cosmosDBDatabase ||
!cosmosDBCollection) {
throw new Error('Cannot get Cosmos DB connection information.');
}
case "CosmosDB": {
if (!componentConfig.componentInfo) {
return false;
}
const cosmosDBAccountName =
componentConfig.componentInfo.values.cosmosDBAccountName;
const cosmosDBDatabase =
componentConfig.componentInfo.values.cosmosDBDatabase;
const cosmosDBCollection =
componentConfig.componentInfo.values.cosmosDBCollection;
if (
!cosmosDBAccountName ||
!cosmosDBDatabase ||
!cosmosDBCollection
) {
throw new Error("Cannot get Cosmos DB connection information.");
}
const asaCosmosDBArmTemplatePath =
this.extensionContext.asAbsolutePath(path.join(
FileNames.resourcesFolderName, 'arm',
'streamanalytics-output-cosmosdb.json'));
const asaCosmosDBArmTemplate =
JSON.parse(fs.readFileSync(
asaCosmosDBArmTemplatePath, 'utf8')) as ARMTemplate;
const asaCosmosArmParameters = {
streamAnalyticsJobName: { value: streamAnalyticsJobName },
outputName: { value: `cosmosdb-${componentConfig.id}` },
cosmosDBName: { value: cosmosDBAccountName },
cosmosDBDatabase: { value: cosmosDBDatabase },
cosmosDBCollection: { value: cosmosDBCollection }
};
const asaCosmosDBArmTemplatePath = this.extensionContext.asAbsolutePath(
path.join(
FileNames.resourcesFolderName,
"arm",
"streamanalytics-output-cosmosdb.json"
)
);
const asaCosmosDBArmTemplate = JSON.parse(
fs.readFileSync(asaCosmosDBArmTemplatePath, "utf8")
) as ARMTemplate;
const asaCosmosArmParameters = {
streamAnalyticsJobName: { value: streamAnalyticsJobName },
outputName: { value: `cosmosdb-${componentConfig.id}` },
cosmosDBName: { value: cosmosDBAccountName },
cosmosDBDatabase: { value: cosmosDBDatabase },
cosmosDBCollection: { value: cosmosDBCollection }
};
const asaOutputDeploy = await AzureUtility.deployARMTemplate(
asaCosmosDBArmTemplate, asaCosmosArmParameters);
if (!asaOutputDeploy) {
throw new Error('Provision Stream Analytics Job failed.');
const asaOutputDeploy = await AzureUtility.deployARMTemplate(
asaCosmosDBArmTemplate,
asaCosmosArmParameters
);
if (!asaOutputDeploy) {
throw new Error("Provision Stream Analytics Job failed.");
}
break;
}
default: {
throw new Error(
`Not supported ASA output type: ${componentConfig.type}.`
);
}
break;
}
default: {
throw new Error(
`Not supported ASA output type: ${componentConfig.type}.`);
}
}
}
}
@ -373,94 +442,114 @@ export class StreamAnalyticsJob implements Component, Provisionable,
if (this.channel) {
channelShowAndAppendLine(
this.channel, 'Stream Analytics Job provision succeeded.');
this.channel,
"Stream Analytics Job provision succeeded."
);
}
return true;
}
async deploy(): Promise<boolean> {
const azureClient = this.azureClient || await this.initAzureClient();
const azureClient = this.azureClient || (await this.initAzureClient());
// Stop Job
let stopPending: NodeJS.Timer|null = null;
let stopPending: NodeJS.Timer | null = null;
if (this.channel) {
channelShowAndAppendLine(
this.channel, 'Stopping Stream Analytics Job...');
this.channel,
"Stopping Stream Analytics Job..."
);
stopPending = setInterval(() => {
this.channel.append('.');
this.channel.append(".");
}, 1000);
}
const jobStopped = await this.stop();
if (!jobStopped) {
if (this.channel) {
channelShowAndAppendLine(
this.channel, 'Stop Stream Analytics Job failed.');
this.channel,
"Stop Stream Analytics Job failed."
);
}
return false;
} else {
if (this.channel && stopPending) {
clearInterval(stopPending);
channelShowAndAppendLine(this.channel, '.');
channelShowAndAppendLine(this.channel, ".");
channelShowAndAppendLine(
this.channel, 'Stop Stream Analytics Job succeeded.');
this.channel,
"Stop Stream Analytics Job succeeded."
);
}
}
const resourceId = `/subscriptions/${this.subscriptionId}/resourceGroups/${
this.resourceGroup}/providers/Microsoft.StreamAnalytics/streamingjobs/${
this.streamAnalyticsJobName}/transformations/Transformation`;
const apiVersion = '2015-10-01';
const resourceId = `/subscriptions/${this.subscriptionId}/resourceGroups/${this.resourceGroup}\
/providers/Microsoft.StreamAnalytics/streamingjobs/${this.streamAnalyticsJobName}/transformations/Transformation`;
const apiVersion = "2015-10-01";
if (!fs.existsSync(this.queryPath)) {
throw new Error(`Cannot find query file at ${this.queryPath}`);
}
const query = fs.readFileSync(this.queryPath, 'utf8');
const query = fs.readFileSync(this.queryPath, "utf8");
const parameters = { properties: { streamingUnits: 1, query } };
let deployPending: NodeJS.Timer|null = null;
let deployPending: NodeJS.Timer | null = null;
try {
if (this.channel) {
channelShowAndAppendLine(
this.channel, 'Deploying Stream Analytics Job...');
this.channel,
"Deploying Stream Analytics Job..."
);
deployPending = setInterval(() => {
this.channel.append('.');
this.channel.append(".");
}, 1000);
}
const deployment = await azureClient.resources.createOrUpdateById(
resourceId, apiVersion, parameters);
resourceId,
apiVersion,
parameters
);
if (this.channel && deployPending) {
clearInterval(deployPending);
channelShowAndAppendLine(this.channel, '.');
channelShowAndAppendLine(this.channel, ".");
channelPrintJsonObject(this.channel, deployment);
channelShowAndAppendLine(
this.channel, 'Stream Analytics Job query deploy succeeded.');
this.channel,
"Stream Analytics Job query deploy succeeded."
);
}
// Start Job
let startPending: NodeJS.Timer|null = null;
let startPending: NodeJS.Timer | null = null;
if (this.channel) {
channelShowAndAppendLine(
this.channel, 'Starting Stream Analytics Job...');
this.channel,
"Starting Stream Analytics Job..."
);
startPending = setInterval(() => {
this.channel.append('.');
this.channel.append(".");
}, 1000);
}
const jobStarted = await this.start();
if (!jobStarted) {
if (this.channel) {
channelShowAndAppendLine(
this.channel, 'Start Stream Analytics Job failed.');
this.channel,
"Start Stream Analytics Job failed."
);
}
return false;
} else if (this.channel && startPending) {
clearInterval(startPending);
channelShowAndAppendLine(this.channel, '.');
channelShowAndAppendLine(this.channel, ".");
channelShowAndAppendLine(
this.channel, 'Start Stream Analytics Job succeeded.');
this.channel,
"Start Stream Analytics Job succeeded."
);
}
} catch (error) {
if (this.channel && deployPending) {
clearInterval(deployPending);
channelShowAndAppendLine(this.channel, '.');
channelShowAndAppendLine(this.channel, ".");
}
throw error;
}
@ -474,16 +563,15 @@ export class StreamAnalyticsJob implements Component, Provisionable,
async start(): Promise<unknown> {
return await this.callAction(StreamAnalyticsAction.Start);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async getState(): Promise<any> {
const azureClient = this.azureClient || await this.initAzureClient();
const azureClient = this.azureClient || (await this.initAzureClient());
const resourceId = `/subscriptions/${this.subscriptionId}/resourceGroups/${
this.resourceGroup}/providers/Microsoft.StreamAnalytics/streamingjobs/${
this.streamAnalyticsJobName}`;
const apiVersion = '2015-10-01';
const resourceId = `/subscriptions/${this.subscriptionId}/resourceGroups/${this.resourceGroup}\
/providers/Microsoft.StreamAnalytics/streamingjobs/${this.streamAnalyticsJobName}`;
const apiVersion = "2015-10-01";
const res = await azureClient.resources.getById(resourceId, apiVersion);
return res.properties.jobState;
}
}
}

Просмотреть файл

@ -1,31 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
"use strict";
import * as vscode from 'vscode';
import * as utils from './utils';
import * as path from 'path';
import * as vscode from "vscode";
import * as utils from "./utils";
import * as path from "path";
import { TelemetryContext } from './telemetry';
import { ScaffoldType, PlatformType } from './constants';
import { RemoteExtension } from './Models/RemoteExtension';
import { IoTWorkbenchProjectBase, OpenScenario } from './Models/IoTWorkbenchProjectBase';
import { ProjectHostType } from './Models/Interfaces/ProjectHostType';
import { configExternalCMakeProjectToIoTContainerProject } from './utils';
import { CancelOperationError } from './CancelOperationError';
import { TelemetryContext } from "./telemetry";
import { ScaffoldType, PlatformType } from "./constants";
import { RemoteExtension } from "./Models/RemoteExtension";
import {
IoTWorkbenchProjectBase,
OpenScenario
} from "./Models/IoTWorkbenchProjectBase";
import { ProjectHostType } from "./Models/Interfaces/ProjectHostType";
import { configExternalCMakeProjectToIoTContainerProject } from "./utils";
import { CancelOperationError } from "./CancelOperationError";
const impor = require('impor')(__dirname);
const ioTWorkspaceProjectModule = impor('./Models/IoTWorkspaceProject') as
typeof import('./Models/IoTWorkspaceProject');
const ioTContainerizedProjectModule =
impor('./Models/IoTContainerizedProject') as
typeof import('./Models/IoTContainerizedProject');
const impor = require("impor")(__dirname);
const ioTWorkspaceProjectModule = impor(
"./Models/IoTWorkspaceProject"
) as typeof import("./Models/IoTWorkspaceProject");
const ioTContainerizedProjectModule = impor(
"./Models/IoTContainerizedProject"
) as typeof import("./Models/IoTContainerizedProject");
export class ProjectEnvironmentConfiger {
async configureCmakeProjectEnvironment(
context: vscode.ExtensionContext, channel: vscode.OutputChannel,
telemetryContext: TelemetryContext): Promise<void> {
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> {
// Only configure project when not in remote environment
const isLocal = RemoteExtension.checkLocalBeforeRunCommand(context);
if (!isLocal) {
@ -41,43 +47,56 @@ export class ProjectEnvironmentConfiger {
await vscode.window.withProgress(
{
title: 'CMake Project development container configuration',
location: vscode.ProgressLocation.Window,
title: "CMake Project development container configuration",
location: vscode.ProgressLocation.Window
},
async () => {
await ProjectEnvironmentConfiger
.configureProjectEnvironmentAsPlatform(
context, channel, telemetryContext,
PlatformType.EmbeddedLinux, projectRootPath, scaffoldType);
await ProjectEnvironmentConfiger.configureProjectEnvironmentAsPlatform(
context,
channel,
telemetryContext,
PlatformType.EmbeddedLinux,
projectRootPath,
scaffoldType
);
const message =
`Successfully configured development container for CMake project.`;
const message = `Successfully configured development container for CMake project.`;
utils.channelShowAndAppendLine(channel, message);
vscode.window.showInformationMessage(message);
});
}
);
return;
}
static async configureProjectEnvironmentAsPlatform(
context: vscode.ExtensionContext, channel: vscode.OutputChannel,
telemetryContext: TelemetryContext, platform: PlatformType,
projectFileRootPath: string, scaffoldType: ScaffoldType): Promise<void> {
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext,
platform: PlatformType,
projectFileRootPath: string,
scaffoldType: ScaffoldType
): Promise<void> {
let project;
if (platform === PlatformType.Arduino) {
// Verify it is an iot workbench Arduino project
const projectHostType = await IoTWorkbenchProjectBase.getProjectType(
scaffoldType, projectFileRootPath);
scaffoldType,
projectFileRootPath
);
if (projectHostType !== ProjectHostType.Workspace) {
const message =
`This is not an iot workbench Arduino project. You cannot configure it as Arduino platform.`;
const message = `This is not an iot workbench Arduino project. You cannot configure it as Arduino platform.`;
vscode.window.showWarningMessage(message);
throw new CancelOperationError(message);
}
const projectRootPath = path.join(projectFileRootPath, '..');
const projectRootPath = path.join(projectFileRootPath, "..");
project = new ioTWorkspaceProjectModule.IoTWorkspaceProject(
context, channel, telemetryContext, projectRootPath);
context,
channel,
telemetryContext,
projectRootPath
);
if (!project) {
// Ensure the project is correctly open.
await utils.properlyOpenIoTWorkspaceProject(telemetryContext);
@ -90,18 +109,27 @@ export class ProjectEnvironmentConfiger {
await RemoteExtension.checkRemoteExtension();
project = new ioTContainerizedProjectModule.IoTContainerizedProject(
context, channel, telemetryContext, projectFileRootPath);
context,
channel,
telemetryContext,
projectFileRootPath
);
} else {
throw new Error('unsupported platform');
throw new Error("unsupported platform");
}
await project.load(scaffoldType);
// Add configuration files
await project.configureProjectEnvironmentCore(
projectFileRootPath, scaffoldType);
projectFileRootPath,
scaffoldType
);
await project.openProject(
scaffoldType, false, OpenScenario.configureProject);
scaffoldType,
false,
OpenScenario.configureProject
);
}
}
}

Просмотреть файл

@ -1,32 +1,34 @@
'use strict';
"use strict";
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as vscode from "vscode";
import * as fs from "fs";
export class WorkbenchExtension {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static extension: vscode.Extension<any>|undefined;
private static extension: vscode.Extension<any> | undefined;
static getExtension(context: vscode.ExtensionContext):
// eslint-disable-next-line @typescript-eslint/no-explicit-any
vscode.Extension<any>|undefined {
static getExtension(
context: vscode.ExtensionContext
): // eslint-disable-next-line @typescript-eslint/no-explicit-any
vscode.Extension<any> | undefined {
if (!WorkbenchExtension.extension) {
const extensionId = WorkbenchExtension.getExtensionId(context);
WorkbenchExtension.extension =
vscode.extensions.getExtension(extensionId);
WorkbenchExtension.extension = vscode.extensions.getExtension(
extensionId
);
}
return WorkbenchExtension.extension;
}
private static getExtensionId(context: vscode.ExtensionContext): string {
// Get extensionId from package.json
const packageJsonPath = context.asAbsolutePath('./package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
const extensionId = packageJson.publisher + '.' + packageJson.name;
const packageJsonPath = context.asAbsolutePath("./package.json");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
const extensionId = packageJson.publisher + "." + packageJson.name;
if (!extensionId) {
throw new Error('Fail to get extension id from package.json.');
throw new Error("Fail to get extension id from package.json.");
}
return extensionId;
}
}
}

48
src/azure-account.api.d.ts поставляемый
Просмотреть файл

@ -3,37 +3,41 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vscode';
import { ServiceClientCredentials } from 'ms-rest';
import { AzureEnvironment } from 'ms-rest-azure';
import { SubscriptionModels } from 'azure-arm-resource';
import { Event } from "vscode";
import { ServiceClientCredentials } from "ms-rest";
import { AzureEnvironment } from "ms-rest-azure";
import { SubscriptionModels } from "azure-arm-resource";
export type AzureLoginStatus = 'Initializing' | 'LoggingIn' | 'LoggedIn' | 'LoggedOut';
export type AzureLoginStatus =
| "Initializing"
| "LoggingIn"
| "LoggedIn"
| "LoggedOut";
export interface AzureAccount {
readonly status: AzureLoginStatus;
readonly onStatusChanged: Event<AzureLoginStatus>;
readonly waitForLogin: () => Promise<boolean>;
readonly sessions: AzureSession[];
readonly onSessionsChanged: Event<void>;
readonly filters: AzureResourceFilter[];
readonly onFiltersChanged: Event<void>;
readonly status: AzureLoginStatus;
readonly onStatusChanged: Event<AzureLoginStatus>;
readonly waitForLogin: () => Promise<boolean>;
readonly sessions: AzureSession[];
readonly onSessionsChanged: Event<void>;
readonly filters: AzureResourceFilter[];
readonly onFiltersChanged: Event<void>;
}
export interface AzureSession {
readonly environment: AzureEnvironment;
readonly userId: string;
readonly tenantId: string;
readonly credentials: ServiceClientCredentials;
readonly environment: AzureEnvironment;
readonly userId: string;
readonly tenantId: string;
readonly credentials: ServiceClientCredentials;
}
export interface AzureResourceFilter {
readonly session: AzureSession;
readonly subscription: SubscriptionModels.Subscription;
readonly session: AzureSession;
readonly subscription: SubscriptionModels.Subscription;
}
export interface Credentials {
readSecret(service: string, account: string): Thenable<string | undefined>;
writeSecret(service: string, account: string, secret: string): Thenable<void>;
deleteSecret(service: string, account: string): Thenable<boolean>;
}
readSecret(service: string, account: string): Thenable<string | undefined>;
writeSecret(service: string, account: string, secret: string): Thenable<void>;
deleteSecret(service: string, account: string): Thenable<boolean>;
}

Просмотреть файл

@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
import { FileNames } from './constants';
import { Board } from './Models/Interfaces/Board';
import * as path from 'path';
"use strict";
import { FileNames } from "./constants";
import { Board } from "./Models/Interfaces/Board";
import * as path from "path";
interface BoardList {
boards: Board[];
@ -15,8 +15,8 @@ export interface BoardOption {
id?: string;
platform?: string;
defaultBaudRate?: number;
vendorId?: string|number;
productId?: string|number;
vendorId?: string | number;
productId?: string | number;
}
export class BoardProvider {
@ -26,13 +26,15 @@ export class BoardProvider {
}
get list(): Board[] {
const boardList =
path.join(this.boardFolderPath, FileNames.boardListFileName);
const boardList = path.join(
this.boardFolderPath,
FileNames.boardListFileName
);
const boardsJson: BoardList = require(boardList);
return boardsJson.boards;
}
find(option: BoardOption): Board|undefined {
find(option: BoardOption): Board | undefined {
const list = this.list;
return list.find(board => {
for (const key of Object.keys(option)) {
@ -47,10 +49,11 @@ export class BoardProvider {
return false;
}
if (key === 'vendorId' || key === 'productId') {
const optionId = typeof optionProperty.value === 'number' ?
optionProperty.value :
Number(`0x${optionProperty.value}`);
if (key === "vendorId" || key === "productId") {
const optionId =
typeof optionProperty.value === "number"
? optionProperty.value
: Number(`0x${optionProperty.value}`);
const boardId = Number(`0x${boardProperty.value}`);
if (optionId !== boardId) {
return false;
@ -62,4 +65,4 @@ export class BoardProvider {
return true;
});
}
}
}

Просмотреть файл

@ -1,52 +1,52 @@
export enum WorkbenchCommands {
// workbench commands
InitializeProject = 'iotworkbench.initializeProject',
ConfigureProjectEnvironment = 'iotworkbench.configureProjectEnvironment',
Examples = 'iotworkbench.examples',
Help = 'iotworkbench.help',
Workbench = 'iotworkbench.workbench',
DeviceCompile = 'iotworkbench.deviceCompile',
DeviceUpload = 'iotworkbench.deviceUpload',
AzureProvision = 'iotworkbench.azureProvision',
AzureDeploy = 'iotworkbench.azureDeploy',
ConfigureDevice = 'iotworkbench.configureDevice',
IotPnPGenerateCode = 'iotworkbench.iotPnPGenerateCode',
InitializeProject = "iotworkbench.initializeProject",
ConfigureProjectEnvironment = "iotworkbench.configureProjectEnvironment",
Examples = "iotworkbench.examples",
Help = "iotworkbench.help",
Workbench = "iotworkbench.workbench",
DeviceCompile = "iotworkbench.deviceCompile",
DeviceUpload = "iotworkbench.deviceUpload",
AzureProvision = "iotworkbench.azureProvision",
AzureDeploy = "iotworkbench.azureDeploy",
ConfigureDevice = "iotworkbench.configureDevice",
IotPnPGenerateCode = "iotworkbench.iotPnPGenerateCode",
// Workbench internal commands
ExampleInitialize = 'iotworkbench.exampleInitialize',
SendTelemetry = 'iotworkbench.sendTelemetry',
OpenUri = 'iotworkbench.openUri',
HttpRequest = 'iotworkbench.httpRequest'
ExampleInitialize = "iotworkbench.exampleInitialize",
SendTelemetry = "iotworkbench.sendTelemetry",
OpenUri = "iotworkbench.openUri",
HttpRequest = "iotworkbench.httpRequest"
}
export enum VscodeCommands {
// vscode commands
VscodeOpen = 'vscode.open',
VscodeOpenFolder = 'vscode.openFolder'
VscodeOpen = "vscode.open",
VscodeOpenFolder = "vscode.openFolder"
}
export enum RemoteContainersCommands {
// remote-containers commands
ReopenInContainer = 'remote-containers.reopenInContainer',
OpenFolder = 'remote-containers.openFolder'
ReopenInContainer = "remote-containers.reopenInContainer",
OpenFolder = "remote-containers.openFolder"
}
export enum ArduinoCommands {
// arduino commands
InstallBoard = 'arduino.installBoard',
CloseSerialMonitor = 'arduino.closeSerialMonitor',
InstallBoard = "arduino.installBoard",
CloseSerialMonitor = "arduino.closeSerialMonitor"
}
export enum AzureAccountCommands {
Login = 'azure-account.login',
Login = "azure-account.login"
}
export enum AzureFunctionsCommands {
CreateNewProject = 'azureFunctions.createNewProject',
CreateFunctionApp = 'azureFunctions.createFunctionApp',
Deploy = 'azureFunctions.deploy'
CreateNewProject = "azureFunctions.createNewProject",
CreateFunctionApp = "azureFunctions.createFunctionApp",
Deploy = "azureFunctions.deploy"
}
export enum IoTCubeCommands {
OpenLocally = 'iotcube.openLocally'
}
OpenLocally = "iotcube.openLocally"
}

Просмотреть файл

@ -1,9 +1,9 @@
import { InternalError } from './InternalError';
import { InternalError } from "./InternalError";
export class BoardNotFoundError extends InternalError {
constructor (board: string) {
constructor(board: string) {
const errorMessage = `${board} is not found in board list.`;
super(errorMessage);
this.name = 'BoardNotFoundError';
this.name = "BoardNotFoundError";
}
}

Просмотреть файл

@ -1,13 +1,13 @@
import { ExtensionName } from '../../Models/Interfaces/Api';
import { ExtensionName } from "../../Models/Interfaces/Api";
// User Error
/**
* Error class used when user cancel operation.
*/
export class OperationCanceledError extends Error {
constructor (message: string) {
constructor(message: string) {
super(`Operation cancelled: ${message}`);
this.name = 'CancelOperationError';
this.name = "CancelOperationError";
}
}
@ -19,10 +19,11 @@ export class RemoteEnvNotSupportedError extends Error {
* Construct a remote environemt not supported error.
* @param suggestedOperation message of the recommended operation for user
*/
constructor (suggestedOperation: string) {
super(`The operation is not supported to be run in remote environment. ${
suggestedOperation}`);
this.name = 'RemoteEnvNotSupportedError';
constructor(suggestedOperation: string) {
super(
`The operation is not supported to be run in remote environment. ${suggestedOperation}`
);
this.name = "RemoteEnvNotSupportedError";
}
}
@ -35,85 +36,93 @@ export class ResourceNotFoundError extends Error {
* @param resource The name of resource that is missing
* @param suggestedOperation Recommended operation for user.
*/
constructor (
operation: string, resource: string, suggestedOperation?: string) {
super(`Failed to ${operation}: Unable to find ${resource}. ${
suggestedOperation}`);
this.name = 'ResourceNotFoundError';
constructor(
operation: string,
resource: string,
suggestedOperation?: string
) {
super(
`Failed to ${operation}: Unable to find ${resource}. ${suggestedOperation}`
);
this.name = "ResourceNotFoundError";
}
}
export class DependentExtensionNotFoundError extends Error {
constructor (extension: ExtensionName) {
super(`Dependent extension ${
extension} is not found. Please install it from Marketplace.`);
this.name = 'DependentExtensionNotFound';
constructor(extension: ExtensionName) {
super(
`Dependent extension ${extension} is not found. Please install it from Marketplace.`
);
this.name = "DependentExtensionNotFound";
}
}
export class WorkspaceNotOpenError extends Error {
constructor () {
constructor() {
super(
'You have not yet opened a folder in Visual Studio Code. Please select a folder first.');
this.name = 'WorkspaceNotOpenError';
"You have not yet opened a folder in Visual Studio Code. Please select a folder first."
);
this.name = "WorkspaceNotOpenError";
}
}
export class PrerequisiteNotMetError extends Error {
constructor (operation: string, suggestedOperation?: string) {
super(`Failed to ${operation} because prerequisite is not met. ${
suggestedOperation}`);
this.name = 'PrerequisiteNotMetError';
constructor(operation: string, suggestedOperation?: string) {
super(
`Failed to ${operation} because prerequisite is not met. ${suggestedOperation}`
);
this.name = "PrerequisiteNotMetError";
}
}
// System Error
export class OperationFailedError extends Error {
constructor (operation: string, suggestedOperation?: string) {
constructor(operation: string, suggestedOperation?: string) {
super(`Failed to ${operation}. ${suggestedOperation}`);
this.name = 'OperationFailedError';
this.name = "OperationFailedError";
}
}
export class BoardNotFoundError extends Error {
constructor (board: string) {
constructor(board: string) {
super(`${board} is not found in board list.`);
this.name = 'BoardNotFoundError';
this.name = "BoardNotFoundError";
}
}
export class ConfigNotFoundError extends Error {
constructor (configKey: string, suggestedOperation?: string) {
super(`Failed to get ${configKey} from workspace settings. ${
suggestedOperation}`);
this.name = 'ConfigNotFoundError';
constructor(configKey: string, suggestedOperation?: string) {
super(
`Failed to get ${configKey} from workspace settings. ${suggestedOperation}`
);
this.name = "ConfigNotFoundError";
}
}
export class TypeNotSupportedError extends Error {
constructor (typeName: string, typeValue: string) {
constructor(typeName: string, typeValue: string) {
super(`Unsupported ${typeName}: ${typeValue}`);
this.name = 'TypeNotSupportedError';
this.name = "TypeNotSupportedError";
}
}
export class InternalError extends Error {
constructor (message: string) {
constructor(message: string) {
super(`Internal Error: ${message}.`);
this.name = 'InternalError';
this.name = "InternalError";
}
}
export class ArgumentEmptyOrNullError extends Error {
constructor (argument: string, suggestedOperation?: string) {
constructor(argument: string, suggestedOperation?: string) {
super(`Argument ${argument} is empty or null. ${suggestedOperation}`);
this.name = 'ArgumentEmptyOrNullError';
this.name = "ArgumentEmptyOrNullError";
}
}
export class ArgumentInvalidError extends Error {
constructor (argument: string, suggestedOperation?: string) {
constructor(argument: string, suggestedOperation?: string) {
super(`${argument} is invalid. ${suggestedOperation}`);
this.name = 'ArgumentInvalidError';
this.name = "ArgumentInvalidError";
}
}

Просмотреть файл

@ -1,7 +1,7 @@
export class InternalError extends Error {
constructor (errorMessage: string) {
constructor(errorMessage: string) {
super(`Internal Error: ${errorMessage}.`);
this.name = 'InternalError';
this.name = "InternalError";
}
}
/*
@ -10,15 +10,15 @@ export class InternalError extends Error {
*/
export class UserError extends Error {
constructor (operation: string, suggestedOperation?: string) {
constructor(operation: string, suggestedOperation?: string) {
super(`Failed to ${operation}. ${suggestedOperation}`);
this.name = 'UserError';
this.name = "UserError";
}
}
export class OperationCanceledError extends UserError {
constructor (message: string) {
constructor(message: string) {
super(`Operation cancelled: ${message}`);
this.name = 'CancelOperationError';
this.name = "CancelOperationError";
}
}

Просмотреть файл

@ -1,8 +1,8 @@
import { InternalError } from './InternalError';
import { InternalError } from "./InternalError";
export class TypeNotSupportedError extends InternalError {
constructor (typeName: string, typeValue: string) {
constructor(typeName: string, typeValue: string) {
super(`Unsupported ${typeName}: ${typeValue}`);
this.name = 'TypeNotSupportedError';
this.name = "TypeNotSupportedError";
}
}

Просмотреть файл

@ -1,24 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as vscode from 'vscode';
import * as vscode from "vscode";
export class ConfigHandler {
static async update(
key: string, value: {}, target = vscode.ConfigurationTarget.Workspace): Promise<void> {
key: string,
value: {},
target = vscode.ConfigurationTarget.Workspace
): Promise<void> {
if (!key) {
throw new Error('Key is empty.');
throw new Error("Key is empty.");
}
return await vscode.workspace.getConfiguration('IoTWorkbench')
return await vscode.workspace
.getConfiguration("IoTWorkbench")
.update(key, value, target);
}
static get<T>(key: string): T|undefined {
static get<T>(key: string): T | undefined {
if (!key) {
throw new Error('Key is empty.');
throw new Error("Key is empty.");
}
return vscode.workspace.getConfiguration('IoTWorkbench').get<T>(key);
return vscode.workspace.getConfiguration("IoTWorkbench").get<T>(key);
}
}
}

Просмотреть файл

@ -3,119 +3,119 @@
export class ConfigKey {
// Keys for condifurations in User / Workspace settings
static readonly workbench = 'workbench';
static readonly devicePath = 'DevicePath';
static readonly functionPath = 'FunctionPath';
static readonly iotHubConnectionString = 'iothubConnectionString';
static readonly iotHubDeviceConnectionString = 'iothubDeviceConnectionString';
static readonly eventHubConnectionString = 'eventHubConnectionString';
static readonly eventHubConnectionPath = 'eventHubConnectionPath';
static readonly functionAppId = 'functionAppId';
static readonly boardId = 'BoardId';
static readonly codeGeneratorVersion = 'IoTPnPCodeGenVersion';
static readonly asaPath = 'StreamAnalyticsPath';
static readonly workbench = "workbench";
static readonly devicePath = "DevicePath";
static readonly functionPath = "FunctionPath";
static readonly iotHubConnectionString = "iothubConnectionString";
static readonly iotHubDeviceConnectionString = "iothubDeviceConnectionString";
static readonly eventHubConnectionString = "eventHubConnectionString";
static readonly eventHubConnectionPath = "eventHubConnectionPath";
static readonly functionAppId = "functionAppId";
static readonly boardId = "BoardId";
static readonly codeGeneratorVersion = "IoTPnPCodeGenVersion";
static readonly asaPath = "StreamAnalyticsPath";
// Keys for configurations in iot workbench project config file
static readonly projectHostType = 'ProjectHostType';
static readonly workbenchVersion = 'version';
static readonly projectHostType = "ProjectHostType";
static readonly workbenchVersion = "version";
// Keys for configurations in global state
static readonly hasPopUp = 'hasPopUp';
static readonly hasPopUp = "hasPopUp";
}
export class EventNames {
static readonly createNewProjectEvent = 'IoTWorkbench.NewProject';
static readonly createNewProjectEvent = "IoTWorkbench.NewProject";
static readonly configProjectEnvironmentEvent =
'IoTWorkbench.ConfigProjectEnvironment';
static readonly azureProvisionEvent = 'IoTWorkbench.AzureProvision';
static readonly azureDeployEvent = 'IoTWorkbench.AzureDeploy';
"IoTWorkbench.ConfigProjectEnvironment";
static readonly azureProvisionEvent = "IoTWorkbench.AzureProvision";
static readonly azureDeployEvent = "IoTWorkbench.AzureDeploy";
static readonly createAzureFunctionsEvent =
'IoTWorkbench.CreateAzureFunctions';
static readonly deviceCompileEvent = 'IoTWorkbench.DeviceCompile';
static readonly deviceUploadEvent = 'IoTWorkbench.DeviceUpload';
static readonly devicePackageEvent = 'IoTWorkbench.DevicePackage';
"IoTWorkbench.CreateAzureFunctions";
static readonly deviceCompileEvent = "IoTWorkbench.DeviceCompile";
static readonly deviceUploadEvent = "IoTWorkbench.DeviceUpload";
static readonly devicePackageEvent = "IoTWorkbench.DevicePackage";
static readonly configDeviceSettingsEvent =
'IoTWorkbench.ConfigDeviceSettingsEvent';
static readonly openExamplePageEvent = 'IoTWorkbench.OpenExamplePage';
static readonly loadExampleEvent = 'IoTWorkbench.loadExample';
static readonly detectBoard = 'IoTWorkbench.DetectBoard';
static readonly generateOtaCrc = 'IoTWorkbench.GenerateOtaCrc';
static readonly nsatsurvery = 'IoTWorkbench.NSATSurvey';
static readonly selectSubscription = 'IoTWorkbench.SelectSubscription';
static readonly openTutorial = 'IoTWorkbench.OpenTutorial';
static readonly projectLoadEvent = 'IoTWorkbench.ProjectLoadEvent';
static readonly scaffoldDeviceStubEvent = 'IoTWorkbench.ScaffoldDeviceStub';
static readonly help = 'IoTWorkbench.Help';
static readonly setProjectDefaultPath = 'IoTWorkbench.SetDefaultPath';
"IoTWorkbench.ConfigDeviceSettingsEvent";
static readonly openExamplePageEvent = "IoTWorkbench.OpenExamplePage";
static readonly loadExampleEvent = "IoTWorkbench.loadExample";
static readonly detectBoard = "IoTWorkbench.DetectBoard";
static readonly generateOtaCrc = "IoTWorkbench.GenerateOtaCrc";
static readonly nsatsurvery = "IoTWorkbench.NSATSurvey";
static readonly selectSubscription = "IoTWorkbench.SelectSubscription";
static readonly openTutorial = "IoTWorkbench.OpenTutorial";
static readonly projectLoadEvent = "IoTWorkbench.ProjectLoadEvent";
static readonly scaffoldDeviceStubEvent = "IoTWorkbench.ScaffoldDeviceStub";
static readonly help = "IoTWorkbench.Help";
static readonly setProjectDefaultPath = "IoTWorkbench.SetDefaultPath";
}
export class FileNames {
static readonly templateFileName = 'templates.json';
static readonly boardListFileName = 'boardlist.json';
static readonly platformListFileName = 'platformlist.json';
static readonly resourcesFolderName = 'resources';
static readonly iotWorkbenchProjectFileName = '.iotworkbenchproject';
static readonly cmakeFileName = 'CMakeLists.txt';
static readonly settingsJsonFileName = 'settings.json';
static readonly codeGenOptionsFileName = 'codeGenOptions.json';
static readonly configDeviceOptionsFileName = 'configDeviceOptions.json';
static readonly devcontainerFolderName = '.devcontainer';
static readonly vscodeSettingsFolderName = '.vscode';
static readonly workspaceConfigFilePath = 'project.code-workspace';
static readonly iotworkbenchTempFolder = '.iotworkbenchtemp';
static readonly workspaceExtensionName = '.code-workspace';
static readonly cacheFolderName = 'cache';
static readonly outputPathName = 'cmake';
static readonly templatesFolderName = 'templates';
static readonly templateFiles = 'templatefiles.json';
static readonly installPackagesFileName = 'install_packages.sh';
static readonly templateFileName = "templates.json";
static readonly boardListFileName = "boardlist.json";
static readonly platformListFileName = "platformlist.json";
static readonly resourcesFolderName = "resources";
static readonly iotWorkbenchProjectFileName = ".iotworkbenchproject";
static readonly cmakeFileName = "CMakeLists.txt";
static readonly settingsJsonFileName = "settings.json";
static readonly codeGenOptionsFileName = "codeGenOptions.json";
static readonly configDeviceOptionsFileName = "configDeviceOptions.json";
static readonly devcontainerFolderName = ".devcontainer";
static readonly vscodeSettingsFolderName = ".vscode";
static readonly workspaceConfigFilePath = "project.code-workspace";
static readonly iotworkbenchTempFolder = ".iotworkbenchtemp";
static readonly workspaceExtensionName = ".code-workspace";
static readonly cacheFolderName = "cache";
static readonly outputPathName = "cmake";
static readonly templatesFolderName = "templates";
static readonly templateFiles = "templatefiles.json";
static readonly installPackagesFileName = "install_packages.sh";
}
export enum OperationType {
Compile = 'Device code compilation',
Upload = 'Device code upload'
Compile = "Device code compilation",
Upload = "Device code upload"
}
export enum AzureFunctionsLanguage {
CSharpScript = 'C#Script',
JavaScript = 'JavaScript',
CSharpLibrary = 'C#'
CSharpScript = "C#Script",
JavaScript = "JavaScript",
CSharpLibrary = "C#"
}
export enum ScaffoldType {
Local = 'local',
Workspace = 'workspace'
Local = "local",
Workspace = "workspace"
}
export class AzureComponentsStorage {
static readonly folderName = '.azurecomponent';
static readonly fileName = 'azureconfig.json';
static readonly folderName = ".azurecomponent";
static readonly fileName = "azureconfig.json";
}
export class DependentExtensions {
static readonly azureFunctions = 'ms-azuretools.vscode-azurefunctions';
static readonly arduino = 'vsciot-vscode.vscode-arduino';
static readonly remote = 'ms-vscode-remote.vscode-remote-extensionpack';
static readonly azureFunctions = "ms-azuretools.vscode-azurefunctions";
static readonly arduino = "vsciot-vscode.vscode-arduino";
static readonly remote = "ms-vscode-remote.vscode-remote-extensionpack";
}
export enum PlatformType {
Arduino = 'Arduino',
EmbeddedLinux = 'Embedded Linux (Preview)',
Unknown = 'Unknown'
Arduino = "Arduino",
EmbeddedLinux = "Embedded Linux (Preview)",
Unknown = "Unknown"
}
export enum DevelopEnvironment {
RemoteEnv = 'in remote environment',
LocalEnv = 'in local environment'
RemoteEnv = "in remote environment",
LocalEnv = "in local environment"
}
export enum TemplateTag {
General = 'general',
DevelopmentEnvironment = 'development_container'
General = "general",
DevelopmentEnvironment = "development_container"
}
export enum OSPlatform {
WIN32 = 'win32',
LINUX = 'linux',
DARWIN = 'darwin'
}
WIN32 = "win32",
LINUX = "linux",
DARWIN = "darwin"
}

Просмотреть файл

@ -1,40 +1,40 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
"use strict";
import * as fs from 'fs-plus';
import * as path from 'path';
import * as vscode from 'vscode';
import * as AdmZip from 'adm-zip';
import { IoTWorkbenchSettings } from './IoTSettings';
import * as utils from './utils';
import { Board, BoardQuickPickItem } from './Models/Interfaces/Board';
import { TelemetryContext } from './telemetry';
import { FileNames } from './constants';
import { ArduinoPackageManager } from './ArduinoPackageManager';
import { BoardProvider } from './boardProvider';
import { VSCExpress } from 'vscode-express';
import { RemoteExtension } from './Models/RemoteExtension';
import { CancelOperationError } from './CancelOperationError';
import { IoTCubeCommands } from './common/Commands';
import * as fs from "fs-plus";
import * as path from "path";
import * as vscode from "vscode";
import * as AdmZip from "adm-zip";
import { IoTWorkbenchSettings } from "./IoTSettings";
import * as utils from "./utils";
import { Board, BoardQuickPickItem } from "./Models/Interfaces/Board";
import { TelemetryContext } from "./telemetry";
import { FileNames } from "./constants";
import { ArduinoPackageManager } from "./ArduinoPackageManager";
import { BoardProvider } from "./boardProvider";
import { VSCExpress } from "vscode-express";
import { RemoteExtension } from "./Models/RemoteExtension";
import { CancelOperationError } from "./CancelOperationError";
import { IoTCubeCommands } from "./common/Commands";
type OptionsWithUri = import('request-promise').OptionsWithUri;
type OptionsWithUri = import("request-promise").OptionsWithUri;
const impor = require('impor')(__dirname);
const request = impor('request-promise') as typeof import('request-promise');
const impor = require("impor")(__dirname);
const request = impor("request-promise") as typeof import("request-promise");
export class ExampleExplorer {
private _exampleName = '';
private _exampleUrl = '';
private _boardId = '';
private _exampleName = "";
private _exampleUrl = "";
private _boardId = "";
private static _vscexpress: VSCExpress|undefined;
private static _vscexpress: VSCExpress | undefined;
private async moveTempFiles(fsPath: string): Promise<boolean> {
const tempPath = path.join(fsPath, '.temp');
const tempPath = path.join(fsPath, ".temp");
const tempPathList = fs.listSync(tempPath);
let examplePath: string|undefined = undefined;
let examplePath: string | undefined = undefined;
for (let i = 0; i < tempPathList.length; i++) {
if (!/\.zip$/.test(tempPathList[i])) {
examplePath = tempPathList[i];
@ -48,9 +48,11 @@ export class ExampleExplorer {
const examplePathList = fs.readdirSync(examplePath);
examplePathList.forEach(item => {
if (item !== '.' && item !== '..') {
if (item !== "." && item !== "..") {
fs.moveSync(
path.join(examplePath as string, item), path.join(fsPath, item));
path.join(examplePath as string, item),
path.join(fsPath, item)
);
}
});
@ -58,31 +60,35 @@ export class ExampleExplorer {
return true;
}
private async downloadExamplePackage(channel: vscode.OutputChannel, url: string, fsPath: string): Promise<boolean> {
private async downloadExamplePackage(
channel: vscode.OutputChannel,
url: string,
fsPath: string
): Promise<boolean> {
const loading = setInterval(() => {
channel.append('.');
channel.append(".");
}, 1000);
const options: OptionsWithUri = {
method: 'GET',
method: "GET",
uri: url,
encoding: null // Binary data
encoding: null // Binary data
};
const zipData = await request(options).promise() as string;
const tempPath = path.join(fsPath, '.temp');
fs.writeFileSync(path.join(tempPath, 'example.zip'), zipData);
const zip = new AdmZip(path.join(tempPath, 'example.zip'));
const zipData = (await request(options).promise()) as string;
const tempPath = path.join(fsPath, ".temp");
fs.writeFileSync(path.join(tempPath, "example.zip"), zipData);
const zip = new AdmZip(path.join(tempPath, "example.zip"));
try {
zip.extractAllTo(tempPath, true);
clearInterval(loading);
utils.channelShowAndAppendLine(channel, '');
utils.channelShowAndAppendLine(channel, 'Example loaded.');
utils.channelShowAndAppendLine(channel, "");
utils.channelShowAndAppendLine(channel, "Example loaded.");
await this.moveTempFiles(fsPath);
return true;
} catch (error) {
clearInterval(loading);
utils.channelShowAndAppendLine(channel, '');
utils.channelShowAndAppendLine(channel, "");
throw error;
}
}
@ -95,58 +101,61 @@ export class ExampleExplorer {
utils.mkdirRecursivelySync(workbench);
}
const name = path.join(workbench, 'examples', exampleName);
const name = path.join(workbench, "examples", exampleName);
if (!utils.fileExistsSync(name) && !utils.directoryExistsSync(name)) {
utils.mkdirRecursivelySync(name);
return name;
}
const workspaceFiles =
fs.listSync(name, [FileNames.workspaceExtensionName]);
const workspaceFiles = fs.listSync(name, [
FileNames.workspaceExtensionName
]);
if (workspaceFiles && workspaceFiles.length > 0) {
const workspaceFile = workspaceFiles[0]; // just pick the first one
const workspaceFile = workspaceFiles[0]; // just pick the first one
if (fs.existsSync(workspaceFile)) {
const selection = await vscode.window.showQuickPick(
[
{
label: `Open an existing example`,
description: '',
description: "",
detail: `Example exists: ${name}`
},
{
label: 'Generate a new example',
description: '',
detail: 'Create a new folder to generate the example'
label: "Generate a new example",
description: "",
detail: "Create a new folder to generate the example"
}
],
{
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select an option',
});
placeHolder: "Select an option"
}
);
if (!selection) {
return '';
return "";
}
if (selection.label === 'Open an existing example') {
if (selection.label === "Open an existing example") {
return name;
}
}
}
const customizedName = await vscode.window.showInputBox({
prompt: 'Input example folder name',
prompt: "Input example folder name",
ignoreFocusOut: true,
validateInput: (exampleName: string) => {
if (exampleName === null) {
return;
}
const name = path.join(workbench, 'examples', exampleName);
const name = path.join(workbench, "examples", exampleName);
if (!utils.fileExistsSync(name) && !utils.directoryExistsSync(name)) {
if (!/^([a-z0-9_]|[a-z0-9_][-a-z0-9_.]*[a-z0-9_])$/i.test(
exampleName)) {
if (
!/^([a-z0-9_]|[a-z0-9_][-a-z0-9_.]*[a-z0-9_])$/i.test(exampleName)
) {
return 'Folder name can only contain letters, numbers, "-" and ".", and cannot start or end with "-" or ".".';
}
return;
@ -161,26 +170,32 @@ export class ExampleExplorer {
});
if (!customizedName) {
return '';
return "";
}
const customizedPath = path.join(workbench, 'examples', customizedName);
if (!utils.fileExistsSync(customizedPath) &&
!utils.directoryExistsSync(customizedPath)) {
const customizedPath = path.join(workbench, "examples", customizedName);
if (
!utils.fileExistsSync(customizedPath) &&
!utils.directoryExistsSync(customizedPath)
) {
utils.mkdirRecursivelySync(customizedPath);
}
return customizedPath;
}
async selectBoard(context: vscode.ExtensionContext, telemetryContext: TelemetryContext): Promise<void> {
async selectBoard(
context: vscode.ExtensionContext,
telemetryContext: TelemetryContext
): Promise<void> {
const isLocal = RemoteExtension.checkLocalBeforeRunCommand(context);
if (!isLocal) {
return;
}
const boardFolderPath = context.asAbsolutePath(path.join(
FileNames.resourcesFolderName, FileNames.templatesFolderName));
const boardFolderPath = context.asAbsolutePath(
path.join(FileNames.resourcesFolderName, FileNames.templatesFolderName)
);
const boardProvider = new BoardProvider(boardFolderPath);
const boardItemList: BoardQuickPickItem[] = [];
const boards = boardProvider.list.filter(board => board.exampleUrl);
@ -190,29 +205,29 @@ export class ExampleExplorer {
id: board.id,
detailInfo: board.detailInfo,
label: board.name,
description: board.detailInfo,
description: board.detailInfo
});
});
// add the selection of 'device not in the list'
boardItemList.push({
name: '',
id: 'no_device',
detailInfo: '',
label: '$(issue-opened) My device is not in the list...',
description: '',
name: "",
id: "no_device",
detailInfo: "",
label: "$(issue-opened) My device is not in the list...",
description: ""
});
const boardSelection = await vscode.window.showQuickPick(boardItemList, {
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select a board',
placeHolder: "Select a board"
});
if (!boardSelection) {
throw new CancelOperationError('Board selection cancelled.');
} else if (boardSelection.id === 'no_device') {
throw new CancelOperationError("Board selection cancelled.");
} else if (boardSelection.id === "no_device") {
await utils.takeNoDeviceSurvey(telemetryContext, context);
return;
} else {
@ -223,36 +238,51 @@ export class ExampleExplorer {
// To avoid block example gallery, use async to install board here
// await ArduinoPackageManager.installBoard(board);
ArduinoPackageManager.installBoard(board);
const exampleUrl = 'example.html?board=' + board.id +
'&url=' + encodeURIComponent(board.exampleUrl || '');
const exampleUrl =
"example.html?board=" +
board.id +
"&url=" +
encodeURIComponent(board.exampleUrl || "");
ExampleExplorer._vscexpress =
ExampleExplorer._vscexpress || new VSCExpress(context, 'views');
ExampleExplorer._vscexpress || new VSCExpress(context, "views");
ExampleExplorer._vscexpress.open(
exampleUrl,
board.examplePageName + ' samples - Azure IoT Device Workbench',
vscode.ViewColumn.One, {
board.examplePageName + " samples - Azure IoT Device Workbench",
vscode.ViewColumn.One,
{
enableScripts: true,
enableCommandUris: true,
retainContextWhenHidden: true
});
}
);
}
}
}
async initializeExample(context: vscode.ExtensionContext, channel: vscode.OutputChannel, telemetryContext: TelemetryContext, name?: string, url?: string, boardId?: string): Promise<void> {
async initializeExample(
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext,
name?: string,
url?: string,
boardId?: string
): Promise<void> {
if (name && url && boardId) {
this._exampleName = name;
this._exampleUrl = url;
this._boardId = boardId;
}
const res = await this.initializeExampleInternal(
context, channel, telemetryContext);
context,
channel,
telemetryContext
);
if (!res) {
throw new CancelOperationError(`Example load cancelled.`);
}
vscode.window.showInformationMessage('Example load successfully.');
vscode.window.showInformationMessage("Example load successfully.");
}
setSelectedExample(name: string, url: string, boardId: string): void {
@ -262,20 +292,26 @@ export class ExampleExplorer {
}
private async initializeExampleInternal(
context: vscode.ExtensionContext, channel: vscode.OutputChannel,
telemetryContext: TelemetryContext): Promise<boolean> {
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<boolean> {
if (!this._exampleName || !this._exampleUrl) {
return false;
}
const boardList = context.asAbsolutePath(path.join(
FileNames.resourcesFolderName, FileNames.templatesFolderName,
FileNames.boardListFileName));
const boardsJson: {boards: Board[]} = require(boardList);
const boardList = context.asAbsolutePath(
path.join(
FileNames.resourcesFolderName,
FileNames.templatesFolderName,
FileNames.boardListFileName
)
);
const boardsJson: { boards: Board[] } = require(boardList);
telemetryContext.properties.Example = this._exampleName;
const board = boardsJson.boards.find(board => board.id === this._boardId);
telemetryContext.properties.board = board ? board.name : '';
telemetryContext.properties.board = board ? board.name : "";
const url = this._exampleUrl;
const fsPath = await this.generateExampleFolder(this._exampleName);
@ -287,30 +323,38 @@ export class ExampleExplorer {
const items = fs.listSync(fsPath, [FileNames.workspaceExtensionName]);
if (items.length !== 0) {
await vscode.commands.executeCommand(
IoTCubeCommands.OpenLocally, items[0], true);
IoTCubeCommands.OpenLocally,
items[0],
true
);
return true;
}
utils.channelShowAndAppendLine(channel, 'Downloading example package...');
const res =
await this.downloadExamplePackage(channel, url, fsPath);
utils.channelShowAndAppendLine(channel, "Downloading example package...");
const res = await this.downloadExamplePackage(channel, url, fsPath);
if (res) {
// Follow the same pattern in Arduino extension to open examples in new
// VSCode instance
const workspaceFiles =
fs.listSync(fsPath, [FileNames.workspaceExtensionName]);
const workspaceFiles = fs.listSync(fsPath, [
FileNames.workspaceExtensionName
]);
if (workspaceFiles && workspaceFiles.length > 0) {
await vscode.commands.executeCommand(
IoTCubeCommands.OpenLocally, workspaceFiles[0], true);
IoTCubeCommands.OpenLocally,
workspaceFiles[0],
true
);
return true;
} else {
// TODO: Add buttom to submit issue to iot-workbench repo.
throw new Error(
'The example does not contain a project for Azure IoT Device Workbench.');
"The example does not contain a project for Azure IoT Device Workbench."
);
}
} else {
throw new Error(
'Downloading example package failed. Please check your network settings.');
"Downloading example package failed. Please check your network settings."
);
}
}
}
}

Просмотреть файл

@ -1,31 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
"use strict";
import * as vscode from 'vscode';
import * as utils from './utils';
import * as vscode from "vscode";
import * as utils from "./utils";
export class ExceptionHelper {
static logError(
channel: vscode.OutputChannel|undefined, error: Error,
popupErrorMsg: string): void;
channel: vscode.OutputChannel | undefined,
error: Error,
popupErrorMsg: string
): void;
static logError(
channel: vscode.OutputChannel|undefined, errorMsg: string,
popupErrorMsg: string): void;
channel: vscode.OutputChannel | undefined,
errorMsg: string,
popupErrorMsg: string
): void;
static logError(
channel: vscode.OutputChannel|undefined, error: Error,
isPopupErrorMsg: boolean): void;
channel: vscode.OutputChannel | undefined,
error: Error,
isPopupErrorMsg: boolean
): void;
static logError(
channel: vscode.OutputChannel|undefined, errorMsg: string,
isPopupErrorMsg: boolean): void;
channel: vscode.OutputChannel | undefined,
errorMsg: string,
isPopupErrorMsg: boolean
): void;
static logError(
channel: vscode.OutputChannel|undefined, errorValue: string|Error,
popupValue: string|boolean): void {
channel: vscode.OutputChannel | undefined,
errorValue: string | Error,
popupValue: string | boolean
): void {
let _error: Error;
let _message: string;
if (typeof errorValue === 'string') {
if (typeof errorValue === "string") {
_error = new Error(errorValue);
_message = errorValue;
} else {
@ -35,7 +45,7 @@ export class ExceptionHelper {
if (popupValue === true) {
vscode.window.showErrorMessage(_message);
} else if (typeof popupValue === 'string') {
} else if (typeof popupValue === "string") {
vscode.window.showErrorMessage(popupValue);
}

Просмотреть файл

@ -1,45 +1,53 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
"use strict";
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import * as path from 'path';
import { BoardProvider } from './boardProvider';
import { ProjectInitializer } from './projectInitializer';
import { DeviceOperator } from './DeviceOperator';
import { AzureOperator } from './AzureOperator';
import { IoTWorkbenchSettings } from './IoTSettings';
import { ConfigHandler } from './configHandler';
import { CodeGeneratorCore } from './DigitalTwin/CodeGeneratorCore';
import { ConfigKey, EventNames, FileNames } from './constants';
import { TelemetryContext, TelemetryWorker, TelemetryResult } from './telemetry';
import { RemoteExtension } from './Models/RemoteExtension';
import { constructAndLoadIoTProject } from './utils';
import { ProjectEnvironmentConfiger } from './ProjectEnvironmentConfiger';
import { WorkbenchExtension } from './WorkbenchExtension';
import { WorkbenchCommands, VscodeCommands } from './common/Commands';
import { ColorizedChannel } from './DigitalTwin/pnp/src/common/colorizedChannel';
import { Constants } from './DigitalTwin/pnp/src/common/constants';
import { DeviceModelManager, ModelType } from './DigitalTwin/pnp/src/deviceModel/deviceModelManager';
import { ModelRepositoryManager } from './DigitalTwin/pnp/src/modelRepository/modelRepositoryManager';
import { IntelliSenseUtility } from './DigitalTwin/pnp/src/intelliSense/intelliSenseUtility';
import { DigitalTwinCompletionItemProvider } from './DigitalTwin/pnp/src/intelliSense/digitalTwinCompletionItemProvider';
import { DigitalTwinHoverProvider } from './DigitalTwin/pnp/src/intelliSense/digitalTwinHoverProvider';
import { DigitalTwinDiagnosticProvider } from './DigitalTwin/pnp/src/intelliSense/digitalTwinDiagnosticProvider';
import { Command } from './DigitalTwin/pnp/src/common/command';
import { UserCancelledError } from './DigitalTwin/pnp/src/common/userCancelledError';
import { UI, MessageType } from './DigitalTwin/pnp/src/view/ui';
import { ProcessError } from './DigitalTwin/pnp/src/common/processError';
import { SearchResult } from './DigitalTwin/pnp/src/modelRepository/modelRepositoryInterface';
import { NSAT } from './nsat';
import { DigitalTwinUtility } from './DigitalTwin/DigitalTwinUtility';
import * as vscode from "vscode";
import * as path from "path";
import { BoardProvider } from "./boardProvider";
import { ProjectInitializer } from "./projectInitializer";
import { DeviceOperator } from "./DeviceOperator";
import { AzureOperator } from "./AzureOperator";
import { IoTWorkbenchSettings } from "./IoTSettings";
import { ConfigHandler } from "./configHandler";
import { CodeGeneratorCore } from "./DigitalTwin/CodeGeneratorCore";
import { ConfigKey, EventNames, FileNames } from "./constants";
import {
TelemetryContext,
TelemetryWorker,
TelemetryResult
} from "./telemetry";
import { RemoteExtension } from "./Models/RemoteExtension";
import { constructAndLoadIoTProject } from "./utils";
import { ProjectEnvironmentConfiger } from "./ProjectEnvironmentConfiger";
import { WorkbenchExtension } from "./WorkbenchExtension";
import { WorkbenchCommands, VscodeCommands } from "./common/Commands";
import { ColorizedChannel } from "./DigitalTwin/pnp/src/common/colorizedChannel";
import { Constants } from "./DigitalTwin/pnp/src/common/constants";
import {
DeviceModelManager,
ModelType
} from "./DigitalTwin/pnp/src/deviceModel/deviceModelManager";
import { ModelRepositoryManager } from "./DigitalTwin/pnp/src/modelRepository/modelRepositoryManager";
import { IntelliSenseUtility } from "./DigitalTwin/pnp/src/intelliSense/intelliSenseUtility";
import { DigitalTwinCompletionItemProvider } from "./DigitalTwin/pnp/src/intelliSense/digitalTwinCompletionItemProvider";
import { DigitalTwinHoverProvider } from "./DigitalTwin/pnp/src/intelliSense/digitalTwinHoverProvider";
import { DigitalTwinDiagnosticProvider } from "./DigitalTwin/pnp/src/intelliSense/digitalTwinDiagnosticProvider";
import { Command } from "./DigitalTwin/pnp/src/common/command";
import { UserCancelledError } from "./DigitalTwin/pnp/src/common/userCancelledError";
import { UI, MessageType } from "./DigitalTwin/pnp/src/view/ui";
import { ProcessError } from "./DigitalTwin/pnp/src/common/processError";
import { SearchResult } from "./DigitalTwin/pnp/src/modelRepository/modelRepositoryInterface";
import { NSAT } from "./nsat";
import { DigitalTwinUtility } from "./DigitalTwin/DigitalTwinUtility";
const impor = require('impor')(__dirname);
const exampleExplorerModule =
impor('./exampleExplorer') as typeof import('./exampleExplorer');
const request = impor('request-promise') as typeof import('request-promise');
const impor = require("impor")(__dirname);
const exampleExplorerModule = impor(
"./exampleExplorer"
) as typeof import("./exampleExplorer");
const request = impor("request-promise") as typeof import("request-promise");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let telemetryWorker: any = undefined;
@ -54,31 +62,47 @@ function printHello(context: vscode.ExtensionContext): void {
console.log(`Congratulations, your extension ${extensionId} is now active!`);
}
function initCommandWithTelemetry(
context: vscode.ExtensionContext, telemetryWorker: TelemetryWorker,
outputChannel: vscode.OutputChannel, command: WorkbenchCommands,
eventName: string, enableSurvey: boolean,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
context: vscode.ExtensionContext,
telemetryWorker: TelemetryWorker,
outputChannel: vscode.OutputChannel,
command: WorkbenchCommands,
eventName: string,
enableSurvey: boolean,
callback: (
context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
telemetrycontext: TelemetryContext, ...args: any[]) => any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
additionalProperties?: {[key: string]: string}): void {
context.subscriptions.push(vscode.commands.registerCommand(
command,
async (...commandArgs) => telemetryWorker.callCommandWithTelemetry(
context, outputChannel, eventName, enableSurvey, callback,
additionalProperties, ...commandArgs)));
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
telemetrycontext: TelemetryContext,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...args: any[]
) => // eslint-disable-next-line @typescript-eslint/no-explicit-any
any,
additionalProperties?: { [key: string]: string }
): void {
context.subscriptions.push(
vscode.commands.registerCommand(command, async (...commandArgs) =>
telemetryWorker.callCommandWithTelemetry(
context,
outputChannel,
eventName,
enableSurvey,
callback,
additionalProperties,
...commandArgs
)
)
);
}
function initCommand(
context: vscode.ExtensionContext, command: WorkbenchCommands,
context: vscode.ExtensionContext,
command: WorkbenchCommands,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callback: (...args: any[]) => Promise<any>): void {
callback: (...args: any[]) => Promise<any>
): void {
context.subscriptions.push(
vscode.commands.registerCommand(command, callback));
vscode.commands.registerCommand(command, callback)
);
}
function initIntelliSense(context: vscode.ExtensionContext): void {
@ -86,57 +110,68 @@ function initIntelliSense(context: vscode.ExtensionContext): void {
IntelliSenseUtility.initGraph(context);
// register providers of completionItem and hover
const selector: vscode.DocumentSelector = {
language: 'json',
scheme: 'file',
language: "json",
scheme: "file"
};
context.subscriptions.push(
vscode.languages.registerCompletionItemProvider(
selector,
new DigitalTwinCompletionItemProvider(),
Constants.COMPLETION_TRIGGER,
),
Constants.COMPLETION_TRIGGER
)
);
context.subscriptions.push(
vscode.languages.registerHoverProvider(
selector,
new DigitalTwinHoverProvider()
)
);
context.subscriptions.push(vscode.languages.registerHoverProvider(
selector, new DigitalTwinHoverProvider()));
// register diagnostic
let pendingDiagnostic: NodeJS.Timer;
const diagnosticCollection: vscode.DiagnosticCollection =
vscode.languages.createDiagnosticCollection(
Constants.CHANNEL_NAME,
);
const diagnosticCollection: vscode.DiagnosticCollection = vscode.languages.createDiagnosticCollection(
Constants.CHANNEL_NAME
);
const diagnosticProvider = new DigitalTwinDiagnosticProvider();
const activeTextEditor: vscode.TextEditor|undefined =
vscode.window.activeTextEditor;
const activeTextEditor: vscode.TextEditor | undefined =
vscode.window.activeTextEditor;
if (activeTextEditor) {
diagnosticProvider.updateDiagnostics(
activeTextEditor.document, diagnosticCollection);
activeTextEditor.document,
diagnosticCollection
);
}
context.subscriptions.push(diagnosticCollection);
context.subscriptions.push(
vscode.window.onDidChangeActiveTextEditor((event) => {
vscode.window.onDidChangeActiveTextEditor(event => {
if (event) {
diagnosticProvider.updateDiagnostics(
event.document, diagnosticCollection);
event.document,
diagnosticCollection
);
}
}),
})
);
context.subscriptions.push(
vscode.workspace.onDidChangeTextDocument((event) => {
vscode.workspace.onDidChangeTextDocument(event => {
if (event) {
if (pendingDiagnostic) {
clearTimeout(pendingDiagnostic);
}
pendingDiagnostic = setTimeout(
() => diagnosticProvider.updateDiagnostics(
event.document, diagnosticCollection),
Constants.DEFAULT_TIMER_MS,
() =>
diagnosticProvider.updateDiagnostics(
event.document,
diagnosticCollection
),
Constants.DEFAULT_TIMER_MS
);
}
}),
})
);
context.subscriptions.push(
vscode.workspace.onDidCloseTextDocument(
(document) => diagnosticCollection.delete(document.uri)),
vscode.workspace.onDidCloseTextDocument(document =>
diagnosticCollection.delete(document.uri)
)
);
}
@ -146,10 +181,12 @@ function initDigitalTwinCommand(
outputChannel: ColorizedChannel,
enableSurvey: boolean,
command: Command,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callback: (telemetryContext: TelemetryContext, ...args: any[]) =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Promise<any>,
callback: (
telemetryContext: TelemetryContext,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...args: any[]
) => // eslint-disable-next-line @typescript-eslint/no-explicit-any
Promise<any>
): void {
context.subscriptions.push(
vscode.commands.registerCommand(
@ -157,8 +194,7 @@ function initDigitalTwinCommand(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async (...args: any[]) => {
const start: number = Date.now();
const telemetryContext: TelemetryContext =
telemetryWorker.createContext();
const telemetryContext: TelemetryContext = telemetryWorker.createContext();
try {
return await callback(telemetryContext, ...args);
} catch (error) {
@ -178,26 +214,30 @@ function initDigitalTwinCommand(
}
}
} finally {
telemetryContext.measurements.duration =
(Date.now() - start) / 1000;
telemetryContext.measurements.duration = (Date.now() - start) / 1000;
telemetryWorker.sendEvent(command, telemetryContext);
outputChannel.show();
if (enableSurvey) {
NSAT.takeSurvey(context);
}
}
}),
}
)
);
}
// DigitalTwin extension part
function initDigitalTwin(
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel): void {
outputChannel: vscode.OutputChannel
): void {
const colorizedChannel = new ColorizedChannel(Constants.CHANNEL_NAME);
context.subscriptions.push(colorizedChannel);
const deviceModelManager = new DeviceModelManager(context, colorizedChannel);
const modelRepositoryManager = new ModelRepositoryManager(
context, Constants.WEB_VIEW_PATH, colorizedChannel);
context,
Constants.WEB_VIEW_PATH,
colorizedChannel
);
DigitalTwinUtility.init(modelRepositoryManager, outputChannel);
initIntelliSense(context);
@ -207,10 +247,9 @@ function initDigitalTwin(
colorizedChannel,
true,
Command.CreateInterface,
async():
Promise<void> => {
async (): Promise<void> => {
return deviceModelManager.createModel(ModelType.Interface);
},
}
);
initDigitalTwinCommand(
context,
@ -218,10 +257,9 @@ function initDigitalTwin(
colorizedChannel,
true,
Command.CreateCapabilityModel,
async():
Promise<void> => {
async (): Promise<void> => {
return deviceModelManager.createModel(ModelType.CapabilityModel);
},
}
);
initDigitalTwinCommand(
context,
@ -229,10 +267,9 @@ function initDigitalTwin(
colorizedChannel,
true,
Command.OpenRepository,
async():
Promise<void> => {
async (): Promise<void> => {
return modelRepositoryManager.signIn();
},
}
);
initDigitalTwinCommand(
context,
@ -240,10 +277,9 @@ function initDigitalTwin(
colorizedChannel,
true,
Command.SignOutRepository,
async():
Promise<void> => {
async (): Promise<void> => {
return modelRepositoryManager.signOut();
},
}
);
initDigitalTwinCommand(
context,
@ -251,10 +287,9 @@ function initDigitalTwin(
colorizedChannel,
true,
Command.SubmitFiles,
async(telemetryContext: TelemetryContext):
Promise<void> => {
async (telemetryContext: TelemetryContext): Promise<void> => {
return modelRepositoryManager.submitFiles(telemetryContext);
},
}
);
initDigitalTwinCommand(
context,
@ -262,13 +297,13 @@ function initDigitalTwin(
colorizedChannel,
false,
Command.DeleteModels,
async(
_telemetryContext: TelemetryContext, publicRepository: boolean,
modelIds: string[]):
Promise<void> => {
return modelRepositoryManager.deleteModels(
publicRepository, modelIds);
},
async (
_telemetryContext: TelemetryContext,
publicRepository: boolean,
modelIds: string[]
): Promise<void> => {
return modelRepositoryManager.deleteModels(publicRepository, modelIds);
}
);
initDigitalTwinCommand(
context,
@ -276,13 +311,13 @@ function initDigitalTwin(
colorizedChannel,
false,
Command.DownloadModels,
async(
_telemetryContext: TelemetryContext, publicRepository: boolean,
modelIds: string[]):
Promise<void> => {
return modelRepositoryManager.downloadModels(
publicRepository, modelIds);
},
async (
_telemetryContext: TelemetryContext,
publicRepository: boolean,
modelIds: string[]
): Promise<void> => {
return modelRepositoryManager.downloadModels(publicRepository, modelIds);
}
);
initDigitalTwinCommand(
context,
@ -290,22 +325,21 @@ function initDigitalTwin(
colorizedChannel,
false,
Command.SearchInterface,
async(
async (
_telemetryContext: TelemetryContext,
publicRepository: boolean,
keyword?: string,
pageSize?: number,
continuationToken?: string,
):
Promise<SearchResult> => {
continuationToken?: string
): Promise<SearchResult> => {
return modelRepositoryManager.searchModel(
ModelType.Interface,
publicRepository,
keyword,
pageSize,
continuationToken,
continuationToken
);
},
}
);
initDigitalTwinCommand(
context,
@ -313,46 +347,49 @@ function initDigitalTwin(
colorizedChannel,
false,
Command.SearchCapabilityModel,
async(
async (
_telemetryContext: TelemetryContext,
publicRepository: boolean,
keyword?: string,
pageSize?: number,
continuationToken?: string,
):
Promise<SearchResult> => {
continuationToken?: string
): Promise<SearchResult> => {
return modelRepositoryManager.searchModel(
ModelType.CapabilityModel,
publicRepository,
keyword,
pageSize,
continuationToken,
continuationToken
);
},
}
);
}
function enableUsbDetector(
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel): void {
outputChannel: vscode.OutputChannel
): void {
if (RemoteExtension.isRemote(context)) {
return;
}
// delay to detect usb
const usbDetectorModule =
impor('./usbDetector') as typeof import('./usbDetector');
const usbDetectorModule = impor(
"./usbDetector"
) as typeof import("./usbDetector");
const usbDetector = new usbDetectorModule.UsbDetector(context, outputChannel);
usbDetector.startListening(context);
}
export async function activate(context: vscode.ExtensionContext): Promise<void> {
export async function activate(
context: vscode.ExtensionContext
): Promise<void> {
printHello(context);
const channelName = 'Azure IoT Device Workbench';
const outputChannel: vscode.OutputChannel =
vscode.window.createOutputChannel(channelName);
const channelName = "Azure IoT Device Workbench";
const outputChannel: vscode.OutputChannel = vscode.window.createOutputChannel(
channelName
);
telemetryWorker = TelemetryWorker.getInstance(context);
context.subscriptions.push(telemetryWorker);
@ -360,150 +397,263 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
// project open since no command has been triggered yet.
const telemetryContext = telemetryWorker.createContext();
await constructAndLoadIoTProject(
context, outputChannel, telemetryContext, true);
context,
outputChannel,
telemetryContext,
true
);
const deviceOperator = new DeviceOperator();
const azureOperator = new AzureOperator();
const exampleExplorer = new exampleExplorerModule.ExampleExplorer();
initCommandWithTelemetry(
context, telemetryWorker, outputChannel,
WorkbenchCommands.InitializeProject, EventNames.createNewProjectEvent,
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.InitializeProject,
EventNames.createNewProjectEvent,
true,
async(
context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext): Promise<void> => {
async (
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> => {
const projectInitializer = new ProjectInitializer();
return projectInitializer.InitializeProject(
context, outputChannel, telemetryContext);
});
context,
outputChannel,
telemetryContext
);
}
);
initCommandWithTelemetry(
context, telemetryWorker, outputChannel,
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.ConfigureProjectEnvironment,
EventNames.configProjectEnvironmentEvent, true,
async(
context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext): Promise<void> => {
EventNames.configProjectEnvironmentEvent,
true,
async (
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> => {
const projectEnvConfiger = new ProjectEnvironmentConfiger();
return projectEnvConfiger.configureCmakeProjectEnvironment(
context, outputChannel, telemetryContext);
});
context,
outputChannel,
telemetryContext
);
}
);
initCommandWithTelemetry(
context, telemetryWorker, outputChannel, WorkbenchCommands.AzureProvision,
EventNames.azureProvisionEvent, true,
async(
context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext): Promise<void> => {
return azureOperator.provision(
context, outputChannel, telemetryContext);
});
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.AzureProvision,
EventNames.azureProvisionEvent,
true,
async (
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> => {
return azureOperator.provision(context, outputChannel, telemetryContext);
}
);
initCommandWithTelemetry(
context, telemetryWorker, outputChannel, WorkbenchCommands.AzureDeploy,
EventNames.azureDeployEvent, true,
async(
context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext): Promise<void> => {
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.AzureDeploy,
EventNames.azureDeployEvent,
true,
async (
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> => {
return azureOperator.deploy(context, outputChannel, telemetryContext);
});
}
);
initCommandWithTelemetry(
context, telemetryWorker, outputChannel, WorkbenchCommands.DeviceCompile,
EventNames.deviceCompileEvent, true,
async(
context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext): Promise<void> => {
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.DeviceCompile,
EventNames.deviceCompileEvent,
true,
async (
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> => {
return deviceOperator.compile(context, outputChannel, telemetryContext);
});
}
);
initCommandWithTelemetry(
context, telemetryWorker, outputChannel, WorkbenchCommands.DeviceUpload,
EventNames.deviceUploadEvent, true,
async(
context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext): Promise<void> => {
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.DeviceUpload,
EventNames.deviceUploadEvent,
true,
async (
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> => {
return deviceOperator.upload(context, outputChannel, telemetryContext);
});
}
);
initCommandWithTelemetry(
context, telemetryWorker, outputChannel,
WorkbenchCommands.ConfigureDevice, EventNames.configDeviceSettingsEvent,
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.ConfigureDevice,
EventNames.configDeviceSettingsEvent,
true,
async(
context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext): Promise<void> => {
async (
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> => {
return deviceOperator.configDeviceSettings(
context, outputChannel, telemetryContext);
});
context,
outputChannel,
telemetryContext
);
}
);
initCommandWithTelemetry(
context, telemetryWorker, outputChannel, WorkbenchCommands.Examples,
EventNames.openExamplePageEvent, true,
async(
context: vscode.ExtensionContext, _outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext): Promise<void> => {
return exampleExplorer.selectBoard(
context, telemetryContext);
});
initCommandWithTelemetry(
context, telemetryWorker, outputChannel,
WorkbenchCommands.ExampleInitialize, EventNames.loadExampleEvent, true,
async(
context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext, name?: string, url?: string,
boardId?: string): Promise<void> => {
return exampleExplorer.initializeExample(
context, outputChannel, telemetryContext, name, url, boardId);
});
initCommandWithTelemetry(
context, telemetryWorker, outputChannel, WorkbenchCommands.SendTelemetry,
EventNames.openTutorial, true, async () => {
// Do nothing.
});
initCommandWithTelemetry(
context, telemetryWorker, outputChannel,
WorkbenchCommands.IotPnPGenerateCode, EventNames.scaffoldDeviceStubEvent,
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.Examples,
EventNames.openExamplePageEvent,
true,
async(
context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext): Promise<void> => {
async (
context: vscode.ExtensionContext,
_outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> => {
return exampleExplorer.selectBoard(context, telemetryContext);
}
);
initCommandWithTelemetry(
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.ExampleInitialize,
EventNames.loadExampleEvent,
true,
async (
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext,
name?: string,
url?: string,
boardId?: string
): Promise<void> => {
return exampleExplorer.initializeExample(
context,
outputChannel,
telemetryContext,
name,
url,
boardId
);
}
);
initCommandWithTelemetry(
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.SendTelemetry,
EventNames.openTutorial,
true,
async () => {
// Do nothing.
}
);
initCommandWithTelemetry(
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.IotPnPGenerateCode,
EventNames.scaffoldDeviceStubEvent,
true,
async (
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> => {
const codeGenerator = new CodeGeneratorCore();
return codeGenerator.generateDeviceCodeStub(
context, outputChannel, telemetryContext);
});
context,
outputChannel,
telemetryContext
);
}
);
initCommandWithTelemetry(
context, telemetryWorker, outputChannel, WorkbenchCommands.Help,
EventNames.help, true, async () => {
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.Help,
EventNames.help,
true,
async () => {
const boardId = ConfigHandler.get<string>(ConfigKey.boardId);
if (boardId) {
const boardListFolderPath = context.asAbsolutePath(path.join(
FileNames.resourcesFolderName, FileNames.templatesFolderName));
const boardListFolderPath = context.asAbsolutePath(
path.join(
FileNames.resourcesFolderName,
FileNames.templatesFolderName
)
);
const boardProvider = new BoardProvider(boardListFolderPath);
const board = boardProvider.find({ id: boardId });
if (board && board.helpUrl) {
await vscode.commands.executeCommand(
VscodeCommands.VscodeOpen, vscode.Uri.parse(board.helpUrl));
VscodeCommands.VscodeOpen,
vscode.Uri.parse(board.helpUrl)
);
return;
}
}
const workbenchHelpUrl =
'https://github.com/microsoft/vscode-iot-workbench/blob/master/README.md';
"https://github.com/microsoft/vscode-iot-workbench/blob/master/README.md";
await vscode.commands.executeCommand(
VscodeCommands.VscodeOpen, vscode.Uri.parse(workbenchHelpUrl));
VscodeCommands.VscodeOpen,
vscode.Uri.parse(workbenchHelpUrl)
);
return;
});
}
);
initCommandWithTelemetry(
context, telemetryWorker, outputChannel, WorkbenchCommands.Workbench,
EventNames.setProjectDefaultPath, true, async () => {
context,
telemetryWorker,
outputChannel,
WorkbenchCommands.Workbench,
EventNames.setProjectDefaultPath,
true,
async () => {
const isLocal = RemoteExtension.checkLocalBeforeRunCommand(context);
if (!isLocal) {
return;
@ -511,11 +661,14 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
const settings = await IoTWorkbenchSettings.getInstance();
await settings.setWorkbenchPath();
return;
});
}
);
initCommand(context, WorkbenchCommands.OpenUri, async (uri: string) => {
vscode.commands.executeCommand(
VscodeCommands.VscodeOpen, vscode.Uri.parse(uri));
VscodeCommands.VscodeOpen,
vscode.Uri.parse(uri)
);
});
initCommand(context, WorkbenchCommands.HttpRequest, async (uri: string) => {

6
src/getmac.d.ts поставляемый
Просмотреть файл

@ -1,6 +1,6 @@
// reference code from https://github.com/0815fox/DefinitelyTyped
declare module "getmac" {
function getMac(opts: (err: Error,macAddress: string) => void): void;
function isMac(macAddress: string): boolean;
}
function getMac(opts: (err: Error, macAddress: string) => void): void;
function isMac(macAddress: string): boolean;
}

Просмотреть файл

@ -1,35 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
'use strict';
"use strict";
import { commands, ExtensionContext, Uri, window } from 'vscode';
import { EventNames } from './constants';
import { TelemetryWorker } from './telemetry';
import { WorkbenchExtension } from './WorkbenchExtension';
import { VscodeCommands } from './common/Commands';
import { commands, ExtensionContext, Uri, window } from "vscode";
import { EventNames } from "./constants";
import { TelemetryWorker } from "./telemetry";
import { WorkbenchExtension } from "./WorkbenchExtension";
import { VscodeCommands } from "./common/Commands";
const NSAT_SURVEY_URL = 'https://aka.ms/vscode-iot-workbench-survey';
const NSAT_SURVEY_URL = "https://aka.ms/vscode-iot-workbench-survey";
const PROBABILITY = 1;
const SESSION_COUNT_THRESHOLD = 2;
const SESSION_COUNT_KEY = 'nsat/sessionCount';
const LAST_SESSION_DATE_KEY = 'nsat/lastSessionDate';
const TAKE_SURVEY_DATE_KEY = 'nsat/takeSurveyDate';
const DONT_SHOW_DATE_KEY = 'nsat/dontShowDate';
const SKIP_VERSION_KEY = 'nsat/skipVersion';
const IS_CANDIDATE_KEY = 'nsat/isCandidate';
const SESSION_COUNT_KEY = "nsat/sessionCount";
const LAST_SESSION_DATE_KEY = "nsat/lastSessionDate";
const TAKE_SURVEY_DATE_KEY = "nsat/takeSurveyDate";
const DONT_SHOW_DATE_KEY = "nsat/dontShowDate";
const SKIP_VERSION_KEY = "nsat/skipVersion";
const IS_CANDIDATE_KEY = "nsat/isCandidate";
export class NSAT {
static async takeSurvey(context: ExtensionContext): Promise<void> {
const globalState = context.globalState;
const skipVersion = globalState.get(SKIP_VERSION_KEY, '');
const skipVersion = globalState.get(SKIP_VERSION_KEY, "");
if (skipVersion) {
return;
}
const date = new Date().toDateString();
const lastSessionDate =
globalState.get(LAST_SESSION_DATE_KEY, new Date(0).toDateString());
const lastSessionDate = globalState.get(
LAST_SESSION_DATE_KEY,
new Date(0).toDateString()
);
if (date === lastSessionDate) {
return;
@ -43,8 +45,8 @@ export class NSAT {
return;
}
const isCandidate = globalState.get(IS_CANDIDATE_KEY, false) ||
Math.random() <= PROBABILITY;
const isCandidate =
globalState.get(IS_CANDIDATE_KEY, false) || Math.random() <= PROBABILITY;
await globalState.update(IS_CANDIDATE_KEY, isCandidate);
@ -55,50 +57,56 @@ export class NSAT {
if (!extension) {
return;
}
const extensionVersion = extension.packageJSON.version || 'unknown';
const extensionVersion = extension.packageJSON.version || "unknown";
if (!isCandidate) {
await globalState.update(SKIP_VERSION_KEY, extensionVersion);
return;
}
const take = {
title: 'Take Survey',
title: "Take Survey",
run: async (): Promise<void> => {
telemetryContext.properties.message = 'nsat.survey/takeShortSurvey';
telemetryContext.properties.message = "nsat.survey/takeShortSurvey";
telemetryWorker.sendEvent(EventNames.nsatsurvery, telemetryContext);
commands.executeCommand(
VscodeCommands.VscodeOpen,
Uri.parse(`${NSAT_SURVEY_URL}?o=${
encodeURIComponent(process.platform)}&v=${
encodeURIComponent(extensionVersion)}`));
Uri.parse(
`${NSAT_SURVEY_URL}?o=${encodeURIComponent(
process.platform
)}&v=${encodeURIComponent(extensionVersion)}`
)
);
await globalState.update(IS_CANDIDATE_KEY, false);
await globalState.update(SKIP_VERSION_KEY, extensionVersion);
await globalState.update(TAKE_SURVEY_DATE_KEY, date);
},
}
};
const remind = {
title: 'Remind Me Later',
title: "Remind Me Later",
run: async (): Promise<void> => {
telemetryContext.properties.message = 'nsat.survey/remindMeLater';
telemetryContext.properties.message = "nsat.survey/remindMeLater";
telemetryWorker.sendEvent(EventNames.nsatsurvery, telemetryContext);
await globalState.update(SESSION_COUNT_KEY, 0);
},
}
};
const never = {
title: 'Don\'t Show Again',
title: "Don't Show Again",
run: async (): Promise<void> => {
telemetryContext.properties.message = 'nsat.survey/dontShowAgain';
telemetryContext.properties.message = "nsat.survey/dontShowAgain";
telemetryWorker.sendEvent(EventNames.nsatsurvery, telemetryContext);
await globalState.update(IS_CANDIDATE_KEY, false);
await globalState.update(SKIP_VERSION_KEY, extensionVersion);
await globalState.update(DONT_SHOW_DATE_KEY, date);
},
}
};
telemetryContext.properties.message = 'nsat.survey/userAsked';
telemetryContext.properties.message = "nsat.survey/userAsked";
telemetryWorker.sendEvent(EventNames.nsatsurvery, telemetryContext);
const button = await window.showInformationMessage(
'Do you mind taking a quick feedback survey about the Azure IoT Device Workbench Extension for VS Code?',
take, remind, never);
"Do you mind taking a quick feedback survey about the Azure IoT Device Workbench Extension for VS Code?",
take,
remind,
never
);
await (button || remind).run();
}
}

Просмотреть файл

@ -1,37 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
"use strict";
import * as vscode from 'vscode';
import * as path from 'path';
import * as utils from './utils';
import * as vscode from "vscode";
import * as path from "path";
import * as utils from "./utils";
import { TelemetryContext } from './telemetry';
import { FileNames, ScaffoldType, PlatformType, TemplateTag } from './constants';
import { IoTWorkbenchSettings } from './IoTSettings';
import { FileUtility } from './FileUtility';
import { ProjectTemplate, ProjectTemplateType, TemplatesType } from './Models/Interfaces/ProjectTemplate';
import { RemoteExtension } from './Models/RemoteExtension';
import { CancelOperationError } from './CancelOperationError';
import { TelemetryContext } from "./telemetry";
import {
FileNames,
ScaffoldType,
PlatformType,
TemplateTag
} from "./constants";
import { IoTWorkbenchSettings } from "./IoTSettings";
import { FileUtility } from "./FileUtility";
import {
ProjectTemplate,
ProjectTemplateType,
TemplatesType
} from "./Models/Interfaces/ProjectTemplate";
import { RemoteExtension } from "./Models/RemoteExtension";
import { CancelOperationError } from "./CancelOperationError";
const impor = require('impor')(__dirname);
const ioTWorkspaceProjectModule = impor('./Models/IoTWorkspaceProject') as
typeof import('./Models/IoTWorkspaceProject');
const ioTContainerizedProjectModule =
impor('./Models/IoTContainerizedProject') as
typeof import('./Models/IoTContainerizedProject');
const impor = require("impor")(__dirname);
const ioTWorkspaceProjectModule = impor(
"./Models/IoTWorkspaceProject"
) as typeof import("./Models/IoTWorkspaceProject");
const ioTContainerizedProjectModule = impor(
"./Models/IoTContainerizedProject"
) as typeof import("./Models/IoTContainerizedProject");
const constants = {
defaultProjectName: 'IoTproject',
noDeviceMessage: '$(issue-opened) My device is not in the list...',
embeddedLinuxProjectName: 'Embedded Linux Project'
defaultProjectName: "IoTproject",
noDeviceMessage: "$(issue-opened) My device is not in the list...",
embeddedLinuxProjectName: "Embedded Linux Project"
};
export class ProjectInitializer {
async InitializeProject(
context: vscode.ExtensionContext, channel: vscode.OutputChannel,
telemetryContext: TelemetryContext): Promise<void> {
context: vscode.ExtensionContext,
channel: vscode.OutputChannel,
telemetryContext: TelemetryContext
): Promise<void> {
// Only create project when not in remote environment
const isLocal = RemoteExtension.checkLocalBeforeRunCommand(context);
if (!isLocal) {
@ -41,63 +53,82 @@ export class ProjectInitializer {
let openInNewWindow = false;
// If current window contains other project, open the created project in new
// window.
if (vscode.workspace.workspaceFolders &&
vscode.workspace.workspaceFolders.length > 0) {
if (
vscode.workspace.workspaceFolders &&
vscode.workspace.workspaceFolders.length > 0
) {
openInNewWindow = true;
}
// Initial project
await vscode.window.withProgress(
{
title: 'Project initialization',
location: vscode.ProgressLocation.Window,
title: "Project initialization",
location: vscode.ProgressLocation.Window
},
async (progress) => {
async progress => {
progress.report({
message: 'Updating a list of available template',
message: "Updating a list of available template"
});
const scaffoldType = ScaffoldType.Local;
// Step 1: Get project name
const projectPath =
await this.generateProjectFolder(telemetryContext, scaffoldType);
const projectPath = await this.generateProjectFolder(
telemetryContext,
scaffoldType
);
if (!projectPath) {
throw new CancelOperationError(
`Project initialization cancelled: Project name input cancelled.`);
`Project initialization cancelled: Project name input cancelled.`
);
}
// Step 2: Select platform
const platformSelection =
await utils.selectPlatform(scaffoldType, context);
const platformSelection = await utils.selectPlatform(
scaffoldType,
context
);
if (!platformSelection) {
throw new CancelOperationError(
`Project initialization cancelled: Platform selection cancelled.`);
`Project initialization cancelled: Platform selection cancelled.`
);
} else {
telemetryContext.properties.platform = platformSelection.label;
}
// Step 3: Select template
const resourceRootPath = context.asAbsolutePath(path.join(
FileNames.resourcesFolderName, FileNames.templatesFolderName));
const templateJsonFilePath =
path.join(resourceRootPath, FileNames.templateFileName);
const templateJsonFileString =
await FileUtility.readFile(
scaffoldType, templateJsonFilePath, 'utf8') as string;
const resourceRootPath = context.asAbsolutePath(
path.join(
FileNames.resourcesFolderName,
FileNames.templatesFolderName
)
);
const templateJsonFilePath = path.join(
resourceRootPath,
FileNames.templateFileName
);
const templateJsonFileString = (await FileUtility.readFile(
scaffoldType,
templateJsonFilePath,
"utf8"
)) as string;
const templateJson = JSON.parse(templateJsonFileString);
if (!templateJson) {
throw new Error(`Fail to load template json.`);
}
let templateName: string|undefined;
let templateName: string | undefined;
if (platformSelection.label === PlatformType.Arduino) {
const templateSelection =
await this.selectTemplate(templateJson, PlatformType.Arduino);
const templateSelection = await this.selectTemplate(
templateJson,
PlatformType.Arduino
);
if (!templateSelection) {
throw new CancelOperationError(
`Project initialization cancelled: Project template selection cancelled.`);
`Project initialization cancelled: Project template selection cancelled.`
);
} else {
telemetryContext.properties.template = templateSelection.label;
if (templateSelection.label === constants.noDeviceMessage) {
@ -112,48 +143,70 @@ export class ProjectInitializer {
templateName = constants.embeddedLinuxProjectName;
}
const template =
templateJson.templates.find((template: ProjectTemplate) => {
return template.platform === platformSelection.label &&
template.name === templateName;
});
const template = templateJson.templates.find(
(template: ProjectTemplate) => {
return (
template.platform === platformSelection.label &&
template.name === templateName
);
}
);
if (!template) {
throw new Error(
`Fail to find the wanted project template in template json file.`);
`Fail to find the wanted project template in template json file.`
);
}
// Step 4: Load the list of template files
const projectTemplateType: ProjectTemplateType =
utils.getEnumKeyByEnumValue(ProjectTemplateType, template.type);
const projectTemplateType: ProjectTemplateType = utils.getEnumKeyByEnumValue(
ProjectTemplateType,
template.type
);
const templateFolder = path.join(resourceRootPath, template.path);
const templateFilesInfo =
await utils.getTemplateFilesInfo(templateFolder);
const templateFilesInfo = await utils.getTemplateFilesInfo(
templateFolder
);
let project;
if (template.platform === PlatformType.EmbeddedLinux) {
project = new ioTContainerizedProjectModule.IoTContainerizedProject(
context, channel, telemetryContext, projectPath);
context,
channel,
telemetryContext,
projectPath
);
} else if (template.platform === PlatformType.Arduino) {
project = new ioTWorkspaceProjectModule.IoTWorkspaceProject(
context, channel, telemetryContext, projectPath);
context,
channel,
telemetryContext,
projectPath
);
} else {
throw new Error('unsupported platform');
throw new Error("unsupported platform");
}
await project.create(
templateFilesInfo, projectTemplateType, template.boardId,
openInNewWindow);
});
templateFilesInfo,
projectTemplateType,
template.boardId,
openInNewWindow
);
}
);
}
private async selectTemplate(templateJson: TemplatesType, platform: string):
Promise<vscode.QuickPickItem|undefined> {
const result =
templateJson.templates.filter((template: ProjectTemplate) => {
return (
template.platform === platform &&
template.tag === TemplateTag.General);
});
private async selectTemplate(
templateJson: TemplatesType,
platform: string
): Promise<vscode.QuickPickItem | undefined> {
const result = templateJson.templates.filter(
(template: ProjectTemplate) => {
return (
template.platform === platform && template.tag === TemplateTag.General
);
}
);
const projectTemplateList: vscode.QuickPickItem[] = [];
@ -165,26 +218,29 @@ export class ProjectInitializer {
});
});
const templateSelection =
await vscode.window.showQuickPick(projectTemplateList, {
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: 'Select a project template'
});
const templateSelection = await vscode.window.showQuickPick(
projectTemplateList,
{
ignoreFocusOut: true,
matchOnDescription: true,
matchOnDetail: true,
placeHolder: "Select a project template"
}
);
return templateSelection;
}
private async generateProjectFolder(
telemetryContext: TelemetryContext,
scaffoldType: ScaffoldType): Promise<string|undefined> {
scaffoldType: ScaffoldType
): Promise<string | undefined> {
// Get default workbench path.
const settings = await IoTWorkbenchSettings.getInstance();
const workbench = settings.getWorkbenchPath();
const projectRootPath = path.join(workbench, 'projects');
if (!await FileUtility.directoryExists(scaffoldType, projectRootPath)) {
const projectRootPath = path.join(workbench, "projects");
if (!(await FileUtility.directoryExists(scaffoldType, projectRootPath))) {
await FileUtility.mkdirRecursively(scaffoldType, projectRootPath);
}
@ -205,17 +261,22 @@ export class ProjectInitializer {
const projectName = await vscode.window.showInputBox({
value: candidateName,
prompt: 'Input project name.',
prompt: "Input project name.",
ignoreFocusOut: true,
validateInput: async (projectName: string) => {
if (!/^([a-z0-9_]|[a-z0-9_][-a-z0-9_.]*[a-z0-9_])(\.ino)?$/i.test(
projectName)) {
if (
!/^([a-z0-9_]|[a-z0-9_][-a-z0-9_.]*[a-z0-9_])(\.ino)?$/i.test(
projectName
)
) {
return 'Project name can only contain letters, numbers, "-" and ".", and cannot start or end with "-" or ".".';
}
const projectPath = path.join(projectRootPath, projectName);
const isProjectNameValid =
await this.isProjectPathValid(scaffoldType, projectPath);
const isProjectNameValid = await this.isProjectPathValid(
scaffoldType,
projectPath
);
if (isProjectNameValid) {
return;
} else {
@ -228,8 +289,9 @@ export class ProjectInitializer {
telemetryContext.properties.projectName = projectNameMd5;
}
const projectPath =
projectName ? path.join(projectRootPath, projectName) : undefined;
const projectPath = projectName
? path.join(projectRootPath, projectName)
: undefined;
// We don't create the projectpath here in case user may cancel their
// initialization in following steps Just generate a valid path for project
@ -237,11 +299,17 @@ export class ProjectInitializer {
}
private async isProjectPathValid(
scaffoldType: ScaffoldType, projectPath: string): Promise<boolean> {
const projectPathExists =
await FileUtility.fileExists(scaffoldType, projectPath);
const projectDirectoryExists =
await FileUtility.directoryExists(scaffoldType, projectPath);
scaffoldType: ScaffoldType,
projectPath: string
): Promise<boolean> {
const projectPathExists = await FileUtility.fileExists(
scaffoldType,
projectPath
);
const projectDirectoryExists = await FileUtility.directoryExists(
scaffoldType,
projectPath
);
return !projectPathExists && !projectDirectoryExists;
}
}

Просмотреть файл

@ -1,16 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as vscode from 'vscode';
import TelemetryReporter from 'vscode-extension-telemetry';
import { CancelOperationError } from './CancelOperationError';
import { DevelopEnvironment } from './constants';
import { ExceptionHelper } from './exceptionHelper';
import { RemoteExtension } from './Models/RemoteExtension';
import { NSAT } from './nsat';
import { WorkbenchExtension } from './WorkbenchExtension';
import * as vscode from "vscode";
import TelemetryReporter from "vscode-extension-telemetry";
import { CancelOperationError } from "./CancelOperationError";
import { DevelopEnvironment } from "./constants";
import { ExceptionHelper } from "./exceptionHelper";
import { RemoteExtension } from "./Models/RemoteExtension";
import { NSAT } from "./nsat";
import { WorkbenchExtension } from "./WorkbenchExtension";
interface PackageInfo {
name: string;
@ -22,39 +21,43 @@ interface PackageInfo {
* Operation result of telemetry
*/
export enum TelemetryResult {
Succeeded = 'Succeeded',
Failed = 'Failed',
Cancelled = 'Cancelled',
Succeeded = "Succeeded",
Failed = "Failed",
Cancelled = "Cancelled"
}
/**
* Context of telemetry
*/
export interface TelemetryContext {
properties: {[key: string]: string};
measurements: {[key: string]: number};
properties: { [key: string]: string };
measurements: { [key: string]: number };
}
export class TelemetryWorker {
private _reporter: TelemetryReporter|undefined;
private _extensionContext: vscode.ExtensionContext|undefined;
private static _instance: TelemetryWorker|undefined;
private _reporter: TelemetryReporter | undefined;
private _extensionContext: vscode.ExtensionContext | undefined;
private static _instance: TelemetryWorker | undefined;
private _isInternal = false;
private constructor(context: vscode.ExtensionContext) {
this._extensionContext = context;
const packageInfo = this.getPackageInfo(context);
if (!packageInfo) {
console.log('Unable to initialize telemetry');
console.log("Unable to initialize telemetry");
return;
}
if (!packageInfo.aiKey) {
console.log(
'Unable to initialize telemetry, please make sure AIKey is set in package.json');
"Unable to initialize telemetry, please make sure AIKey is set in package.json"
);
return;
}
this._reporter = new TelemetryReporter(
packageInfo.name, packageInfo.version, packageInfo.aiKey);
packageInfo.name,
packageInfo.version,
packageInfo.aiKey
);
this._isInternal = TelemetryWorker.isInternalUser();
}
@ -69,10 +72,10 @@ export class TelemetryWorker {
* check if it is Microsoft internal user
*/
private static isInternalUser(): boolean {
const userDomain: string = process.env.USERDNSDOMAIN ?
process.env.USERDNSDOMAIN.toLowerCase() :
'';
return userDomain.endsWith('microsoft.com');
const userDomain: string = process.env.USERDNSDOMAIN
? process.env.USERDNSDOMAIN.toLowerCase()
: "";
return userDomain.endsWith("microsoft.com");
}
/**
@ -83,10 +86,11 @@ export class TelemetryWorker {
context.properties.result = TelemetryResult.Succeeded;
context.properties.isInternal = this._isInternal.toString();
if (this._extensionContext) {
context.properties.developEnvironment =
RemoteExtension.isRemote(this._extensionContext) ?
DevelopEnvironment.RemoteEnv :
DevelopEnvironment.LocalEnv;
context.properties.developEnvironment = RemoteExtension.isRemote(
this._extensionContext
)
? DevelopEnvironment.RemoteEnv
: DevelopEnvironment.LocalEnv;
}
return context;
}
@ -104,7 +108,10 @@ export class TelemetryWorker {
telemetryContext = this.createContext();
}
this._reporter.sendTelemetryEvent(
eventName, telemetryContext.properties, telemetryContext.measurements);
eventName,
telemetryContext.properties,
telemetryContext.measurements
);
}
/**
@ -118,25 +125,37 @@ export class TelemetryWorker {
* @param additionalProperties
*/
async callCommandWithTelemetry(
context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel,
eventName: string, enableSurvey: boolean,
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
eventName: string,
enableSurvey: boolean,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callback:
(context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
callback: (
context: vscode.ExtensionContext,
outputChannel: vscode.OutputChannel,
telemetrycontext: TelemetryContext,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
telemetrycontext: TelemetryContext, ...args: any[]) => any,
...args: any[]
) => // eslint-disable-next-line @typescript-eslint/no-explicit-any
any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
additionalProperties?: {[key: string]: string},
additionalProperties?: { [key: string]: string },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...commandArgs: any[]): Promise<any> {
...commandArgs: any[]
): // eslint-disable-next-line @typescript-eslint/no-explicit-any
Promise<any> {
const telemetryWorker = TelemetryWorker.getInstance(context);
const telemetryContext = telemetryWorker.createContext();
const start: number = Date.now();
if (additionalProperties) {
for (const key of Object.keys(additionalProperties)) {
if (!Object.prototype.hasOwnProperty.call(telemetryContext.properties, key)) {
if (
!Object.prototype.hasOwnProperty.call(
telemetryContext.properties,
key
)
) {
telemetryContext.properties[key] = additionalProperties[key];
}
}
@ -144,7 +163,11 @@ export class TelemetryWorker {
try {
return await callback(
context, outputChannel, telemetryContext, ...commandArgs);
context,
outputChannel,
telemetryContext,
...commandArgs
);
} catch (error) {
telemetryContext.properties.errorMessage = error.message;
let isPopupErrorMsg = true;
@ -181,8 +204,9 @@ export class TelemetryWorker {
/**
* Get extension information
*/
private getPackageInfo(context: vscode.ExtensionContext): PackageInfo
|undefined {
private getPackageInfo(
context: vscode.ExtensionContext
): PackageInfo | undefined {
const extension = WorkbenchExtension.getExtension(context);
if (extension) {
const extensionPackage = extension.packageJSON;

Просмотреть файл

@ -1,17 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import * as os from 'os';
import * as path from 'path';
import * as vscode from 'vscode';
import { VSCExpress } from 'vscode-express';
import * as os from "os";
import * as path from "path";
import * as vscode from "vscode";
import { VSCExpress } from "vscode-express";
import { ArduinoPackageManager } from './ArduinoPackageManager';
import { BoardProvider } from './boardProvider';
import { ConfigKey, EventNames, FileNames, OSPlatform } from './constants';
import { TelemetryWorker } from './telemetry';
import { shouldShowLandingPage } from './utils';
import { Board } from './Models/Interfaces/Board';
import { ArduinoPackageManager } from "./ArduinoPackageManager";
import { BoardProvider } from "./boardProvider";
import { ConfigKey, EventNames, FileNames, OSPlatform } from "./constants";
import { TelemetryWorker } from "./telemetry";
import { shouldShowLandingPage } from "./utils";
import { Board } from "./Models/Interfaces/Board";
export interface DeviceInfo {
vendorId: number;
@ -19,29 +19,33 @@ export interface DeviceInfo {
}
export class UsbDetector {
private static _vscexpress: VSCExpress|undefined;
private static _vscexpress: VSCExpress | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private static _usbDetector: any;
constructor(
private context: vscode.ExtensionContext,
private channel: vscode.OutputChannel) {
private context: vscode.ExtensionContext,
private channel: vscode.OutputChannel
) {
const enableUSBDetection = shouldShowLandingPage(context);
if (os.platform() === OSPlatform.LINUX || !enableUSBDetection) {
return;
} else {
// Only load detector module when not in remote
UsbDetector._usbDetector = require('../vendor/node-usb-native').detector;
UsbDetector._usbDetector = require("../vendor/node-usb-native").detector;
}
}
getBoardFromDeviceInfo(device: DeviceInfo): Board|undefined {
getBoardFromDeviceInfo(device: DeviceInfo): Board | undefined {
if (device.vendorId && device.productId) {
const boardFolderPath = this.context.asAbsolutePath(path.join(
FileNames.resourcesFolderName, FileNames.templatesFolderName));
const boardFolderPath = this.context.asAbsolutePath(
path.join(FileNames.resourcesFolderName, FileNames.templatesFolderName)
);
const boardProvider = new BoardProvider(boardFolderPath);
const board = boardProvider.find(
{ vendorId: device.vendorId, productId: device.productId });
const board = boardProvider.find({
vendorId: device.vendorId,
productId: device.productId
});
return board;
}
@ -55,26 +59,36 @@ export class UsbDetector {
const telemetryWorker = TelemetryWorker.getInstance(this.context);
telemetryWorker.callCommandWithTelemetry(
this.context, this.channel, EventNames.detectBoard,
false, async () => {
this.context,
this.channel,
EventNames.detectBoard,
false,
async () => {
if (board.exampleUrl) {
ArduinoPackageManager.installBoard(board);
const exampleUrl = 'example.html?board=' + board.id +
'&url=' + encodeURIComponent(board.exampleUrl || '');
UsbDetector._vscexpress = UsbDetector._vscexpress ||
new VSCExpress(this.context, 'views');
const exampleUrl =
"example.html?board=" +
board.id +
"&url=" +
encodeURIComponent(board.exampleUrl || "");
UsbDetector._vscexpress =
UsbDetector._vscexpress || new VSCExpress(this.context, "views");
UsbDetector._vscexpress.open(
exampleUrl,
board.examplePageName +
' samples - Azure IoT Device Workbench',
vscode.ViewColumn.One, {
board.examplePageName + " samples - Azure IoT Device Workbench",
vscode.ViewColumn.One,
{
enableScripts: true,
enableCommandUris: true,
retainContextWhenHidden: true
});
}
);
}
}, {}, { board: board.name });
},
{},
{ board: board.name }
);
}
// Will not auto pop up landing page next time.
@ -91,16 +105,21 @@ export class UsbDetector {
return;
}
const devices: DeviceInfo[]|undefined =
await UsbDetector._usbDetector.find();
const devices:
| DeviceInfo[]
| undefined = await UsbDetector._usbDetector.find();
if (devices) {
const uniqueDevices: DeviceInfo[] = [];
devices.forEach(device => {
if (uniqueDevices.findIndex(
item => item.vendorId === device.vendorId &&
item.productId === device.productId) < 0) {
if (
uniqueDevices.findIndex(
item =>
item.vendorId === device.vendorId &&
item.productId === device.productId
) < 0
) {
uniqueDevices.push(device);
}
});
@ -108,6 +127,6 @@ export class UsbDetector {
uniqueDevices.forEach(this.showLandingPage.bind(this));
}
UsbDetector._usbDetector.on('add', this.showLandingPage.bind(this));
UsbDetector._usbDetector.on("add", this.showLandingPage.bind(this));
}
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,23 +1,23 @@
import * as assert from 'assert';
import * as vscode from 'vscode';
import * as assert from "assert";
import * as vscode from "vscode";
import { AZ3166Device } from '../src/Models/AZ3166Device';
import { ComponentType } from '../src/Models/Interfaces/Component';
import { DeviceType } from '../src/Models/Interfaces/Device';
import { TelemetryContext, TelemetryWorker } from '../src/telemetry';
import { AZ3166Device } from "../src/Models/AZ3166Device";
import { ComponentType } from "../src/Models/Interfaces/Component";
import { DeviceType } from "../src/Models/Interfaces/Device";
import { TelemetryContext, TelemetryWorker } from "../src/telemetry";
import { TestExtensionContext } from './stub';
import { TestExtensionContext } from "./stub";
suite('IoT Device Workbench: Device', () => {
suite("IoT Device Workbench: Device", () => {
// tslint:disable-next-line: only-arrow-functions
test('property of device should be set correctly', function(done) {
test("property of device should be set correctly", function(done) {
const context = new TestExtensionContext();
const channel = vscode.window.createOutputChannel('IoT workbench test');
const channel = vscode.window.createOutputChannel("IoT workbench test");
const telemetryWorker = TelemetryWorker.getInstance(context);
const telemetryContext: TelemetryContext = telemetryWorker.createContext();
const device = new AZ3166Device(context, channel, telemetryContext, '', []);
const device = new AZ3166Device(context, channel, telemetryContext, "", []);
assert.equal(device.getDeviceType(), DeviceType.MXChipAZ3166);
assert.equal(device.getComponentType(), ComponentType.Device);
done();
});
});
});

Просмотреть файл

@ -2,56 +2,55 @@
// Note: This example test is leveraging the Mocha test framework.
// Please refer to their documentation on https://mochajs.org/ for help.
//
import * as vscode from 'vscode';
import { WorkbenchCommands } from '../src/common/Commands';
import * as vscode from "vscode";
import { WorkbenchCommands } from "../src/common/Commands";
suite('IoT Device Workbench: Commands Tests', () => {
suite("IoT Device Workbench: Commands Tests", () => {
// tslint:disable-next-line: only-arrow-functions
setup(function(done) {
// Ensure that extension is activate while testing
this.timeout(60 * 1000);
const extension =
vscode.extensions.getExtension('vsciot-vscode.vscode-iot-workbench');
const extension = vscode.extensions.getExtension(
"vsciot-vscode.vscode-iot-workbench"
);
if (!extension) {
done('Failed to activate extension');
done("Failed to activate extension");
} else if (!extension.isActive) {
extension.activate().then(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
(_api) => {
_api => {
done();
},
() => {
done('Failed to activate extension');
});
done("Failed to activate extension");
}
);
} else {
done();
}
});
// tslint:disable-next-line: only-arrow-functions
test(
'should be able to run command: iotworkbench.exampleInitialize',
function(done) {
this.timeout(60 * 1000);
try {
vscode.commands.executeCommand(WorkbenchCommands.InitializeProject)
.then(() => {
done();
});
} catch (error) {
done(new Error(error));
}
});
test("should be able to run command: iotworkbench.exampleInitialize", function(done) {
this.timeout(60 * 1000);
try {
vscode.commands
.executeCommand(WorkbenchCommands.InitializeProject)
.then(() => {
done();
});
} catch (error) {
done(new Error(error));
}
});
// tslint:disable-next-line: only-arrow-functions
test('should be able to run command: iotworkbench.help', function(done) {
test("should be able to run command: iotworkbench.help", function(done) {
this.timeout(60 * 1000);
try {
vscode.commands.executeCommand(WorkbenchCommands.Help).then(() => {
done();
});
} catch (error) {
done(new Error(error));
}

Просмотреть файл

@ -1,16 +1,16 @@
import * as assert from 'assert';
import * as assert from "assert";
import { ConfigHandler } from '../src/configHandler';
import { ConfigKey } from '../src/constants';
import { ConfigHandler } from "../src/configHandler";
import { ConfigKey } from "../src/constants";
suite('IoT Device Workbench: Config', () => {
test('should set and get config value correctly', async function() {
suite("IoT Device Workbench: Config", () => {
test("should set and get config value correctly", async function() {
this.timeout(60 * 1000);
assert.equal(ConfigHandler.get<string>(ConfigKey.boardId), 'devkit');
assert.equal(ConfigHandler.get<string>(ConfigKey.devicePath), 'Device');
await ConfigHandler.update(ConfigKey.boardId, 'IoTButton');
assert.equal(ConfigHandler.get<string>(ConfigKey.boardId), 'IoTButton');
await ConfigHandler.update(ConfigKey.boardId, 'devkit');
assert.equal(ConfigHandler.get<string>(ConfigKey.boardId), 'devkit');
assert.equal(ConfigHandler.get<string>(ConfigKey.boardId), "devkit");
assert.equal(ConfigHandler.get<string>(ConfigKey.devicePath), "Device");
await ConfigHandler.update(ConfigKey.boardId, "IoTButton");
assert.equal(ConfigHandler.get<string>(ConfigKey.boardId), "IoTButton");
await ConfigHandler.update(ConfigKey.boardId, "devkit");
assert.equal(ConfigHandler.get<string>(ConfigKey.boardId), "devkit");
});
});
});

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше