From 85e2754b6eb3b9f27d20ab9c3f2384147520af6e Mon Sep 17 00:00:00 2001 From: Petr Kubica Date: Sun, 15 Oct 2023 02:49:38 +0200 Subject: [PATCH] add experimental new-install command --- package-lock.json | 241 ++++++++++++++++++++ package.json | 7 +- src/commands/index.ts | 3 + src/commands/new-install.ts | 35 +++ src/commands/tsconfig.json | 4 + src/distribution/esp32/esp32.ts | 145 ++++++++++++ src/distribution/esp32/nodeTransport.ts | 291 ++++++++++++++++++++++++ src/distribution/package.ts | 149 ++++++++++++ src/distribution/tsconfig.json | 13 ++ 9 files changed, 887 insertions(+), 1 deletion(-) create mode 100644 src/commands/new-install.ts create mode 100644 src/distribution/esp32/esp32.ts create mode 100644 src/distribution/esp32/nodeTransport.ts create mode 100644 src/distribution/package.ts create mode 100644 src/distribution/tsconfig.json diff --git a/package-lock.json b/package-lock.json index c791c0a..f3715a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,11 @@ "chalk": "^5.3.0", "cli-progress": "^3.12.0", "crc": "^4.3.2", + "esptool-js": "https://github.com/cubicap/esptool-js/archive/d096cbfa6a8a615cb71ad24941b2b83186c53495.tar.gz", + "get-uri": "^6.0.1", "node-stream-zip": "^1.15.0", "serialport": "^12.0.0", + "tar-stream": "^3.1.6", "typescript": "^5.2.2", "winston": "^3.10.0" }, @@ -26,6 +29,7 @@ "@types/cli-progress": "^3.11.3", "@types/mocha": "^10.0.2", "@types/node": "^18.18.1", + "@types/tar-stream": "^2.2.2", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "chai": "^4.3.10", @@ -476,6 +480,15 @@ "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", "dev": true }, + "node_modules/@types/tar-stream": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/tar-stream/-/tar-stream-2.2.3.tgz", + "integrity": "sha512-if3mugZfjVkXOMZdFjIHySxY13r6GXPpyOlsDmLffvyI7tLz9wXE8BFjNivXsvUeyJ1KNlOpfLnag+ISmxgxPw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.3.tgz", @@ -814,12 +827,25 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" }, + "node_modules/b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/basic-ftp": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", + "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1074,6 +1100,14 @@ "node": ">= 8" } }, + "node_modules/data-uri-to-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-5.0.1.tgz", + "integrity": "sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg==", + "engines": { + "node": ">= 14" + } + }, "node_modules/dbly-linked-list": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/dbly-linked-list/-/dbly-linked-list-0.3.4.tgz", @@ -1329,6 +1363,16 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esptool-js": { + "version": "0.3.0", + "resolved": "https://github.com/cubicap/esptool-js/archive/d096cbfa6a8a615cb71ad24941b2b83186c53495.tar.gz", + "integrity": "sha512-po3AmEa4zsHOvpbssLlSOFUr1jtHUAda5zqDBQVH0evm4psMytfkHR+thFIg6OGoQpMkKvXbXu5PZPjPrNHB/w==", + "license": "Apache-2.0", + "dependencies": { + "pako": "^2.1.0", + "tslib": "^2.4.1" + } + }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -1386,6 +1430,11 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -1514,6 +1563,19 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1552,6 +1614,20 @@ "node": "*" } }, + "node_modules/get-uri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.1.tgz", + "integrity": "sha512-7ZqONUVqaabogsYNWlYj0t3YZaL6dhuEueZXGF+/YVmf6dHmaFg8/6psJKqhx9QykIDKzpGcy2cn4oV4YC7V/Q==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^5.0.1", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -1619,6 +1695,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -1824,6 +1905,14 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -2186,6 +2275,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2302,6 +2396,11 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -2526,6 +2625,15 @@ "node": "*" } }, + "node_modules/streamx": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", + "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -2585,6 +2693,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/tar-stream": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -2680,6 +2798,11 @@ "node": ">=0.3.1" } }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2725,6 +2848,14 @@ "node": ">=14.17" } }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -3208,6 +3339,15 @@ "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", "dev": true }, + "@types/tar-stream": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/tar-stream/-/tar-stream-2.2.3.tgz", + "integrity": "sha512-if3mugZfjVkXOMZdFjIHySxY13r6GXPpyOlsDmLffvyI7tLz9wXE8BFjNivXsvUeyJ1KNlOpfLnag+ISmxgxPw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/triple-beam": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.3.tgz", @@ -3420,12 +3560,22 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" }, + "b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "basic-ftp": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", + "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==" + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -3620,6 +3770,11 @@ "which": "^2.0.1" } }, + "data-uri-to-buffer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-5.0.1.tgz", + "integrity": "sha512-a9l6T1qqDogvvnw0nKlfZzqsyikEBZBClF39V3TFoKhDtGBqHu2HkuomJc02j5zft8zrUaXEuoicLeW54RkzPg==" + }, "dbly-linked-list": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/dbly-linked-list/-/dbly-linked-list-0.3.4.tgz", @@ -3803,6 +3958,14 @@ "eslint-visitor-keys": "^3.4.1" } }, + "esptool-js": { + "version": "https://github.com/cubicap/esptool-js/archive/d096cbfa6a8a615cb71ad24941b2b83186c53495.tar.gz", + "integrity": "sha512-po3AmEa4zsHOvpbssLlSOFUr1jtHUAda5zqDBQVH0evm4psMytfkHR+thFIg6OGoQpMkKvXbXu5PZPjPrNHB/w==", + "requires": { + "pako": "^2.1.0", + "tslib": "^2.4.1" + } + }, "esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -3849,6 +4012,11 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, "fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -3955,6 +4123,16 @@ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3980,6 +4158,17 @@ "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true }, + "get-uri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.1.tgz", + "integrity": "sha512-7ZqONUVqaabogsYNWlYj0t3YZaL6dhuEueZXGF+/YVmf6dHmaFg8/6psJKqhx9QykIDKzpGcy2cn4oV4YC7V/Q==", + "requires": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^5.0.1", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + } + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -4026,6 +4215,11 @@ "slash": "^3.0.0" } }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -4177,6 +4371,14 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "requires": { + "graceful-fs": "^4.1.6" + } + }, "keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -4457,6 +4659,11 @@ "p-limit": "^3.0.2" } }, + "pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4529,6 +4736,11 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -4676,6 +4888,15 @@ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" }, + "streamx": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", + "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", + "requires": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4717,6 +4938,16 @@ "has-flag": "^4.0.0" } }, + "tar-stream": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -4778,6 +5009,11 @@ } } }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4804,6 +5040,11 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==" }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index f88bfdc..81a9072 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "clean": "tsc --build --clean", "lint": "eslint --ext .ts src unit", "test": "mocha", - "prepack": "npm run lint && npm run clean && npm run build && npm run test" + "prepack": "npm run lint && npm run clean && npm run build && npm run test", + "command": "node ./dist/src/commands/index.js" }, "bin": { "jac": "./dist/src/commands/index.js" @@ -33,8 +34,11 @@ "chalk": "^5.3.0", "cli-progress": "^3.12.0", "crc": "^4.3.2", + "esptool-js": "https://github.com/cubicap/esptool-js/archive/d096cbfa6a8a615cb71ad24941b2b83186c53495.tar.gz", + "get-uri": "^6.0.1", "node-stream-zip": "^1.15.0", "serialport": "^12.0.0", + "tar-stream": "^3.1.6", "typescript": "^5.2.2", "winston": "^3.10.0" }, @@ -44,6 +48,7 @@ "@types/cli-progress": "^3.11.3", "@types/mocha": "^10.0.2", "@types/node": "^18.18.1", + "@types/tar-stream": "^2.2.2", "@typescript-eslint/eslint-plugin": "^6.7.3", "@typescript-eslint/parser": "^6.7.3", "chai": "^4.3.10", diff --git a/src/commands/index.ts b/src/commands/index.ts index 09c8952..094e2e7 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -64,6 +64,7 @@ import version from "./version.js"; import monitor from "./monitor.js"; import pull from "./pull.js"; import fomat from "./format.js"; +import newInstall from "./new-install.js"; jac.addCommand("list-ports", listPorts); jac.addCommand("serial-socket", serialSocket); @@ -86,6 +87,8 @@ jac.addCommand("stop", stop); jac.addCommand("status", status); jac.addCommand("version", version); +jac.addCommand("new-install", newInstall); + jac.addCommand("monitor", monitor); diff --git a/src/commands/new-install.ts b/src/commands/new-install.ts new file mode 100644 index 0000000..7e2d417 --- /dev/null +++ b/src/commands/new-install.ts @@ -0,0 +1,35 @@ +import { Command, Opt } from "./lib/command.js"; + +import { loadPackage } from "../distribution/package.js"; +import { stderr, stdout } from "process"; + + +const cmd = new Command("Test new install command", { + action: async (options: Record) => { + const pkgPath = options["package"] as string; + const port = options["port"] as string; + + if (!port) { + stderr.write("Port not specified\n"); + throw 1; + } + + stdout.write("Loading package...\n"); + + const pkg = await loadPackage(pkgPath); + + stdout.write("Version: " + pkg.getManifest().getVersion() + "\n"); + stdout.write("Board: " + pkg.getManifest().getBoard() + "\n"); + stdout.write("Platform: " + pkg.getManifest().getPlatform() + "\n"); + stdout.write("\n"); + + await pkg.flash(port); + }, + args: [], + options: { + "package": new Opt("Uri to the package file", { required: true }) + }, + chainable: false +}); + +export default cmd; diff --git a/src/commands/tsconfig.json b/src/commands/tsconfig.json index d62a99b..785e342 100644 --- a/src/commands/tsconfig.json +++ b/src/commands/tsconfig.json @@ -22,6 +22,7 @@ "pull.ts", "format.ts", "index.ts", + "new-install.ts", "lib/command.ts", ], "references": [ @@ -36,6 +37,9 @@ }, { "path": "../project" + }, + { + "path": "../distribution" } ] } diff --git a/src/distribution/esp32/esp32.ts b/src/distribution/esp32/esp32.ts new file mode 100644 index 0000000..6fac466 --- /dev/null +++ b/src/distribution/esp32/esp32.ts @@ -0,0 +1,145 @@ +import { Package } from "../package.js"; +import { ESPLoader } from "esptool-js"; +import { SerialPort } from "serialport"; +import { NodeTransport } from "./nodeTransport.js"; +import { logger } from "../../util/logger.js"; +import cliProgress from "cli-progress"; +import { stdout } from "process"; + + +class UploadReporter { + private bar: cliProgress.SingleBar; + private fileIndex: number; + + constructor(private files: { size: number, name: string }[]) { + this.fileIndex = 0; + this.bar = this.createBar(this.fileIndex); + } + + private createBar(fileIndex: number) { + const fileName = this.files[fileIndex].name; + + return new cliProgress.SingleBar({ + format: `${fileIndex + 1}/${this.files.length} | {bar} {percentage}% | ${fileName} | {value} / {total}`, + hideCursor: true + }, cliProgress.Presets.rect); + } + + public update(fileIndex: number, written: number) { + if (fileIndex !== this.fileIndex) { + this.bar.update(this.bar.getTotal()); + this.bar.stop(); + this.fileIndex = fileIndex; + this.bar = this.createBar(this.fileIndex); + this.bar.start(this.files[this.fileIndex].size, 0); + } + + this.bar.update(written); + } + + public start () { + this.bar.start(this.files[this.fileIndex].size, 0); + } + + public stop() { + this.bar.update(this.bar.getTotal()); + this.bar.stop(); + } +} + + +export async function flash(Package: Package, path: string): Promise { + const config = Package.getManifest().getConfig(); + + const flashBaud = parseInt(config["flashBaud"] ?? 921600); + + const partitions = config["partitions"]; + if (!partitions) { + throw new Error("No partitions defined"); + } + + const list = await SerialPort.list(); + const info = list.find((port) => port.path === path); + if (!info) { + throw new Error("Port not found"); + } + + stdout.write("Connecting to " + info.path + "...\n"); + + const port = new SerialPort({ + path: info.path, + baudRate: 115200, + autoOpen: false + }); + + const loaderOptions: any = { + debugLogging: false, + transport: new NodeTransport(port), + baudrate: flashBaud, + romBaudrate: 115200, + terminal: { + clean: () => { }, + writeLine: (data: any) => { logger.debug(data); }, + write: (data: any) => { logger.debug(data); } + } + }; + const esploader = new ESPLoader(loaderOptions); + + const fileArray: { data: string, address: number, fileName: string }[] = []; + for (const partition of partitions) { + const file = partition["file"]; + if (file === undefined) { + throw new Error("No file defined for partition"); + } + const address = parseInt(partition["address"]); + if (address === undefined) { + throw new Error("No address defined for partition"); + } + const dataBuffer = Package.getData()[file]; + if (dataBuffer === undefined) { + throw new Error("File not found in package"); + } + + fileArray.push({ data: esploader.ui8ToBstr(dataBuffer), address: address, fileName: file }); + } + + const reporter = new UploadReporter(fileArray.map((file) => { + return { + size: file.data.length, + name: file.fileName + }; + })); + + try { + await esploader.main_fn(); + + stdout.write("Detected chip type: " + esploader.chip.CHIP_NAME + "\n"); + stdout.write("Flash size: " + await esploader.get_flash_size() + "K\n"); + + stdout.write("\n"); + + if (esploader.chip.CHIP_NAME !== config["chip"]) { + throw new Error("Chip type mismatch (expected " + config["chip"] + ", got " + esploader.chip.CHIP_NAME + ")"); + } + + stdout.write("Writing flash...\n"); + + reporter.start(); + await esploader.write_flash({ + fileArray: fileArray, + flashSize: "4MB", + flashMode: "keep", + flashFreq: "keep", + eraseAll: false, + compress: true, + reportProgress: (fileIndex: number, written: number) => { + reporter.update(fileIndex, written); + } + }); + } + finally { + reporter.stop(); + await esploader.hard_reset(); + await esploader.transport.disconnect(); + } +} diff --git a/src/distribution/esp32/nodeTransport.ts b/src/distribution/esp32/nodeTransport.ts new file mode 100644 index 0000000..c8f4af1 --- /dev/null +++ b/src/distribution/esp32/nodeTransport.ts @@ -0,0 +1,291 @@ +/** + * The original code in this file is + * Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd. + * It is licensed under Apache 2.0 license + * + * It has been modified to work with the node-serialport library + */ + +import { SerialPort } from "serialport"; + + +export class NodeTransport { + public slip_reader_enabled = false; + public left_over = new Uint8Array(0); + public baudrate = 0; + private usbVendorId: number | undefined; + private usbProductId: number | undefined; + + constructor(public device: SerialPort, usbVendorId?: number, usbProductId?: number) { + this.usbVendorId = usbVendorId; + this.usbProductId = usbProductId; + } + + get_info() { + return this.usbVendorId && this.usbProductId + ? `Node SerialPort VendorID 0x${this.usbVendorId.toString(16)} ProductID 0x${this.usbProductId.toString(16)}` + : ""; + } + + get_pid() { + return this.usbProductId; + } + + slip_writer(data: Uint8Array) { + let count_esc = 0; + let i = 0, + j = 0; + + for (i = 0; i < data.length; i++) { + if (data[i] === 0xc0 || data[i] === 0xdb) { + count_esc++; + } + } + const out_data = new Uint8Array(2 + count_esc + data.length); + out_data[0] = 0xc0; + j = 1; + for (i = 0; i < data.length; i++, j++) { + if (data[i] === 0xc0) { + out_data[j++] = 0xdb; + out_data[j] = 0xdc; + continue; + } + if (data[i] === 0xdb) { + out_data[j++] = 0xdb; + out_data[j] = 0xdd; + continue; + } + + out_data[j] = data[i]; + } + out_data[j] = 0xc0; + return out_data; + } + + async write(data: Uint8Array) { + const out_data = this.slip_writer(data); + + if (this.device.writable) { + this.device.write(out_data); + await new Promise((resolve, reject) => { + this.device.drain((err) => { + if (err) { + reject(err); + } else { + resolve(null); + } + }); + }); + } + } + + _appendBuffer(buffer1: Uint8Array, buffer2: Uint8Array) { + const tmp = new Uint8Array(buffer1.length + buffer2.length); + tmp.set(new Uint8Array(buffer1), 0); + tmp.set(new Uint8Array(buffer2), buffer1.length); + return tmp.buffer; + } + + /* this function expects complete packet (hence reader reads for atleast 8 bytes. This function is + * stateless and returns the first wellformed packet only after replacing escape sequence */ + slip_reader(data: Uint8Array) { + let i = 0; + let data_start = 0, + data_end = 0; + let state = "init"; + while (i < data.length) { + if (state === "init" && data[i] == 0xc0) { + data_start = i + 1; + state = "valid_data"; + i++; + continue; + } + if (state === "valid_data" && data[i] == 0xc0) { + data_end = i - 1; + state = "packet_complete"; + break; + } + i++; + } + if (state !== "packet_complete") { + this.left_over = data; + return new Uint8Array(0); + } + + this.left_over = data.slice(data_end + 2); + const temp_pkt = new Uint8Array(data_end - data_start + 1); + let j = 0; + for (i = data_start; i <= data_end; i++, j++) { + if (data[i] === 0xdb && data[i + 1] === 0xdc) { + temp_pkt[j] = 0xc0; + i++; + continue; + } + if (data[i] === 0xdb && data[i + 1] === 0xdd) { + temp_pkt[j] = 0xdb; + i++; + continue; + } + temp_pkt[j] = data[i]; + } + const packet = temp_pkt.slice(0, j); /* Remove unused bytes due to escape seq */ + return packet; + } + + async read(timeout = 0, min_data = 12) { + let packet = this.left_over; + this.left_over = new Uint8Array(0); + if (this.slip_reader_enabled) { + const val_final = this.slip_reader(packet); + if (val_final.length > 0) { + return val_final; + } + packet = this.left_over; + this.left_over = new Uint8Array(0); + } + if (this.device.readable == null) { + return this.left_over; + } + + let t; + try { + let done = false; + if (timeout > 0) { + t = setTimeout(function () { + done = true; + }, timeout); + } + do { + const value = this.device.read(); + if (done) { + this.left_over = packet; + await new Promise((resolve, reject) => { + this.device.flush((err) => { + if (err) { + reject(err); + } else { + resolve(null); + } + }); + }); + throw new Error("Timeout"); + } + if (value == null) { + await this.sleep(1); + continue; + } + const p = new Uint8Array(this._appendBuffer(packet, value)); + packet = p; + } while (packet.length < min_data); + } + finally { + if (timeout > 0) { + clearTimeout(t); + } + } + if (this.slip_reader_enabled) { + return this.slip_reader(packet); + } + return packet; + } + + async rawRead(timeout = 0) { + if (this.left_over.length != 0) { + const p = this.left_over; + this.left_over = new Uint8Array(0); + return p; + } + if (!this.device.readable) { + return this.left_over; + } + let t; + try { + let done = false; + if (timeout > 0) { + t = setTimeout(function () { + done = true; + }, timeout); + } + while (!done) { + if (this.device.readableLength > 0) { + await this.sleep(10); + } + const value = this.device.read(); + if (value == null) { + await this.sleep(1); + continue; + } + return value; + } + throw new Error("Timeout"); + } finally { + if (timeout > 0) { + clearTimeout(t); + } + } + } + + _DTR_state = false; + _RTS_state = false; + async setRTS(state: boolean) { + this._RTS_state = state; + } + + async setDTR(state: boolean) { + this._DTR_state = state; + } + + async writeRTSDTR() { + await new Promise((resolve, reject) => { + this.device.set({ + rts: this._RTS_state, + dtr: this._DTR_state + }, (err) => { + if (err) { + reject(err); + } else { + resolve(null); + } + }); + }); + } + + async connect(baud = 115200) { + await new Promise((resolve, reject) => { + this.device.open((err) => { + if (err) { + reject(err); + } else { + resolve(null); + } + }); + }); + await new Promise((resolve, reject) => { + this.device.update({ baudRate: baud }, (err) => { + if (err) { + reject(err); + } else { + resolve(null); + } + }); + }); + this.baudrate = baud; + this.left_over = new Uint8Array(0); + } + + async sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + async disconnect() { + await new Promise((resolve, reject) => { + this.device.close((err) => { + if (err) { + reject(err); + } else { + resolve(null); + } + }); + }); + } +} diff --git a/src/distribution/package.ts b/src/distribution/package.ts new file mode 100644 index 0000000..898006e --- /dev/null +++ b/src/distribution/package.ts @@ -0,0 +1,149 @@ +import { getUri } from "get-uri"; +import * as tar from "tar-stream"; +import * as espPlatform from "./esp32/esp32.js"; +import * as zlib from "zlib"; + + +export class Manifest { + private board: string; + private version: string; + private platform: string; + private config: Record; + + constructor(board: string, version: string, platform: string, config: Record) { + this.board = board; + this.version = version; + this.platform = platform; + this.config = config; + } + + public getBoard(): string { + return this.board; + } + + public getVersion(): string { + return this.version; + } + + public getPlatform(): string { + return this.platform; + } + + public getConfig(): Record { + return this.config; + } +} + +/** + * Parse the manifest file + * @param data Manifest file data + * @returns The manifest + */ +function parseManifest(data: string) { + const manifest = JSON.parse(data); + + const board = manifest["board"]; + if (!board) { + throw new Error("No board defined in manifest"); + } + + const version = manifest["version"]; + if (!version) { + throw new Error("No version defined in manifest"); + } + + const platform = manifest["platform"]; + if (!platform) { + throw new Error("No platform defined in manifest"); + } + + const config = manifest["config"]; + if (!config) { + throw new Error("No config defined in manifest"); + } + + return new Manifest(board, version, platform, config); +} + +export class Package { + private manifest: Manifest; + private data: Record; + + constructor(manifest: Manifest, data: Record) { + this.manifest = manifest; + this.data = data; + } + + public getManifest(): Manifest { + return this.manifest; + } + + public getData(): Record { + return this.data; + } + + public async flash(port: string): Promise { + switch (this.manifest.getPlatform()) { + case "esp32": + await espPlatform.flash(this, port); + break; + default: + throw new Error("Unsupported platform"); + } + + return; + } +} + +/** + * Load the package file from the given URI + * @param uri Uri to the package file (.tar.gz) + * @returns The package file and manifest + */ +export async function loadPackage(uri: string): Promise { + const stream = await getUri(uri); + const extract = tar.extract(); + + + return new Promise((resolve, reject) => { + let manifest: Manifest = new Manifest("", "", "", {}); + const files: Record = {}; + + extract.on("entry", (header, stream, next) => { + if (header.name === "manifest.json") { + let str = ""; + stream.on("data", (chunk) => { + str += chunk; + }); + stream.on("end", () => { + manifest = parseManifest(str); + next(); + }); + stream.on("error", (err) => { + reject(err); + }); + } + else { + const chunks: Buffer[] = []; + stream.on("data", (chunk) => { + chunks.push(chunk); + }); + stream.on("end", () => { + files[header.name] = Buffer.concat(chunks); + next(); + }); + stream.on("error", (err) => { + reject(err); + }); + } + }); + extract.on("finish", () => { + resolve(new Package(manifest, files)); + }); + extract.on("error", (err) => { + reject(err); + }); + + stream.pipe(zlib.createGunzip()).pipe(extract); + }); +} diff --git a/src/distribution/tsconfig.json b/src/distribution/tsconfig.json new file mode 100644 index 0000000..330a80f --- /dev/null +++ b/src/distribution/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig-base.json", + "files": [ + "package.ts", + "esp32/esp32.ts", + "esp32/nodeTransport.ts" + ], + "references": [ + { + "path": "../util" + } + ] +}