diff --git a/package-lock.json b/package-lock.json index 8229b6b1..a3d058d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -159,6 +159,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -4492,6 +4493,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -4503,6 +4505,7 @@ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^18.0.0" } @@ -4616,6 +4619,7 @@ "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.21.0", "@typescript-eslint/types": "6.21.0", @@ -4986,7 +4990,8 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/7zip-bin": { "version": "5.2.0", @@ -5010,6 +5015,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5073,6 +5079,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -5253,7 +5260,6 @@ "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", @@ -5273,7 +5279,6 @@ "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", @@ -5295,8 +5300,7 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/archiver-utils/node_modules/readable-stream": { "version": "2.3.8", @@ -5304,7 +5308,6 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -5320,8 +5323,7 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/archiver-utils/node_modules/string_decoder": { "version": "1.1.1", @@ -5329,7 +5331,6 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -5797,6 +5798,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -6275,6 +6277,7 @@ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", @@ -6572,7 +6575,6 @@ "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", @@ -6746,7 +6748,6 @@ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "crc32": "bin/crc32.njs" }, @@ -6760,7 +6761,6 @@ "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -6806,6 +6806,7 @@ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } @@ -7215,6 +7216,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -7677,6 +7679,7 @@ "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "24.13.3", "builder-util": "24.13.1", @@ -8022,7 +8025,6 @@ "integrity": "sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "app-builder-lib": "24.13.3", "archiver": "^5.3.1", @@ -8036,7 +8038,6 @@ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -8052,7 +8053,6 @@ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -8066,7 +8066,6 @@ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">= 10.0.0" } @@ -8460,6 +8459,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -8535,6 +8535,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -11121,6 +11122,7 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", + "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -11305,7 +11307,6 @@ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "readable-stream": "^2.0.5" }, @@ -11318,8 +11319,7 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lazystream/node_modules/readable-stream": { "version": "2.3.8", @@ -11327,7 +11327,6 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -11343,8 +11342,7 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lazystream/node_modules/string_decoder": { "version": "1.1.1", @@ -11352,7 +11350,6 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -11440,16 +11437,14 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.escaperegexp": { "version": "4.1.2", @@ -11462,8 +11457,7 @@ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.isequal": { "version": "4.5.0", @@ -11477,8 +11471,7 @@ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -11492,8 +11485,7 @@ "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", @@ -12022,6 +12014,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", @@ -12658,7 +12651,8 @@ "url": "https://opencollective.com/unified" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/micromatch": { "version": "4.0.8", @@ -12957,6 +12951,7 @@ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", "license": "MIT", + "peer": true, "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" @@ -13840,6 +13835,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -14054,6 +14050,7 @@ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -14175,8 +14172,7 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/progress": { "version": "2.0.3", @@ -14317,6 +14313,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -14329,6 +14326,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -14559,7 +14557,6 @@ "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "minimatch": "^5.1.0" } @@ -14570,7 +14567,6 @@ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -15783,6 +15779,7 @@ "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", "hasInstallScript": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", @@ -16515,6 +16512,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -16808,6 +16806,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17097,6 +17096,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17141,6 +17141,7 @@ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", "license": "MIT", + "peer": true, "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", @@ -17461,6 +17462,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -18415,7 +18417,6 @@ "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", @@ -18431,7 +18432,6 @@ "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", @@ -18454,6 +18454,7 @@ "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 6411a63b..e570c562 100644 --- a/package.json +++ b/package.json @@ -6,18 +6,18 @@ "scripts": { "d": "npm install && npm run dev", "dev": "concurrently \"npm run dev:main\" \"npm run dev:renderer\"", - "dev:main": "tsc -p tsconfig.main.json && mkdir -p dist/main && cp src/main/appConfig.json dist/main/appConfig.json && electron dist/main/main/entry.js --dev", + "dev:main": "tsc -p tsconfig.main.json && node scripts/copy-app-config.cjs && electron dist/main/main/entry.js --dev", "dev:renderer": "vite", "build": "npm run build:main && npm run build:renderer", - "build:main": "tsc -p tsconfig.main.json && mkdir -p dist/main && cp src/main/appConfig.json dist/main/appConfig.json", + "build:main": "tsc -p tsconfig.main.json && node scripts/copy-app-config.cjs", "build:renderer": "vite build", "package": "npm run build && electron-builder", "package:mac": "npm run build && electron-builder --mac", "package:linux": "npm run build && electron-builder --linux --publish never", "package:win": "npm run build && electron-builder --win --publish never", - "postinstall": "electron-rebuild", + "postinstall": "node scripts/postinstall.cjs", "rebuild": "electron-rebuild -f -v 30.5.1", - "clean": "rm -rf node_modules dist", + "clean": "node scripts/clean.cjs", "reset": "npm run clean && npm install", "lint": "eslint . --ext .ts,.tsx", "format": "prettier --write .", diff --git a/scripts/clean.cjs b/scripts/clean.cjs new file mode 100644 index 00000000..8baa612c --- /dev/null +++ b/scripts/clean.cjs @@ -0,0 +1,8 @@ +const fs = require('fs'); +const path = require('path'); + +const repoRoot = path.resolve(__dirname, '..'); + +for (const dir of ['node_modules', 'dist']) { + fs.rmSync(path.join(repoRoot, dir), { recursive: true, force: true }); +} diff --git a/scripts/copy-app-config.cjs b/scripts/copy-app-config.cjs new file mode 100644 index 00000000..a9acb228 --- /dev/null +++ b/scripts/copy-app-config.cjs @@ -0,0 +1,10 @@ +const fs = require('fs'); +const path = require('path'); + +const repoRoot = path.resolve(__dirname, '..'); +const src = path.join(repoRoot, 'src', 'main', 'appConfig.json'); +const destDir = path.join(repoRoot, 'dist', 'main'); +const dest = path.join(destDir, 'appConfig.json'); + +fs.mkdirSync(destDir, { recursive: true }); +fs.copyFileSync(src, dest); diff --git a/scripts/postinstall.cjs b/scripts/postinstall.cjs new file mode 100644 index 00000000..6ea279fd --- /dev/null +++ b/scripts/postinstall.cjs @@ -0,0 +1,45 @@ +const { spawnSync } = require('child_process'); +const path = require('path'); + +function getElectronRebuildBin() { + const binName = process.platform === 'win32' ? 'electron-rebuild.cmd' : 'electron-rebuild'; + return path.resolve(__dirname, '..', 'node_modules', '.bin', binName); +} + +function runElectronRebuild(onlyModules) { + const electronRebuildBin = getElectronRebuildBin(); + const args = ['-f']; + + if (onlyModules && onlyModules.length > 0) { + args.push('--only', onlyModules.join(',')); + } + + const result = + process.platform === 'win32' + ? spawnSync(electronRebuildBin, args, { stdio: 'inherit', shell: true }) + : spawnSync(electronRebuildBin, args, { stdio: 'inherit' }); + + if (result.error) { + // eslint-disable-next-line no-console + console.error('postinstall: failed to run electron-rebuild:', result.error); + } + // spawnSync.status is the numeric exit code, null when terminated by signal. + if (result.status === 0) return; + process.exit(typeof result.status === 'number' ? result.status : 1); +} + +const disablePty = process.env.EMDASH_DISABLE_PTY === '1'; +const disableNativeDb = process.env.EMDASH_DISABLE_NATIVE_DB === '1'; + +// Keep this list explicit: these are the native modules we ship/unpack. +const nativeModules = []; +if (!disableNativeDb) nativeModules.push('sqlite3'); +if (!disablePty) nativeModules.push('node-pty'); +nativeModules.push('keytar'); + +if (nativeModules.length === 0) { + // Nothing to rebuild; skip quietly. + process.exit(0); +} + +runElectronRebuild(nativeModules); diff --git a/src/main/services/ConnectionsService.ts b/src/main/services/ConnectionsService.ts index 30072734..eaea41d8 100644 --- a/src/main/services/ConnectionsService.ts +++ b/src/main/services/ConnectionsService.ts @@ -1,5 +1,6 @@ import { spawn, execFileSync } from 'child_process'; import { BrowserWindow } from 'electron'; +import { extname } from 'node:path'; import { providerStatusCache, type ProviderStatus } from './providerStatusCache'; import { listDetectableProviders, type ProviderDefinition } from '@shared/providers/registry'; import { log } from '../lib/logger'; @@ -232,7 +233,12 @@ class ConnectionsService { const resolvedPath = this.resolveCommandPath(command); return new Promise((resolve) => { try { - const child = spawn(command, args); + const execPath = resolvedPath ?? command; + const execExt = process.platform === 'win32' ? extname(execPath).toLowerCase() : ''; + const needsShell = + process.platform === 'win32' && + (execExt === '.cmd' || execExt === '.bat' || execExt === '.ps1'); + const child = spawn(execPath, args, { shell: needsShell, windowsHide: true }); let stdout = ''; let stderr = ''; @@ -318,7 +324,32 @@ class ConnectionsService { .split(/\r?\n/) .map((l) => l.trim()) .filter(Boolean); - return lines[0] ?? null; + + if (process.platform !== 'win32') { + return lines[0] ?? null; + } + + // On Windows, `where ` can return an extensionless shim first (e.g. `codex`) + // which is not directly executable via CreateProcess, causing ENOENT at spawn time. + // Prefer actual executable extensions when present. + const extensionPreference: Record = { + '.exe': 0, + '.cmd': 1, + '.bat': 2, + '.com': 3, + '.ps1': 50, + '': 100, + }; + + const best = [...lines].sort((a, b) => { + const aExt = extname(a).toLowerCase(); + const bExt = extname(b).toLowerCase(); + const aRank = extensionPreference[aExt] ?? extensionPreference['']; + const bRank = extensionPreference[bExt] ?? extensionPreference['']; + return aRank - bRank; + })[0]; + + return best ?? null; } catch { return null; } diff --git a/src/main/services/GitHubService.ts b/src/main/services/GitHubService.ts index 41fa9628..4ceacbb6 100644 --- a/src/main/services/GitHubService.ts +++ b/src/main/services/GitHubService.ts @@ -623,6 +623,24 @@ export class GitHubService { // First check if gh CLI is authenticated system-wide const isGHAuth = await this.isGHCLIAuthenticated(); if (isGHAuth) { + // Best-effort: if user is logged in via gh but we don't have a stored token yet, + // retrieve it and store it for subsequent gh command retries and API calls. + try { + const existingToken = await this.getStoredToken(); + if (!existingToken) { + const { stdout } = await this.execGH('gh auth token'); + const token = String(stdout || '').trim(); + if (token) { + try { + await this.storeToken(token); + } catch { + // Non-fatal: user is still authenticated via gh CLI + } + } + } + } catch { + // Non-fatal: user is still authenticated via gh CLI + } return true; } diff --git a/src/main/services/PrGenerationService.ts b/src/main/services/PrGenerationService.ts index b9bd816d..ec9bb7a1 100644 --- a/src/main/services/PrGenerationService.ts +++ b/src/main/services/PrGenerationService.ts @@ -1,10 +1,67 @@ -import { exec, execFile, spawn } from 'child_process'; +import { exec, execFile, execFileSync, spawn } from 'child_process'; +import { extname } from 'node:path'; import { promisify } from 'util'; import { log } from '../lib/logger'; import { getProvider, PROVIDER_IDS, type ProviderId } from '../../shared/providers/registry'; const execAsync = promisify(exec); -const execFileAsync = promisify(execFile); + +const cliPathCache = new Map(); + +function resolveCliPath(command: string): string | null { + if (!command) return null; + + // If it's already a path (absolute or relative), just use it. + if (command.includes('/') || command.includes('\\')) return command; + + if (cliPathCache.has(command)) return cliPathCache.get(command) ?? null; + + const resolver = process.platform === 'win32' ? 'where' : 'which'; + try { + const result = execFileSync(resolver, [command], { encoding: 'utf8' }); + const lines = result + .split(/\r?\n/) + .map((l) => l.trim()) + .filter(Boolean); + + if (process.platform !== 'win32') { + const resolved = lines[0] ?? null; + cliPathCache.set(command, resolved); + return resolved; + } + + // Prefer actual executable extensions on Windows (avoid extensionless shims like `%APPDATA%\\npm\\codex`). + const extensionPreference: Record = { + '.exe': 0, + '.cmd': 1, + '.bat': 2, + '.com': 3, + '.ps1': 50, + '': 100, + }; + + const best = [...lines].sort((a, b) => { + const aExt = extname(a).toLowerCase(); + const bExt = extname(b).toLowerCase(); + const aRank = extensionPreference[aExt] ?? extensionPreference['']; + const bRank = extensionPreference[bExt] ?? extensionPreference['']; + return aRank - bRank; + })[0]; + + const resolved = best ?? null; + cliPathCache.set(command, resolved); + return resolved; + } catch { + cliPathCache.set(command, null); + return null; + } +} + +function shouldUseShellForWindows(execPath: string): boolean { + if (process.platform !== 'win32') return false; + const ext = extname(execPath).toLowerCase(); + return ext === '.cmd' || ext === '.bat' || ext === '.ps1'; +} export interface GeneratedPrContent { title: string; @@ -254,10 +311,23 @@ export class PrGenerationService { return null; } + const resolvedCliPath = resolveCliPath(cliCommand) ?? cliCommand; + const needsShell = shouldUseShellForWindows(resolvedCliPath); + // Check if provider CLI is available try { - await execFileAsync(cliCommand, provider.versionArgs || ['--version'], { - cwd: taskPath, + await new Promise((resolve, reject) => { + const child = spawn(resolvedCliPath, provider.versionArgs || ['--version'], { + cwd: taskPath, + stdio: ['ignore', 'pipe', 'pipe'], + shell: needsShell, + windowsHide: true, + }); + child.on('error', reject); + child.on('exit', (code) => { + if (code === 0) resolve(); + else reject(new Error(`Exit code ${code ?? 'null'}`)); + }); }); } catch { log.debug(`Provider ${providerId} CLI not available`); @@ -293,9 +363,11 @@ export class PrGenerationService { } // Spawn the provider CLI - const child = spawn(cliCommand, args, { + const child = spawn(resolvedCliPath, args, { cwd: taskPath, stdio: ['pipe', 'pipe', 'pipe'], + shell: needsShell, + windowsHide: true, env: { ...process.env, // Ensure we have a proper terminal environment diff --git a/src/main/services/providerCli.test.ts b/src/main/services/providerCli.test.ts new file mode 100644 index 00000000..e42d72e1 --- /dev/null +++ b/src/main/services/providerCli.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import { buildProviderCliArgs, detectProviderFromShellCommand } from './providerCli'; +import { getProvider } from '@shared/providers/registry'; + +describe('providerCli', () => { + it('detects provider from Windows shim path', () => { + const provider = detectProviderFromShellCommand( + 'C:\\\\Users\\\\User\\\\AppData\\\\Roaming\\\\npm\\\\codex.cmd' + ); + expect(provider?.id).toBe('codex'); + }); + + it('detects provider from bare command', () => { + const provider = detectProviderFromShellCommand('codex'); + expect(provider?.id).toBe('codex'); + }); + + it('builds args with auto-approve', () => { + const provider = getProvider('codex'); + expect(provider).toBeTruthy(); + expect(provider?.autoApproveFlag).toBeTruthy(); + const args = buildProviderCliArgs(provider!, { autoApprove: true }); + expect(args).toContain(provider!.autoApproveFlag!); + }); + + it('includes resume flag unless skipped', () => { + const provider = getProvider('claude'); + expect(provider).toBeTruthy(); + + const args = buildProviderCliArgs(provider!, { skipResume: false }); + expect(args.slice(0, 2)).toEqual(['-c', '-r']); + + const skipped = buildProviderCliArgs(provider!, { skipResume: true }); + expect(skipped).not.toContain('-c'); + expect(skipped).not.toContain('-r'); + }); +}); diff --git a/src/main/services/providerCli.ts b/src/main/services/providerCli.ts new file mode 100644 index 00000000..87a6ff64 --- /dev/null +++ b/src/main/services/providerCli.ts @@ -0,0 +1,47 @@ +import path from 'path'; +import { PROVIDERS, type ProviderDefinition } from '@shared/providers/registry'; + +export function detectProviderFromShellCommand( + shellCommand: string | undefined +): ProviderDefinition | undefined { + if (!shellCommand) return undefined; + + const base = path.basename(shellCommand).toLowerCase(); + const baseNoExt = base.replace(/\.(exe|cmd|bat|com)$/i, ''); + if (!baseNoExt) return undefined; + + return PROVIDERS.find((p) => (p.cli || '').toLowerCase() === baseNoExt); +} + +export function buildProviderCliArgs( + provider: ProviderDefinition, + options: { + autoApprove?: boolean; + initialPrompt?: string; + skipResume?: boolean; + } +): string[] { + const { autoApprove, initialPrompt, skipResume } = options; + const cliArgs: string[] = []; + + if (provider.resumeFlag && !skipResume) { + cliArgs.push(...provider.resumeFlag.split(' ').filter(Boolean)); + } + + if (provider.defaultArgs?.length) { + cliArgs.push(...provider.defaultArgs); + } + + if (autoApprove && provider.autoApproveFlag) { + cliArgs.push(provider.autoApproveFlag); + } + + if (provider.initialPromptFlag !== undefined && initialPrompt?.trim()) { + if (provider.initialPromptFlag) { + cliArgs.push(provider.initialPromptFlag); + } + cliArgs.push(initialPrompt.trim()); + } + + return cliArgs; +} diff --git a/src/main/services/ptyManager.ts b/src/main/services/ptyManager.ts index 4c39e982..ef44d6c5 100644 --- a/src/main/services/ptyManager.ts +++ b/src/main/services/ptyManager.ts @@ -1,10 +1,13 @@ +import { execFileSync } from 'child_process'; import os from 'os'; import fs from 'fs'; import path from 'path'; +import { extname } from 'node:path'; import type { IPty } from 'node-pty'; import { log } from '../lib/logger'; import { PROVIDERS } from '@shared/providers/registry'; import { errorTracking } from '../errorTracking'; +import { buildProviderCliArgs, detectProviderFromShellCommand } from './providerCli'; type PtyRecord = { id: string; @@ -13,6 +16,57 @@ type PtyRecord = { const ptys = new Map(); +const cliPathCache = new Map(); + +function resolveCliPath(command: string): string | null { + if (!command) return null; + + // If it's already a path (absolute or relative), just use it. + if (command.includes('/') || command.includes('\\')) return command; + + if (cliPathCache.has(command)) return cliPathCache.get(command) ?? null; + + const resolver = process.platform === 'win32' ? 'where' : 'which'; + try { + const result = execFileSync(resolver, [command], { encoding: 'utf8' }); + const lines = result + .split(/\r?\n/) + .map((l) => l.trim()) + .filter(Boolean); + + if (process.platform !== 'win32') { + const resolved = lines[0] ?? null; + cliPathCache.set(command, resolved); + return resolved; + } + + // Prefer actual executable extensions on Windows (avoid extensionless shims like `%APPDATA%\\npm\\codex`). + const extensionPreference: Record = { + '.exe': 0, + '.cmd': 1, + '.bat': 2, + '.com': 3, + '.ps1': 50, + '': 100, + }; + + const best = [...lines].sort((a, b) => { + const aExt = extname(a).toLowerCase(); + const bExt = extname(b).toLowerCase(); + const aRank = extensionPreference[aExt] ?? extensionPreference['']; + const bRank = extensionPreference[bExt] ?? extensionPreference['']; + return aRank - bRank; + })[0]; + + const resolved = best ?? null; + cliPathCache.set(command, resolved); + return resolved; + } catch { + cliPathCache.set(command, null); + return null; + } +} + function getDefaultShell(): string { if (process.platform === 'win32') { // Prefer ComSpec (usually cmd.exe) or fallback to PowerShell @@ -72,6 +126,18 @@ export async function startPty(options: { HOME: process.env.HOME || os.homedir(), USER: process.env.USER || os.userInfo().username, SHELL: process.env.SHELL || defaultShell, + ...(process.platform === 'win32' && { + // Windows shells (.cmd/.bat) and many tools rely on PATH/PATHEXT to locate executables (e.g. `node`). + PATH: process.env.PATH || process.env.Path || '', + PATHEXT: process.env.PATHEXT || '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC', + SystemRoot: process.env.SystemRoot || 'C:\\Windows', + ComSpec: process.env.ComSpec || 'C:\\Windows\\System32\\cmd.exe', + TEMP: process.env.TEMP || '', + TMP: process.env.TMP || '', + USERPROFILE: process.env.USERPROFILE || os.homedir(), + APPDATA: process.env.APPDATA || '', + LOCALAPPDATA: process.env.LOCALAPPDATA || '', + }), ...(process.env.LANG && { LANG: process.env.LANG }), ...(process.env.DISPLAY && { DISPLAY: process.env.DISPLAY }), ...(process.env.SSH_AUTH_SOCK && { SSH_AUTH_SOCK: process.env.SSH_AUTH_SOCK }), @@ -136,72 +202,62 @@ export async function startPty(options: { // For provider CLIs, spawn the user's shell and run the provider command via -c, // then exec back into the shell to allow users to stay in a normal prompt after exiting the agent. const args: string[] = []; - if (process.platform !== 'win32') { - try { - const base = String(useShell).split('/').pop() || ''; - const baseLower = base.toLowerCase(); - const provider = PROVIDERS.find((p) => p.cli === baseLower); - - if (provider) { - // Build the provider command with flags - const cliArgs: string[] = []; - - // Add resume flag FIRST if available (unless skipResume is true) - if (provider.resumeFlag && !skipResume) { - const resumeParts = provider.resumeFlag.split(' '); - cliArgs.push(...resumeParts); - } - - // Then add default args - if (provider.defaultArgs?.length) { - cliArgs.push(...provider.defaultArgs); - } - - // Then auto-approve flag - if (autoApprove && provider.autoApproveFlag) { - cliArgs.push(provider.autoApproveFlag); - } - - // Finally initial prompt - if (provider.initialPromptFlag !== undefined && initialPrompt?.trim()) { - if (provider.initialPromptFlag) { - cliArgs.push(provider.initialPromptFlag); - } - cliArgs.push(initialPrompt.trim()); - } + try { + const provider = detectProviderFromShellCommand(useShell); - const cliCommand = provider.cli || baseLower; + if (provider) { + if (process.platform === 'win32') { + // On Windows, spawn the provider CLI directly with args. + const cliCommand = provider.cli || String(useShell); + const resolvedCliPath = resolveCliPath(cliCommand); + if (resolvedCliPath) useShell = resolvedCliPath; + args.push( + ...buildProviderCliArgs(provider, { + autoApprove, + initialPrompt, + skipResume, + }) + ); + } else { + // On POSIX shells, spawn the user's shell and run the provider via `-c`, + // then exec back into an interactive login shell. + const cliArgs = buildProviderCliArgs(provider, { autoApprove, initialPrompt, skipResume }); + const cliCommand = provider.cli || String(useShell); + const resolvedCliPath = resolveCliPath(cliCommand); + const finalCommand = resolvedCliPath || cliCommand; + const quotedFinalCommand = /[\s'"\\$`\n\r\t]/.test(finalCommand) + ? `'${finalCommand.replace(/'/g, "'\\''")}'` + : finalCommand; const commandString = cliArgs.length > 0 - ? `${cliCommand} ${cliArgs + ? `${quotedFinalCommand} ${cliArgs .map((arg) => /[\s'"\\$`\n\r\t]/.test(arg) ? `'${arg.replace(/'/g, "'\\''")}'` : arg ) .join(' ')}` - : cliCommand; + : quotedFinalCommand; - // After the provider exits, exec back into the user's shell (login+interactive) const resumeShell = `'${defaultShell.replace(/'/g, "'\\''")}' -il`; const chainCommand = `${commandString}; exec ${resumeShell}`; - // Always use the default shell for the -c command to avoid re-detecting provider CLI useShell = defaultShell; const shellBase = defaultShell.split('/').pop() || ''; if (shellBase === 'zsh') args.push('-lic', chainCommand); else if (shellBase === 'bash') args.push('-lic', chainCommand); else if (shellBase === 'fish') args.push('-ic', chainCommand); else if (shellBase === 'sh') args.push('-lc', chainCommand); - else args.push('-c', chainCommand); // Fallback for other shells - } else { - // For normal shells, use login + interactive to load user configs - if (base === 'zsh') args.push('-il'); - else if (base === 'bash') args.push('-il'); - else if (base === 'fish') args.push('-il'); - else if (base === 'sh') args.push('-il'); - else args.push('-i'); // Fallback for other shells + else args.push('-c', chainCommand); } - } catch {} - } + } else if (process.platform !== 'win32') { + // For normal shells on POSIX, use login + interactive to load user configs + const base = String(useShell).split('/').pop() || ''; + if (base === 'zsh') args.push('-il'); + else if (base === 'bash') args.push('-il'); + else if (base === 'fish') args.push('-il'); + else if (base === 'sh') args.push('-il'); + else args.push('-i'); + } + } catch {} let proc: IPty; try { diff --git a/src/renderer/components/ChatInterface.tsx b/src/renderer/components/ChatInterface.tsx index b5656434..2745a783 100644 --- a/src/renderer/components/ChatInterface.tsx +++ b/src/renderer/components/ChatInterface.tsx @@ -47,7 +47,7 @@ const ChatInterface: React.FC = ({ >({}); const [provider, setProvider] = useState(initialProvider || 'claude'); const currentProviderStatus = providerStatuses[provider]; - const [cliStartFailed, setCliStartFailed] = useState(false); + const [cliStartError, setCliStartError] = useState(null); // Multi-chat state const [conversations, setConversations] = useState([]); @@ -222,7 +222,7 @@ const ChatInterface: React.FC = ({ }, [provider, terminalId]); useEffect(() => { - setCliStartFailed(false); + setCliStartError(null); setIsProviderInstalled(null); }, [task.id]); @@ -838,16 +838,20 @@ const ChatInterface: React.FC = ({ installCommand={getInstallCommandForProvider(provider as any)} onRunInstall={runInstallCommand} onOpenExternal={(url) => window.electronAPI.openExternal(url)} + mode="missing" /> ); } - if (cliStartFailed) { + if (cliStartError) { return ( window.electronAPI.openExternal(url)} + mode="start_failed" + details={cliStartError} /> ); } @@ -891,11 +895,11 @@ const ChatInterface: React.FC = ({ window.localStorage.setItem(`provider:locked:${task.id}`, provider); } catch {} }} - onStartError={() => { - setCliStartFailed(true); + onStartError={(message) => { + setCliStartError(message); }} onStartSuccess={() => { - setCliStartFailed(false); + setCliStartError(null); // Mark initial injection as sent so it won't re-run on restart if (initialInjection && !task.metadata?.initialInjectionSent) { void window.electronAPI.saveTask({ diff --git a/src/renderer/components/InstallBanner.tsx b/src/renderer/components/InstallBanner.tsx index f093a2e5..dba678c9 100644 --- a/src/renderer/components/InstallBanner.tsx +++ b/src/renderer/components/InstallBanner.tsx @@ -10,6 +10,8 @@ type Props = { installCommand?: string | null; terminalId?: string; onRunInstall?: (command: string) => void; + mode?: 'missing' | 'start_failed'; + details?: string | null; }; export const InstallBanner: React.FC = ({ @@ -18,12 +20,15 @@ export const InstallBanner: React.FC = ({ installCommand, terminalId, onRunInstall, + mode = 'missing', + details, }) => { const meta = providerMeta[provider]; const helpUrl = getDocUrlForProvider(provider) ?? null; const baseLabel = meta?.label || 'this provider'; - const command = installCommand || getInstallCommandForProvider(provider); + const command = + installCommand === undefined ? getInstallCommandForProvider(provider) : installCommand; const canRunInstall = Boolean(command && (onRunInstall || terminalId)); const [copied, setCopied] = useState(false); const copyResetRef = useRef(null); @@ -70,6 +75,13 @@ export const InstallBanner: React.FC = ({ }; }, []); + const showInstall = mode === 'missing'; + const showDetails = mode === 'start_failed' && Boolean(details?.trim()); + const isPtyDisabled = + mode === 'start_failed' && + (details?.includes('EMDASH_DISABLE_PTY=1') || + details?.toLowerCase().includes('pty unavailable')); + return (
@@ -87,12 +99,26 @@ export const InstallBanner: React.FC = ({ ) : ( baseLabel )}{' '} - isn’t installed. + {mode === 'start_failed' ? 'couldn’t start.' : 'isn’t installed.'} {' '} - Run this in the terminal to use it: + {showInstall ? ( + Run this in the terminal to use it: + ) : null}
- {command ? ( + {showDetails ? ( +
+ Error: {details} + {isPtyDisabled ? ( +
+ Embedded terminals are disabled/unavailable. Unset `EMDASH_DISABLE_PTY` (or set it + to `0`) and ensure the PTY native module is installed. +
+ ) : null} +
+ ) : null} + + {showInstall && command ? (
{command} @@ -124,9 +150,9 @@ export const InstallBanner: React.FC = ({ ) : null}
- ) : ( + ) : showInstall ? (
Install the CLI to use it.
- )} + ) : null}
); diff --git a/src/test/main/WorktreeService.test.ts b/src/test/main/WorktreeService.test.ts index 4cd83f75..20c2c711 100644 --- a/src/test/main/WorktreeService.test.ts +++ b/src/test/main/WorktreeService.test.ts @@ -154,8 +154,11 @@ describe('WorktreeService', () => { await service.preserveFilesToWorktree(sourceDir, destDir); const destStat = fs.statSync(path.join(destDir, '.env')); - // Check that permissions are preserved (at least the readable/writable bits) - expect(destStat.mode & 0o777).toBe(0o600); + // POSIX-only: Windows doesn't reliably preserve/represent these permission bits. + if (process.platform !== 'win32') { + // Check that permissions are preserved (at least the readable/writable bits) + expect(destStat.mode & 0o777).toBe(0o600); + } }); it('should return empty result when no patterns match', async () => {