Merge pull request #90 from davidmonterocrespo24/feature/vscode-extension
Feature/vscode extensionpull/74/merge
commit
9aab48ec64
|
|
@ -0,0 +1,499 @@
|
||||||
|
{
|
||||||
|
"name": "velxio-simulator",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "velxio-simulator",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@iarna/toml": "^2.2.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"@types/vscode": "^1.85.0",
|
||||||
|
"esbuild": "^0.20.0",
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.85.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-x64": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@iarna/toml": {
|
||||||
|
"version": "2.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
|
||||||
|
"integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "20.19.37",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
|
||||||
|
"integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/vscode": {
|
||||||
|
"version": "1.110.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.110.0.tgz",
|
||||||
|
"integrity": "sha512-AGuxUEpU4F4mfuQjxPPaQVyuOMhs+VT/xRok1jiHVBubHK7lBRvCuOMZG0LKUwxncrPorJ5qq/uil3IdZBd5lA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/esbuild": {
|
||||||
|
"version": "0.20.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
|
||||||
|
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.20.2",
|
||||||
|
"@esbuild/android-arm": "0.20.2",
|
||||||
|
"@esbuild/android-arm64": "0.20.2",
|
||||||
|
"@esbuild/android-x64": "0.20.2",
|
||||||
|
"@esbuild/darwin-arm64": "0.20.2",
|
||||||
|
"@esbuild/darwin-x64": "0.20.2",
|
||||||
|
"@esbuild/freebsd-arm64": "0.20.2",
|
||||||
|
"@esbuild/freebsd-x64": "0.20.2",
|
||||||
|
"@esbuild/linux-arm": "0.20.2",
|
||||||
|
"@esbuild/linux-arm64": "0.20.2",
|
||||||
|
"@esbuild/linux-ia32": "0.20.2",
|
||||||
|
"@esbuild/linux-loong64": "0.20.2",
|
||||||
|
"@esbuild/linux-mips64el": "0.20.2",
|
||||||
|
"@esbuild/linux-ppc64": "0.20.2",
|
||||||
|
"@esbuild/linux-riscv64": "0.20.2",
|
||||||
|
"@esbuild/linux-s390x": "0.20.2",
|
||||||
|
"@esbuild/linux-x64": "0.20.2",
|
||||||
|
"@esbuild/netbsd-x64": "0.20.2",
|
||||||
|
"@esbuild/openbsd-x64": "0.20.2",
|
||||||
|
"@esbuild/sunos-x64": "0.20.2",
|
||||||
|
"@esbuild/win32-arm64": "0.20.2",
|
||||||
|
"@esbuild/win32-ia32": "0.20.2",
|
||||||
|
"@esbuild/win32-x64": "0.20.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
{
|
||||||
|
"name": "velxio-simulator",
|
||||||
|
"displayName": "Velxio Simulator",
|
||||||
|
"description": "Local Arduino & ESP32 simulator for VS Code — compile, simulate, and debug embedded projects without leaving your editor",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"publisher": "velxio",
|
||||||
|
"license": "MIT",
|
||||||
|
"icon": "media/icon.png",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/davidmonterocrespo24/velxio"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.85.0"
|
||||||
|
},
|
||||||
|
"categories": [
|
||||||
|
"Other",
|
||||||
|
"Testing",
|
||||||
|
"Education"
|
||||||
|
],
|
||||||
|
"keywords": [
|
||||||
|
"arduino",
|
||||||
|
"esp32",
|
||||||
|
"simulator",
|
||||||
|
"emulator",
|
||||||
|
"embedded",
|
||||||
|
"micropython",
|
||||||
|
"raspberry-pi-pico",
|
||||||
|
"wokwi",
|
||||||
|
"electronics"
|
||||||
|
],
|
||||||
|
"activationEvents": [
|
||||||
|
"workspaceContains:velxio.toml",
|
||||||
|
"workspaceContains:diagram.json"
|
||||||
|
],
|
||||||
|
"main": "./dist/extension.js",
|
||||||
|
"contributes": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "velxio.openSimulator",
|
||||||
|
"title": "Open Simulator",
|
||||||
|
"category": "Velxio",
|
||||||
|
"icon": "$(circuit-board)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "velxio.compile",
|
||||||
|
"title": "Compile Sketch",
|
||||||
|
"category": "Velxio",
|
||||||
|
"icon": "$(tools)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "velxio.run",
|
||||||
|
"title": "Run Simulation",
|
||||||
|
"category": "Velxio",
|
||||||
|
"icon": "$(play)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "velxio.stop",
|
||||||
|
"title": "Stop Simulation",
|
||||||
|
"category": "Velxio",
|
||||||
|
"icon": "$(debug-stop)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "velxio.selectBoard",
|
||||||
|
"title": "Select Board",
|
||||||
|
"category": "Velxio",
|
||||||
|
"icon": "$(list-selection)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"menus": {
|
||||||
|
"editor/title": [
|
||||||
|
{
|
||||||
|
"command": "velxio.run",
|
||||||
|
"when": "resourceExtname == .ino || resourceExtname == .py",
|
||||||
|
"group": "navigation"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"configuration": {
|
||||||
|
"title": "Velxio Simulator",
|
||||||
|
"properties": {
|
||||||
|
"velxio.defaultBoard": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "arduino-uno",
|
||||||
|
"enum": [
|
||||||
|
"arduino-uno",
|
||||||
|
"arduino-nano",
|
||||||
|
"arduino-mega",
|
||||||
|
"raspberry-pi-pico",
|
||||||
|
"pi-pico-w",
|
||||||
|
"esp32",
|
||||||
|
"esp32-s3",
|
||||||
|
"esp32-c3",
|
||||||
|
"attiny85"
|
||||||
|
],
|
||||||
|
"description": "Default board type for new projects"
|
||||||
|
},
|
||||||
|
"velxio.autoStartBackend": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Automatically start the compilation backend when needed"
|
||||||
|
},
|
||||||
|
"velxio.backendPort": {
|
||||||
|
"type": "number",
|
||||||
|
"default": 0,
|
||||||
|
"description": "Fixed port for the backend (0 = auto-assign)"
|
||||||
|
},
|
||||||
|
"velxio.arduinoCliPath": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Path to arduino-cli executable (leave empty to auto-detect)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jsonValidation": [
|
||||||
|
{
|
||||||
|
"fileMatch": "diagram.json",
|
||||||
|
"url": "./schemas/diagram.schema.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"vscode:prepublish": "npm run build",
|
||||||
|
"build": "npm run build:extension && npm run build:webview",
|
||||||
|
"build:extension": "esbuild ./src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node --target=node18 --sourcemap",
|
||||||
|
"build:webview": "cd webview && npm run build",
|
||||||
|
"watch": "esbuild ./src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node --target=node18 --sourcemap --watch",
|
||||||
|
"lint": "eslint src/",
|
||||||
|
"package": "vsce package"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.0",
|
||||||
|
"@types/vscode": "^1.85.0",
|
||||||
|
"esbuild": "^0.20.0",
|
||||||
|
"typescript": "^5.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@iarna/toml": "^2.2.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"title": "Velxio Diagram",
|
||||||
|
"description": "Circuit diagram layout (Wokwi-compatible format)",
|
||||||
|
"type": "object",
|
||||||
|
"required": ["version", "parts", "connections"],
|
||||||
|
"properties": {
|
||||||
|
"version": {
|
||||||
|
"type": "integer",
|
||||||
|
"const": 1
|
||||||
|
},
|
||||||
|
"author": { "type": "string" },
|
||||||
|
"editor": { "type": "string" },
|
||||||
|
"parts": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["id", "type", "left", "top"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string" },
|
||||||
|
"type": { "type": "string" },
|
||||||
|
"left": { "type": "number" },
|
||||||
|
"top": { "type": "number" },
|
||||||
|
"rotate": { "type": "number" },
|
||||||
|
"hide": { "type": "boolean" },
|
||||||
|
"attrs": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 3,
|
||||||
|
"maxItems": 4,
|
||||||
|
"items": [
|
||||||
|
{ "type": "string", "description": "Source pin (componentId:pinName)" },
|
||||||
|
{ "type": "string", "description": "Target pin (componentId:pinName)" },
|
||||||
|
{ "type": "string", "description": "Wire color" },
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"description": "Routing hints"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serialMonitor": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"display": { "type": "string", "enum": ["terminal", "plotter"] },
|
||||||
|
"newline": { "type": "string", "enum": ["lf", "cr", "crlf"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
/**
|
||||||
|
* BackendManager — Spawns and manages the Velxio FastAPI backend process.
|
||||||
|
*
|
||||||
|
* The backend provides:
|
||||||
|
* - /api/compile — Arduino sketch compilation via arduino-cli
|
||||||
|
* - /api/simulation/ws — WebSocket bridge to QEMU for ESP32 simulation
|
||||||
|
*
|
||||||
|
* AVR/RP2040 boards don't need the backend (simulation runs in the WebView).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { ChildProcess, spawn } from 'child_process';
|
||||||
|
import * as net from 'net';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
export class BackendManager {
|
||||||
|
private process: ChildProcess | null = null;
|
||||||
|
private _port = 0;
|
||||||
|
private _ready = false;
|
||||||
|
private outputChannel: vscode.OutputChannel;
|
||||||
|
|
||||||
|
constructor(outputChannel: vscode.OutputChannel) {
|
||||||
|
this.outputChannel = outputChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
get port(): number { return this._port; }
|
||||||
|
get ready(): boolean { return this._ready; }
|
||||||
|
get apiBase(): string { return `http://localhost:${this._port}/api`; }
|
||||||
|
|
||||||
|
/** Start the backend on a free port. Returns the port number. */
|
||||||
|
async start(): Promise<number> {
|
||||||
|
if (this.process && this._ready) return this._port;
|
||||||
|
|
||||||
|
this._port = await this.findFreePort();
|
||||||
|
const configPort = vscode.workspace.getConfiguration('velxio').get<number>('backendPort');
|
||||||
|
if (configPort && configPort > 0) {
|
||||||
|
this._port = configPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the backend directory
|
||||||
|
const backendDir = await this.findBackendDir();
|
||||||
|
if (!backendDir) {
|
||||||
|
throw new Error('Velxio backend not found. Please install the Velxio backend or set the path in settings.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.outputChannel.appendLine(`[Backend] Starting on port ${this._port}...`);
|
||||||
|
this.outputChannel.appendLine(`[Backend] Directory: ${backendDir}`);
|
||||||
|
|
||||||
|
// Spawn uvicorn
|
||||||
|
const python = this.findPython(backendDir);
|
||||||
|
this.process = spawn(python, [
|
||||||
|
'-m', 'uvicorn',
|
||||||
|
'app.main:app',
|
||||||
|
'--port', String(this._port),
|
||||||
|
'--host', '127.0.0.1',
|
||||||
|
], {
|
||||||
|
cwd: backendDir,
|
||||||
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
env: { ...process.env, PYTHONUNBUFFERED: '1' },
|
||||||
|
});
|
||||||
|
|
||||||
|
this.process.stdout?.on('data', (data) => {
|
||||||
|
this.outputChannel.appendLine(`[Backend] ${data.toString().trim()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.process.stderr?.on('data', (data) => {
|
||||||
|
this.outputChannel.appendLine(`[Backend] ${data.toString().trim()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.process.on('exit', (code) => {
|
||||||
|
this.outputChannel.appendLine(`[Backend] Process exited (code=${code})`);
|
||||||
|
this._ready = false;
|
||||||
|
this.process = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for the server to be ready
|
||||||
|
await this.waitForReady();
|
||||||
|
this._ready = true;
|
||||||
|
this.outputChannel.appendLine(`[Backend] Ready at ${this.apiBase}`);
|
||||||
|
return this._port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stop the backend process */
|
||||||
|
async stop(): Promise<void> {
|
||||||
|
if (!this.process) return;
|
||||||
|
|
||||||
|
this.outputChannel.appendLine('[Backend] Stopping...');
|
||||||
|
this.process.kill('SIGTERM');
|
||||||
|
|
||||||
|
// Give it 3 seconds to shut down gracefully
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
this.process?.kill('SIGKILL');
|
||||||
|
resolve();
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
this.process?.on('exit', () => {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.process = null;
|
||||||
|
this._ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find the backend directory (relative to extension or workspace) */
|
||||||
|
private async findBackendDir(): Promise<string | null> {
|
||||||
|
// 1. Check relative to the extension (monorepo layout)
|
||||||
|
const extensionDir = path.resolve(__dirname, '..');
|
||||||
|
const monorepoBackend = path.resolve(extensionDir, '..', 'backend');
|
||||||
|
if (fs.existsSync(path.join(monorepoBackend, 'app', 'main.py'))) {
|
||||||
|
return monorepoBackend;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Check workspace folders
|
||||||
|
for (const folder of vscode.workspace.workspaceFolders ?? []) {
|
||||||
|
const wsBackend = path.join(folder.uri.fsPath, 'backend');
|
||||||
|
if (fs.existsSync(path.join(wsBackend, 'app', 'main.py'))) {
|
||||||
|
return wsBackend;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find the correct Python executable (venv or system) */
|
||||||
|
private findPython(backendDir: string): string {
|
||||||
|
// Check for venv
|
||||||
|
const venvPaths = [
|
||||||
|
path.join(backendDir, 'venv', 'Scripts', 'python.exe'), // Windows
|
||||||
|
path.join(backendDir, 'venv', 'bin', 'python'), // Unix
|
||||||
|
path.join(backendDir, '.venv', 'Scripts', 'python.exe'),
|
||||||
|
path.join(backendDir, '.venv', 'bin', 'python'),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const p of venvPaths) {
|
||||||
|
if (fs.existsSync(p)) return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'python';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find a free TCP port */
|
||||||
|
private findFreePort(): Promise<number> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const srv = net.createServer();
|
||||||
|
srv.listen(0, '127.0.0.1', () => {
|
||||||
|
const addr = srv.address() as net.AddressInfo;
|
||||||
|
srv.close(() => resolve(addr.port));
|
||||||
|
});
|
||||||
|
srv.on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wait for the backend health endpoint to respond */
|
||||||
|
private async waitForReady(maxRetries = 30): Promise<void> {
|
||||||
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://127.0.0.1:${this._port}/docs`);
|
||||||
|
if (response.ok) return;
|
||||||
|
} catch {
|
||||||
|
// Not ready yet
|
||||||
|
}
|
||||||
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
}
|
||||||
|
throw new Error(`Backend failed to start after ${maxRetries} seconds`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
/**
|
||||||
|
* FileWatcher — Watches sketch files for changes and triggers recompilation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
export class FileWatcher {
|
||||||
|
private watcher: vscode.FileSystemWatcher | null = null;
|
||||||
|
private debounceTimer: NodeJS.Timeout | null = null;
|
||||||
|
private _onChange = new vscode.EventEmitter<vscode.Uri>();
|
||||||
|
|
||||||
|
/** Fired when a sketch file changes (debounced by 500ms) */
|
||||||
|
public readonly onChange = this._onChange.event;
|
||||||
|
|
||||||
|
/** Start watching sketch files */
|
||||||
|
start(): void {
|
||||||
|
if (this.watcher) return;
|
||||||
|
|
||||||
|
this.watcher = vscode.workspace.createFileSystemWatcher(
|
||||||
|
'**/*.{ino,h,cpp,c,py}',
|
||||||
|
false, // creations
|
||||||
|
false, // changes
|
||||||
|
false, // deletions
|
||||||
|
);
|
||||||
|
|
||||||
|
const debouncedFire = (uri: vscode.Uri) => {
|
||||||
|
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
||||||
|
this.debounceTimer = setTimeout(() => this._onChange.fire(uri), 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.watcher.onDidChange(debouncedFire);
|
||||||
|
this.watcher.onDidCreate(debouncedFire);
|
||||||
|
this.watcher.onDidDelete(debouncedFire);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stop watching */
|
||||||
|
stop(): void {
|
||||||
|
if (this.debounceTimer) {
|
||||||
|
clearTimeout(this.debounceTimer);
|
||||||
|
this.debounceTimer = null;
|
||||||
|
}
|
||||||
|
this.watcher?.dispose();
|
||||||
|
this.watcher = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.stop();
|
||||||
|
this._onChange.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
/**
|
||||||
|
* ProjectConfig — Reads velxio.toml and diagram.json from the workspace.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import TOML from '@iarna/toml';
|
||||||
|
import type { VelxioConfig, DiagramJson, BoardKind } from './types';
|
||||||
|
|
||||||
|
export class ProjectConfig {
|
||||||
|
private workspaceRoot: string;
|
||||||
|
|
||||||
|
constructor(workspaceRoot: string) {
|
||||||
|
this.workspaceRoot = workspaceRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read and parse velxio.toml from the workspace root */
|
||||||
|
readVelxioToml(): VelxioConfig | null {
|
||||||
|
const tomlPath = path.join(this.workspaceRoot, 'velxio.toml');
|
||||||
|
if (!fs.existsSync(tomlPath)) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(tomlPath, 'utf-8');
|
||||||
|
const parsed = TOML.parse(content) as unknown as VelxioConfig;
|
||||||
|
return parsed;
|
||||||
|
} catch (err) {
|
||||||
|
vscode.window.showWarningMessage(`Failed to parse velxio.toml: ${err}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read and parse diagram.json from the workspace root */
|
||||||
|
readDiagramJson(): DiagramJson | null {
|
||||||
|
const jsonPath = path.join(this.workspaceRoot, 'diagram.json');
|
||||||
|
if (!fs.existsSync(jsonPath)) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const content = fs.readFileSync(jsonPath, 'utf-8');
|
||||||
|
return JSON.parse(content) as DiagramJson;
|
||||||
|
} catch (err) {
|
||||||
|
vscode.window.showWarningMessage(`Failed to parse diagram.json: ${err}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Resolve the board kind from config or settings */
|
||||||
|
getBoard(): BoardKind {
|
||||||
|
const config = this.readVelxioToml();
|
||||||
|
if (config?.velxio?.board) {
|
||||||
|
return config.velxio.board as BoardKind;
|
||||||
|
}
|
||||||
|
return vscode.workspace.getConfiguration('velxio').get<BoardKind>('defaultBoard') ?? 'arduino-uno';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the language mode (arduino or micropython) */
|
||||||
|
getLanguageMode(): 'arduino' | 'micropython' {
|
||||||
|
const config = this.readVelxioToml();
|
||||||
|
return config?.velxio?.language ?? 'arduino';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the pre-compiled firmware path (if specified) */
|
||||||
|
getFirmwarePath(): string | null {
|
||||||
|
const config = this.readVelxioToml();
|
||||||
|
if (!config?.velxio?.firmware) return null;
|
||||||
|
return path.resolve(this.workspaceRoot, config.velxio.firmware);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Collect all sketch files (.ino, .h, .cpp, .py) from the workspace */
|
||||||
|
async getSketchFiles(): Promise<Array<{ name: string; content: string }>> {
|
||||||
|
const language = this.getLanguageMode();
|
||||||
|
const pattern = language === 'micropython' ? '**/*.py' : '**/*.{ino,h,cpp,c}';
|
||||||
|
const uris = await vscode.workspace.findFiles(pattern, '**/node_modules/**');
|
||||||
|
const files: Array<{ name: string; content: string }> = [];
|
||||||
|
|
||||||
|
for (const uri of uris) {
|
||||||
|
const relativePath = path.relative(this.workspaceRoot, uri.fsPath);
|
||||||
|
const content = fs.readFileSync(uri.fsPath, 'utf-8');
|
||||||
|
files.push({ name: relativePath, content });
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Create a default velxio.toml in the workspace */
|
||||||
|
async createDefaultConfig(board: BoardKind): Promise<void> {
|
||||||
|
const tomlPath = path.join(this.workspaceRoot, 'velxio.toml');
|
||||||
|
const content = `[velxio]\nversion = 1\nboard = "${board}"\n`;
|
||||||
|
fs.writeFileSync(tomlPath, content, 'utf-8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* SerialTerminal — VS Code pseudo-terminal for simulation serial I/O.
|
||||||
|
*
|
||||||
|
* Provides a native VS Code terminal that displays serial output from the
|
||||||
|
* running simulation and sends user input back to the simulated UART.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
export class SerialTerminal {
|
||||||
|
private terminal: vscode.Terminal | null = null;
|
||||||
|
private writeEmitter = new vscode.EventEmitter<string>();
|
||||||
|
private closeEmitter = new vscode.EventEmitter<number | void>();
|
||||||
|
private _onInput = new vscode.EventEmitter<string>();
|
||||||
|
|
||||||
|
/** Fired when the user types in the terminal */
|
||||||
|
public readonly onInput = this._onInput.event;
|
||||||
|
|
||||||
|
/** Create and show the serial terminal */
|
||||||
|
open(boardName: string): void {
|
||||||
|
if (this.terminal) {
|
||||||
|
this.terminal.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pty: vscode.Pseudoterminal = {
|
||||||
|
onDidWrite: this.writeEmitter.event,
|
||||||
|
onDidClose: this.closeEmitter.event,
|
||||||
|
open: () => {
|
||||||
|
this.writeEmitter.fire(`\x1b[36m--- Velxio Serial Monitor (${boardName}) ---\x1b[0m\r\n`);
|
||||||
|
},
|
||||||
|
close: () => {
|
||||||
|
this.terminal = null;
|
||||||
|
},
|
||||||
|
handleInput: (data: string) => {
|
||||||
|
this._onInput.fire(data);
|
||||||
|
// Echo input in a dim color
|
||||||
|
this.writeEmitter.fire(`\x1b[90m${data}\x1b[0m`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.terminal = vscode.window.createTerminal({
|
||||||
|
name: `Velxio Serial (${boardName})`,
|
||||||
|
pty,
|
||||||
|
iconPath: new vscode.ThemeIcon('circuit-board'),
|
||||||
|
});
|
||||||
|
this.terminal.show(true); // preserve focus
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Write serial output to the terminal */
|
||||||
|
write(text: string): void {
|
||||||
|
if (!this.terminal) return;
|
||||||
|
// Convert \n to \r\n for terminal display
|
||||||
|
this.writeEmitter.fire(text.replace(/\n/g, '\r\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Clear the terminal */
|
||||||
|
clear(): void {
|
||||||
|
this.writeEmitter.fire('\x1b[2J\x1b[H'); // ANSI clear + home
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Close the terminal */
|
||||||
|
close(): void {
|
||||||
|
if (this.terminal) {
|
||||||
|
this.closeEmitter.fire();
|
||||||
|
this.terminal.dispose();
|
||||||
|
this.terminal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.close();
|
||||||
|
this.writeEmitter.dispose();
|
||||||
|
this.closeEmitter.dispose();
|
||||||
|
this._onInput.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,198 @@
|
||||||
|
/**
|
||||||
|
* SimulatorPanel — Manages the VS Code WebView panel that hosts the simulation UI.
|
||||||
|
*
|
||||||
|
* The WebView runs a stripped-down version of the Velxio React frontend
|
||||||
|
* (SimulatorCanvas + SerialMonitor) with simulation engines (avr8js, rp2040js)
|
||||||
|
* running directly in the WebView's JavaScript context.
|
||||||
|
*
|
||||||
|
* Communication with the extension host uses VS Code's postMessage API.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { ToWebviewMessage, FromWebviewMessage, BoardKind } from './types';
|
||||||
|
|
||||||
|
export class SimulatorPanel {
|
||||||
|
public static readonly viewType = 'velxio.simulator';
|
||||||
|
private static instance: SimulatorPanel | undefined;
|
||||||
|
|
||||||
|
private readonly panel: vscode.WebviewPanel;
|
||||||
|
private readonly extensionUri: vscode.Uri;
|
||||||
|
private disposables: vscode.Disposable[] = [];
|
||||||
|
private _ready = false;
|
||||||
|
private _onSerialOutput = new vscode.EventEmitter<string>();
|
||||||
|
private _onSimulationState = new vscode.EventEmitter<boolean>();
|
||||||
|
private _onReady = new vscode.EventEmitter<void>();
|
||||||
|
|
||||||
|
/** Fired when serial data arrives from the simulation */
|
||||||
|
public readonly onSerialOutput = this._onSerialOutput.event;
|
||||||
|
/** Fired when simulation starts/stops */
|
||||||
|
public readonly onSimulationState = this._onSimulationState.event;
|
||||||
|
/** Fired when the WebView is ready */
|
||||||
|
public readonly onReady = this._onReady.event;
|
||||||
|
|
||||||
|
public get ready(): boolean { return this._ready; }
|
||||||
|
|
||||||
|
/** Get or create the singleton panel */
|
||||||
|
public static createOrShow(extensionUri: vscode.Uri): SimulatorPanel {
|
||||||
|
const column = vscode.ViewColumn.Beside;
|
||||||
|
|
||||||
|
if (SimulatorPanel.instance) {
|
||||||
|
SimulatorPanel.instance.panel.reveal(column);
|
||||||
|
return SimulatorPanel.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
const panel = vscode.window.createWebviewPanel(
|
||||||
|
SimulatorPanel.viewType,
|
||||||
|
'Velxio Simulator',
|
||||||
|
column,
|
||||||
|
{
|
||||||
|
enableScripts: true,
|
||||||
|
retainContextWhenHidden: true,
|
||||||
|
localResourceRoots: [
|
||||||
|
vscode.Uri.joinPath(extensionUri, 'dist', 'webview'),
|
||||||
|
vscode.Uri.joinPath(extensionUri, 'media'),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
SimulatorPanel.instance = new SimulatorPanel(panel, extensionUri);
|
||||||
|
return SimulatorPanel.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(panel: vscode.WebviewPanel, extensionUri: vscode.Uri) {
|
||||||
|
this.panel = panel;
|
||||||
|
this.extensionUri = extensionUri;
|
||||||
|
|
||||||
|
this.panel.webview.html = this.getHtmlContent();
|
||||||
|
|
||||||
|
// Handle messages from the WebView
|
||||||
|
this.panel.webview.onDidReceiveMessage(
|
||||||
|
(msg: FromWebviewMessage) => this.handleMessage(msg),
|
||||||
|
null,
|
||||||
|
this.disposables,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Cleanup on dispose
|
||||||
|
this.panel.onDidDispose(() => this.dispose(), null, this.disposables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send a message to the WebView */
|
||||||
|
public postMessage(message: ToWebviewMessage): void {
|
||||||
|
if (this._ready) {
|
||||||
|
this.panel.webview.postMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send compiled hex to the WebView */
|
||||||
|
public loadHex(hex: string, board: BoardKind): void {
|
||||||
|
this.postMessage({ type: 'loadHex', hex, board });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send firmware binary (base64) for ESP32 */
|
||||||
|
public loadBinary(firmwareBase64: string, board: BoardKind): void {
|
||||||
|
this.postMessage({ type: 'loadBinary', firmwareBase64, board });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Load MicroPython files */
|
||||||
|
public loadMicroPython(files: Array<{ name: string; content: string }>, board: BoardKind): void {
|
||||||
|
this.postMessage({ type: 'loadMicroPython', files, board });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Start the simulation */
|
||||||
|
public start(): void {
|
||||||
|
this.postMessage({ type: 'start' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stop the simulation */
|
||||||
|
public stop(): void {
|
||||||
|
this.postMessage({ type: 'stop' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send serial input text */
|
||||||
|
public serialInput(text: string): void {
|
||||||
|
this.postMessage({ type: 'serialInput', text });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the board type */
|
||||||
|
public setBoard(board: BoardKind): void {
|
||||||
|
this.postMessage({ type: 'setBoard', board });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Set the backend API base URL */
|
||||||
|
public setApiBase(apiBase: string): void {
|
||||||
|
this.postMessage({ type: 'setApiBase', apiBase });
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMessage(msg: FromWebviewMessage): void {
|
||||||
|
switch (msg.type) {
|
||||||
|
case 'ready':
|
||||||
|
this._ready = true;
|
||||||
|
this._onReady.fire();
|
||||||
|
break;
|
||||||
|
case 'serialOutput':
|
||||||
|
this._onSerialOutput.fire(msg.text);
|
||||||
|
break;
|
||||||
|
case 'simulationState':
|
||||||
|
this._onSimulationState.fire(msg.running);
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
vscode.window.showErrorMessage(`Velxio: ${msg.message}`);
|
||||||
|
break;
|
||||||
|
case 'log':
|
||||||
|
// Forward WebView logs to the output channel
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHtmlContent(): string {
|
||||||
|
const webview = this.panel.webview;
|
||||||
|
const webviewDistUri = vscode.Uri.joinPath(this.extensionUri, 'dist', 'webview');
|
||||||
|
|
||||||
|
// In development, point to local files; in production, use bundled assets
|
||||||
|
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(webviewDistUri, 'index.js'));
|
||||||
|
const styleUri = webview.asWebviewUri(vscode.Uri.joinPath(webviewDistUri, 'index.css'));
|
||||||
|
|
||||||
|
const nonce = getNonce();
|
||||||
|
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="
|
||||||
|
default-src 'none';
|
||||||
|
style-src ${webview.cspSource} 'unsafe-inline';
|
||||||
|
script-src 'nonce-${nonce}' 'unsafe-eval';
|
||||||
|
img-src ${webview.cspSource} data: https:;
|
||||||
|
font-src ${webview.cspSource};
|
||||||
|
connect-src http://127.0.0.1:* ws://127.0.0.1:* https://micropython.org;
|
||||||
|
">
|
||||||
|
<link rel="stylesheet" href="${styleUri}">
|
||||||
|
<title>Velxio Simulator</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private dispose(): void {
|
||||||
|
SimulatorPanel.instance = undefined;
|
||||||
|
this._onSerialOutput.dispose();
|
||||||
|
this._onSimulationState.dispose();
|
||||||
|
this._onReady.dispose();
|
||||||
|
for (const d of this.disposables) d.dispose();
|
||||||
|
this.disposables = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNonce(): string {
|
||||||
|
let text = '';
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||||
|
for (let i = 0; i < 32; i++) {
|
||||||
|
text += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,285 @@
|
||||||
|
/**
|
||||||
|
* Velxio VS Code Extension — Entry point
|
||||||
|
*
|
||||||
|
* Provides commands to compile, simulate, and interact with Arduino/ESP32
|
||||||
|
* sketches directly within VS Code. Simulation runs locally using avr8js,
|
||||||
|
* rp2040js (in the WebView), and QEMU (via the backend) for ESP32 boards.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { SimulatorPanel } from './SimulatorPanel';
|
||||||
|
import { BackendManager } from './BackendManager';
|
||||||
|
import { ProjectConfig } from './ProjectConfig';
|
||||||
|
import { SerialTerminal } from './SerialTerminal';
|
||||||
|
import { FileWatcher } from './FileWatcher';
|
||||||
|
import { BOARD_LABELS, type BoardKind } from './types';
|
||||||
|
|
||||||
|
let backend: BackendManager;
|
||||||
|
let serialTerminal: SerialTerminal;
|
||||||
|
let fileWatcher: FileWatcher;
|
||||||
|
let outputChannel: vscode.OutputChannel;
|
||||||
|
let statusBarItem: vscode.StatusBarItem;
|
||||||
|
|
||||||
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
|
outputChannel = vscode.window.createOutputChannel('Velxio');
|
||||||
|
backend = new BackendManager(outputChannel);
|
||||||
|
serialTerminal = new SerialTerminal();
|
||||||
|
fileWatcher = new FileWatcher();
|
||||||
|
|
||||||
|
// Status bar item showing current board
|
||||||
|
statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 50);
|
||||||
|
statusBarItem.command = 'velxio.selectBoard';
|
||||||
|
statusBarItem.tooltip = 'Click to change board';
|
||||||
|
updateStatusBar('arduino-uno');
|
||||||
|
|
||||||
|
// ── Commands ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('velxio.openSimulator', () => {
|
||||||
|
const panel = SimulatorPanel.createOrShow(context.extensionUri);
|
||||||
|
setupPanelListeners(panel, context);
|
||||||
|
statusBarItem.show();
|
||||||
|
}),
|
||||||
|
|
||||||
|
vscode.commands.registerCommand('velxio.compile', async () => {
|
||||||
|
await compileAndLoad(context);
|
||||||
|
}),
|
||||||
|
|
||||||
|
vscode.commands.registerCommand('velxio.run', async () => {
|
||||||
|
const panel = SimulatorPanel.createOrShow(context.extensionUri);
|
||||||
|
setupPanelListeners(panel, context);
|
||||||
|
|
||||||
|
if (!panel.ready) {
|
||||||
|
// Wait for the WebView to initialize
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
const disposable = panel.onReady(() => { disposable.dispose(); resolve(); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await compileAndLoad(context);
|
||||||
|
panel.start();
|
||||||
|
}),
|
||||||
|
|
||||||
|
vscode.commands.registerCommand('velxio.stop', () => {
|
||||||
|
const panel = SimulatorPanel.createOrShow(context.extensionUri);
|
||||||
|
panel.stop();
|
||||||
|
}),
|
||||||
|
|
||||||
|
vscode.commands.registerCommand('velxio.selectBoard', async () => {
|
||||||
|
const boards = Object.entries(BOARD_LABELS) as [BoardKind, string][];
|
||||||
|
const items = boards.map(([kind, label]) => ({
|
||||||
|
label,
|
||||||
|
description: kind,
|
||||||
|
kind: kind,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const selected = await vscode.window.showQuickPick(items, {
|
||||||
|
placeHolder: 'Select a board',
|
||||||
|
title: 'Velxio: Select Board',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
const boardKind = selected.description as BoardKind;
|
||||||
|
updateStatusBar(boardKind);
|
||||||
|
|
||||||
|
// Update velxio.toml if it exists
|
||||||
|
const workspaceRoot = getWorkspaceRoot();
|
||||||
|
if (workspaceRoot) {
|
||||||
|
const config = new ProjectConfig(workspaceRoot);
|
||||||
|
const existingConfig = config.readVelxioToml();
|
||||||
|
if (existingConfig) {
|
||||||
|
await config.createDefaultConfig(boardKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the WebView
|
||||||
|
try {
|
||||||
|
const panel = SimulatorPanel.createOrShow(context.extensionUri);
|
||||||
|
panel.setBoard(boardKind);
|
||||||
|
} catch {
|
||||||
|
// Panel not open yet, that's fine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Auto-activation ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// If velxio.toml or diagram.json exists, show the status bar
|
||||||
|
const workspaceRoot = getWorkspaceRoot();
|
||||||
|
if (workspaceRoot) {
|
||||||
|
const config = new ProjectConfig(workspaceRoot);
|
||||||
|
const velxioConfig = config.readVelxioToml();
|
||||||
|
if (velxioConfig) {
|
||||||
|
updateStatusBar(config.getBoard());
|
||||||
|
statusBarItem.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Cleanup ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
outputChannel,
|
||||||
|
statusBarItem,
|
||||||
|
serialTerminal,
|
||||||
|
fileWatcher,
|
||||||
|
{ dispose: () => { backend.stop(); } },
|
||||||
|
);
|
||||||
|
|
||||||
|
outputChannel.appendLine('Velxio extension activated');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deactivate() {
|
||||||
|
backend.stop();
|
||||||
|
fileWatcher.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function getWorkspaceRoot(): string | null {
|
||||||
|
const folders = vscode.workspace.workspaceFolders;
|
||||||
|
return folders?.[0]?.uri.fsPath ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatusBar(board: BoardKind): void {
|
||||||
|
const label = BOARD_LABELS[board] ?? board;
|
||||||
|
statusBarItem.text = `$(circuit-board) ${label}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let panelListenersSet = false;
|
||||||
|
|
||||||
|
function setupPanelListeners(panel: SimulatorPanel, context: vscode.ExtensionContext): void {
|
||||||
|
if (panelListenersSet) return;
|
||||||
|
panelListenersSet = true;
|
||||||
|
|
||||||
|
// Wire serial output to the VS Code terminal
|
||||||
|
panel.onSerialOutput((text) => {
|
||||||
|
serialTerminal.write(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wire terminal input back to the simulation
|
||||||
|
serialTerminal.onInput((text) => {
|
||||||
|
panel.serialInput(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the panel is ready, send initial configuration
|
||||||
|
panel.onReady(async () => {
|
||||||
|
const workspaceRoot = getWorkspaceRoot();
|
||||||
|
if (!workspaceRoot) return;
|
||||||
|
|
||||||
|
const config = new ProjectConfig(workspaceRoot);
|
||||||
|
const board = config.getBoard();
|
||||||
|
panel.setBoard(board);
|
||||||
|
|
||||||
|
// Read diagram.json if it exists
|
||||||
|
const diagram = config.readDiagramJson();
|
||||||
|
if (diagram) {
|
||||||
|
panel.postMessage({ type: 'setDiagram', diagram });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start backend if needed (for ESP32 boards)
|
||||||
|
if (needsBackend(board)) {
|
||||||
|
try {
|
||||||
|
await backend.start();
|
||||||
|
panel.setApiBase(backend.apiBase);
|
||||||
|
} catch (err) {
|
||||||
|
outputChannel.appendLine(`[Backend] Failed to start: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function needsBackend(board: BoardKind): boolean {
|
||||||
|
// Arduino compilation always needs the backend
|
||||||
|
// ESP32 boards also need QEMU via the backend WebSocket
|
||||||
|
return true; // For MVP, always start the backend
|
||||||
|
}
|
||||||
|
|
||||||
|
async function compileAndLoad(context: vscode.ExtensionContext): Promise<void> {
|
||||||
|
const workspaceRoot = getWorkspaceRoot();
|
||||||
|
if (!workspaceRoot) {
|
||||||
|
vscode.window.showErrorMessage('No workspace folder open');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = new ProjectConfig(workspaceRoot);
|
||||||
|
const board = config.getBoard();
|
||||||
|
const language = config.getLanguageMode();
|
||||||
|
|
||||||
|
const panel = SimulatorPanel.createOrShow(context.extensionUri);
|
||||||
|
setupPanelListeners(panel, context);
|
||||||
|
|
||||||
|
// Check for pre-compiled firmware first
|
||||||
|
const firmwarePath = config.getFirmwarePath();
|
||||||
|
if (firmwarePath) {
|
||||||
|
outputChannel.appendLine(`[Compile] Loading pre-compiled firmware: ${firmwarePath}`);
|
||||||
|
const fs = await import('fs');
|
||||||
|
const data = fs.readFileSync(firmwarePath);
|
||||||
|
|
||||||
|
if (firmwarePath.endsWith('.hex')) {
|
||||||
|
panel.loadHex(data.toString('utf-8'), board);
|
||||||
|
} else {
|
||||||
|
panel.loadBinary(data.toString('base64'), board);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MicroPython: just send .py files
|
||||||
|
if (language === 'micropython') {
|
||||||
|
const files = await config.getSketchFiles();
|
||||||
|
panel.loadMicroPython(files, board);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arduino: compile via backend
|
||||||
|
try {
|
||||||
|
await backend.start();
|
||||||
|
|
||||||
|
const files = await config.getSketchFiles();
|
||||||
|
outputChannel.appendLine(`[Compile] Compiling ${files.length} files for ${board}...`);
|
||||||
|
|
||||||
|
await vscode.window.withProgress(
|
||||||
|
{ location: vscode.ProgressLocation.Notification, title: 'Velxio: Compiling...' },
|
||||||
|
async () => {
|
||||||
|
const response = await fetch(`${backend.apiBase}/compile`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
files: files.map(f => ({ name: f.name, content: f.content })),
|
||||||
|
board_fqbn: getBoardFqbn(board),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json() as { detail?: string };
|
||||||
|
throw new Error(error.detail ?? `Compilation failed (${response.status})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json() as { hex?: string; binary?: string };
|
||||||
|
if (result.hex) {
|
||||||
|
panel.loadHex(result.hex, board);
|
||||||
|
outputChannel.appendLine('[Compile] Success — hex loaded');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
vscode.window.showErrorMessage(`Compilation failed: ${msg}`);
|
||||||
|
outputChannel.appendLine(`[Compile] Error: ${msg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBoardFqbn(board: BoardKind): string {
|
||||||
|
const fqbnMap: Record<string, string> = {
|
||||||
|
'arduino-uno': 'arduino:avr:uno',
|
||||||
|
'arduino-nano': 'arduino:avr:nano:cpu=atmega328',
|
||||||
|
'arduino-mega': 'arduino:avr:mega',
|
||||||
|
'raspberry-pi-pico': 'rp2040:rp2040:rpipico',
|
||||||
|
'pi-pico-w': 'rp2040:rp2040:rpipicow',
|
||||||
|
'esp32': 'esp32:esp32:esp32',
|
||||||
|
'esp32-s3': 'esp32:esp32:esp32s3',
|
||||||
|
'esp32-c3': 'esp32:esp32:esp32c3',
|
||||||
|
'attiny85': 'ATTinyCore:avr:attinyx5:chip=85,clock=internal16mhz',
|
||||||
|
};
|
||||||
|
return fqbnMap[board] ?? 'arduino:avr:uno';
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
/**
|
||||||
|
* Shared types for extension ↔ WebView communication.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type BoardKind =
|
||||||
|
| 'arduino-uno'
|
||||||
|
| 'arduino-nano'
|
||||||
|
| 'arduino-mega'
|
||||||
|
| 'raspberry-pi-pico'
|
||||||
|
| 'pi-pico-w'
|
||||||
|
| 'esp32'
|
||||||
|
| 'esp32-s3'
|
||||||
|
| 'esp32-c3'
|
||||||
|
| 'attiny85';
|
||||||
|
|
||||||
|
export type LanguageMode = 'arduino' | 'micropython';
|
||||||
|
|
||||||
|
/** Messages from the VS Code extension → WebView */
|
||||||
|
export type ToWebviewMessage =
|
||||||
|
| { type: 'loadHex'; hex: string; board: BoardKind }
|
||||||
|
| { type: 'loadBinary'; firmwareBase64: string; board: BoardKind }
|
||||||
|
| { type: 'loadMicroPython'; files: Array<{ name: string; content: string }>; board: BoardKind }
|
||||||
|
| { type: 'start' }
|
||||||
|
| { type: 'stop' }
|
||||||
|
| { type: 'serialInput'; text: string }
|
||||||
|
| { type: 'setBoard'; board: BoardKind }
|
||||||
|
| { type: 'setDiagram'; diagram: DiagramJson }
|
||||||
|
| { type: 'setApiBase'; apiBase: string };
|
||||||
|
|
||||||
|
/** Messages from the WebView → VS Code extension */
|
||||||
|
export type FromWebviewMessage =
|
||||||
|
| { type: 'ready' }
|
||||||
|
| { type: 'serialOutput'; text: string }
|
||||||
|
| { type: 'simulationState'; running: boolean }
|
||||||
|
| { type: 'error'; message: string }
|
||||||
|
| { type: 'requestCompile'; files: Array<{ name: string; content: string }>; board: string; fqbn: string }
|
||||||
|
| { type: 'log'; level: 'info' | 'warn' | 'error'; message: string };
|
||||||
|
|
||||||
|
/** diagram.json format (Wokwi-compatible) */
|
||||||
|
export interface DiagramJson {
|
||||||
|
version: 1;
|
||||||
|
author?: string;
|
||||||
|
editor?: string;
|
||||||
|
parts: DiagramPart[];
|
||||||
|
connections: DiagramConnection[];
|
||||||
|
serialMonitor?: { display?: string; newline?: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DiagramPart {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
left: number;
|
||||||
|
top: number;
|
||||||
|
rotate?: number;
|
||||||
|
hide?: boolean;
|
||||||
|
attrs?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** [fromPin, toPin, wireColor, routingHints?] */
|
||||||
|
export type DiagramConnection = [string, string, string, string[]?];
|
||||||
|
|
||||||
|
/** velxio.toml parsed config */
|
||||||
|
export interface VelxioConfig {
|
||||||
|
velxio: {
|
||||||
|
version: number;
|
||||||
|
board?: string;
|
||||||
|
firmware?: string;
|
||||||
|
elf?: string;
|
||||||
|
language?: LanguageMode;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BOARD_LABELS: Record<BoardKind, string> = {
|
||||||
|
'arduino-uno': 'Arduino Uno',
|
||||||
|
'arduino-nano': 'Arduino Nano',
|
||||||
|
'arduino-mega': 'Arduino Mega 2560',
|
||||||
|
'raspberry-pi-pico': 'Raspberry Pi Pico',
|
||||||
|
'pi-pico-w': 'Raspberry Pi Pico W',
|
||||||
|
'esp32': 'ESP32 DevKit V1',
|
||||||
|
'esp32-s3': 'ESP32-S3 DevKit',
|
||||||
|
'esp32-c3': 'ESP32-C3 DevKit',
|
||||||
|
'attiny85': 'ATtiny85',
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist", "webview"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Velxio Simulator</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/webviewApp.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "velxio-webview",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"dev": "vite build --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^19.0.0",
|
||||||
|
"@types/react-dom": "^19.0.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
|
"typescript": "^5.3.0",
|
||||||
|
"vite": "^6.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* WebView ↔ Extension bridge
|
||||||
|
*
|
||||||
|
* Receives messages from the VS Code extension host and translates them
|
||||||
|
* into simulation actions. Sends simulation events back to the extension.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// @ts-expect-error — acquireVsCodeApi is injected by VS Code's webview runtime
|
||||||
|
const vscode = acquireVsCodeApi();
|
||||||
|
|
||||||
|
export type ToWebviewMessage =
|
||||||
|
| { type: 'loadHex'; hex: string; board: string }
|
||||||
|
| { type: 'loadBinary'; firmwareBase64: string; board: string }
|
||||||
|
| { type: 'loadMicroPython'; files: Array<{ name: string; content: string }>; board: string }
|
||||||
|
| { type: 'start' }
|
||||||
|
| { type: 'stop' }
|
||||||
|
| { type: 'serialInput'; text: string }
|
||||||
|
| { type: 'setBoard'; board: string }
|
||||||
|
| { type: 'setDiagram'; diagram: unknown }
|
||||||
|
| { type: 'setApiBase'; apiBase: string };
|
||||||
|
|
||||||
|
type MessageHandler = (msg: ToWebviewMessage) => void;
|
||||||
|
const handlers: MessageHandler[] = [];
|
||||||
|
|
||||||
|
/** Register a handler for messages from the extension */
|
||||||
|
export function onMessage(handler: MessageHandler): void {
|
||||||
|
handlers.push(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send a message to the extension host */
|
||||||
|
export function postToExtension(message: unknown): void {
|
||||||
|
vscode.postMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Notify the extension that the WebView is ready */
|
||||||
|
export function notifyReady(): void {
|
||||||
|
postToExtension({ type: 'ready' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send serial output to the extension */
|
||||||
|
export function sendSerialOutput(text: string): void {
|
||||||
|
postToExtension({ type: 'serialOutput', text });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send simulation state change to the extension */
|
||||||
|
export function sendSimulationState(running: boolean): void {
|
||||||
|
postToExtension({ type: 'simulationState', running });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Send an error to the extension */
|
||||||
|
export function sendError(message: string): void {
|
||||||
|
postToExtension({ type: 'error', message });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for messages from the extension host
|
||||||
|
window.addEventListener('message', (event) => {
|
||||||
|
const msg = event.data as ToWebviewMessage;
|
||||||
|
for (const handler of handlers) {
|
||||||
|
handler(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,259 @@
|
||||||
|
/**
|
||||||
|
* WebView App — Stripped-down Velxio simulator for VS Code WebView.
|
||||||
|
*
|
||||||
|
* This is a minimal React app that embeds:
|
||||||
|
* - SimulatorCanvas (board visualization + components)
|
||||||
|
* - SerialMonitor (serial output/input)
|
||||||
|
*
|
||||||
|
* It does NOT include: routing, auth, Monaco editor, project persistence.
|
||||||
|
* Communication with the VS Code extension is via postMessage bridge.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { onMessage, notifyReady, sendSerialOutput, sendSimulationState, sendError } from './bridge';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder app — Phase 1 MVP
|
||||||
|
*
|
||||||
|
* In the full implementation, this will import and render:
|
||||||
|
* - SimulatorCanvas from the shared frontend/src/components/
|
||||||
|
* - SerialMonitor from the shared frontend/src/components/
|
||||||
|
* - useSimulatorStore from the shared frontend/src/store/
|
||||||
|
*
|
||||||
|
* For now, this is a functional skeleton that handles the postMessage
|
||||||
|
* protocol and shows a basic simulation status panel.
|
||||||
|
*/
|
||||||
|
const App: React.FC = () => {
|
||||||
|
const [board, setBoard] = useState('arduino-uno');
|
||||||
|
const [running, setRunning] = useState(false);
|
||||||
|
const [serialOutput, setSerialOutput] = useState('');
|
||||||
|
const [status, setStatus] = useState('Ready');
|
||||||
|
const [hexLoaded, setHexLoaded] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onMessage((msg) => {
|
||||||
|
switch (msg.type) {
|
||||||
|
case 'setBoard':
|
||||||
|
setBoard(msg.board);
|
||||||
|
setStatus(`Board: ${msg.board}`);
|
||||||
|
break;
|
||||||
|
case 'loadHex':
|
||||||
|
setHexLoaded(true);
|
||||||
|
setStatus(`Firmware loaded for ${msg.board}`);
|
||||||
|
break;
|
||||||
|
case 'loadBinary':
|
||||||
|
setHexLoaded(true);
|
||||||
|
setStatus(`Binary firmware loaded for ${msg.board}`);
|
||||||
|
break;
|
||||||
|
case 'loadMicroPython':
|
||||||
|
setHexLoaded(true);
|
||||||
|
setStatus(`MicroPython loaded (${msg.files.length} files)`);
|
||||||
|
break;
|
||||||
|
case 'start':
|
||||||
|
setRunning(true);
|
||||||
|
setStatus('Simulation running');
|
||||||
|
sendSimulationState(true);
|
||||||
|
break;
|
||||||
|
case 'stop':
|
||||||
|
setRunning(false);
|
||||||
|
setStatus('Simulation stopped');
|
||||||
|
sendSimulationState(false);
|
||||||
|
break;
|
||||||
|
case 'serialInput':
|
||||||
|
setSerialOutput(prev => prev + `> ${msg.text}\n`);
|
||||||
|
break;
|
||||||
|
case 'setApiBase':
|
||||||
|
setStatus(`Backend: ${msg.apiBase}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tell the extension we're ready
|
||||||
|
notifyReady();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={styles.container}>
|
||||||
|
{/* Header */}
|
||||||
|
<div style={styles.header}>
|
||||||
|
<span style={styles.logo}>Velxio</span>
|
||||||
|
<span style={styles.boardBadge}>{board}</span>
|
||||||
|
<span style={{
|
||||||
|
...styles.statusDot,
|
||||||
|
background: running ? '#4caf50' : hexLoaded ? '#ff9800' : '#666',
|
||||||
|
}} />
|
||||||
|
<span style={styles.status}>{status}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Simulation Canvas Placeholder */}
|
||||||
|
<div style={styles.canvas}>
|
||||||
|
<div style={styles.canvasPlaceholder}>
|
||||||
|
<div style={styles.boardIcon}>
|
||||||
|
{board.includes('esp32') ? '⬡' : board.includes('pico') ? '◆' : '⬤'}
|
||||||
|
</div>
|
||||||
|
<div style={styles.boardName}>{board}</div>
|
||||||
|
{running && (
|
||||||
|
<div style={styles.runningIndicator}>
|
||||||
|
<span style={styles.pulsingDot} />
|
||||||
|
Simulating...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!running && !hexLoaded && (
|
||||||
|
<div style={styles.hint}>
|
||||||
|
Use <code>Velxio: Run Simulation</code> to start
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!running && hexLoaded && (
|
||||||
|
<div style={styles.hint}>
|
||||||
|
Firmware loaded. Press <strong>Run</strong> to start.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Serial Monitor */}
|
||||||
|
<div style={styles.serialContainer}>
|
||||||
|
<div style={styles.serialHeader}>
|
||||||
|
<span style={styles.serialTitle}>Serial Monitor</span>
|
||||||
|
{running && <span style={styles.serialBadge}>CONNECTED</span>}
|
||||||
|
</div>
|
||||||
|
<pre style={styles.serialOutput}>
|
||||||
|
{serialOutput || (running ? 'Waiting for serial data...\n' : 'Start simulation to see output.\n')}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles: Record<string, React.CSSProperties> = {
|
||||||
|
container: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100vh',
|
||||||
|
background: 'var(--vscode-editor-background, #1e1e1e)',
|
||||||
|
color: 'var(--vscode-editor-foreground, #cccccc)',
|
||||||
|
fontFamily: 'var(--vscode-font-family, monospace)',
|
||||||
|
fontSize: 13,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
padding: '6px 12px',
|
||||||
|
background: 'var(--vscode-titleBar-activeBackground, #3c3c3c)',
|
||||||
|
borderBottom: '1px solid var(--vscode-panel-border, #333)',
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
fontWeight: 700,
|
||||||
|
fontSize: 14,
|
||||||
|
color: 'var(--vscode-textLink-foreground, #4fc3f7)',
|
||||||
|
},
|
||||||
|
boardBadge: {
|
||||||
|
background: 'var(--vscode-badge-background, #0e639c)',
|
||||||
|
color: 'var(--vscode-badge-foreground, #fff)',
|
||||||
|
padding: '1px 8px',
|
||||||
|
borderRadius: 10,
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
statusDot: {
|
||||||
|
width: 8,
|
||||||
|
height: 8,
|
||||||
|
borderRadius: '50%',
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
fontSize: 11,
|
||||||
|
color: 'var(--vscode-descriptionForeground, #999)',
|
||||||
|
},
|
||||||
|
canvas: {
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
minHeight: 0,
|
||||||
|
background: 'var(--vscode-editor-background, #1e1e1e)',
|
||||||
|
},
|
||||||
|
canvasPlaceholder: {
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
padding: 40,
|
||||||
|
},
|
||||||
|
boardIcon: {
|
||||||
|
fontSize: 64,
|
||||||
|
marginBottom: 16,
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
boardName: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: 600,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
runningIndicator: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 8,
|
||||||
|
color: '#4caf50',
|
||||||
|
fontSize: 14,
|
||||||
|
marginTop: 12,
|
||||||
|
},
|
||||||
|
pulsingDot: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: '#4caf50',
|
||||||
|
animation: 'pulse 1.5s ease-in-out infinite',
|
||||||
|
},
|
||||||
|
hint: {
|
||||||
|
color: 'var(--vscode-descriptionForeground, #888)',
|
||||||
|
fontSize: 12,
|
||||||
|
marginTop: 12,
|
||||||
|
},
|
||||||
|
serialContainer: {
|
||||||
|
height: 200,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
borderTop: '1px solid var(--vscode-panel-border, #333)',
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
serialHeader: {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
padding: '4px 12px',
|
||||||
|
background: 'var(--vscode-sideBar-background, #252526)',
|
||||||
|
borderBottom: '1px solid var(--vscode-panel-border, #333)',
|
||||||
|
},
|
||||||
|
serialTitle: {
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
|
serialBadge: {
|
||||||
|
background: '#4caf50',
|
||||||
|
color: '#fff',
|
||||||
|
padding: '0 6px',
|
||||||
|
borderRadius: 3,
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: 700,
|
||||||
|
},
|
||||||
|
serialOutput: {
|
||||||
|
flex: 1,
|
||||||
|
margin: 0,
|
||||||
|
padding: 8,
|
||||||
|
color: '#00ff41',
|
||||||
|
background: '#0a0a0a',
|
||||||
|
overflowY: 'auto' as const,
|
||||||
|
whiteSpace: 'pre-wrap' as const,
|
||||||
|
fontSize: 12,
|
||||||
|
lineHeight: 1.4,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mount
|
||||||
|
const root = document.getElementById('root');
|
||||||
|
if (root) {
|
||||||
|
createRoot(root).render(<App />);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"]
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
build: {
|
||||||
|
outDir: path.resolve(__dirname, '..', 'dist', 'webview'),
|
||||||
|
emptyOutDir: true,
|
||||||
|
rollupOptions: {
|
||||||
|
input: path.resolve(__dirname, 'src', 'webviewApp.tsx'),
|
||||||
|
output: {
|
||||||
|
entryFileNames: 'index.js',
|
||||||
|
assetFileNames: 'index.[ext]',
|
||||||
|
// Single chunk for simplicity in WebView loading
|
||||||
|
manualChunks: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Inline small assets to avoid CSP issues
|
||||||
|
assetsInlineLimit: 100000,
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue