Add prettier
This commit is contained in:
Родитель
afdd0dad0f
Коммит
eaceda84d7
|
@ -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(() => {
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
152
src/constants.ts
152
src/constants.ts
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
615
src/extension.ts
615
src/extension.ts
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
76
src/nsat.ts
76
src/nsat.ts
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
106
src/telemetry.ts
106
src/telemetry.ts
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
771
src/utils.ts
771
src/utils.ts
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче