diff --git a/.config/guardian/.gdnbaselines b/.config/guardian/.gdnbaselines deleted file mode 100644 index e8f9f8db2f7b6..0000000000000 --- a/.config/guardian/.gdnbaselines +++ /dev/null @@ -1,420 +0,0 @@ -{ - "properties": { - "helpUri": "https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/baselines" - }, - "version": "1.0.0", - "baselines": { - "default": { - "name": "default", - "createdDate": "2025-01-28 06:29:05Z", - "lastUpdatedDate": "2025-01-28 06:29:05Z" - } - }, - "results": { - "ea3b2bf4f5b3d0bd8a6ad35cc61e49f2a1596660fd66d17d740e4806e7ed7dcc": { - "signature": "ea3b2bf4f5b3d0bd8a6ad35cc61e49f2a1596660fd66d17d740e4806e7ed7dcc", - "alternativeSignatures": [ - "ff528c0b5a010ae7b5e9178b004a8b816a429a28ba98ce8336466b490a09dcef" - ], - "target": ".build/win32-arm64/system-setup/VSCodeSetup-arm64-1.97.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2009", - "createdDate": "2025-01-30 19:19:49Z", - "expirationDate": "2025-07-19 21:12:48Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-01-30 21:12:48Z" - }, - "12babbc85192ed1c8d927693da788537c1eef199bbecbe226f940a2d0e97637c": { - "signature": "12babbc85192ed1c8d927693da788537c1eef199bbecbe226f940a2d0e97637c", - "alternativeSignatures": [ - "35b0519e201e56fb87fc6fb085e6fb1df5b89715142bb9086a5b2006e0fd4ced" - ], - "target": ".build/win32-arm64/system-setup/VSCodeSetup-arm64-1.97.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2018", - "createdDate": "2025-01-30 19:19:49Z", - "expirationDate": "2025-07-19 21:12:48Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-01-30 21:12:48Z" - }, - "49163bd1dc9d965d3baced1694dc8c43305b8bf96e884f478d8e4bd124454ba0": { - "signature": "49163bd1dc9d965d3baced1694dc8c43305b8bf96e884f478d8e4bd124454ba0", - "alternativeSignatures": [ - "aa80bcf44aa8ddd20fb9802e9032c1257048b973896a944ded70bb195f060b2a" - ], - "target": ".build/win32-arm64/user-setup/VSCodeUserSetup-arm64-1.97.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2009", - "createdDate": "2025-01-30 19:21:17Z", - "expirationDate": "2025-07-19 21:12:48Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-01-30 21:12:48Z" - }, - "c405af02e021c3a473d4e45ec4daa658db1527ea7430c6be968d182e7b50fbd1": { - "signature": "c405af02e021c3a473d4e45ec4daa658db1527ea7430c6be968d182e7b50fbd1", - "alternativeSignatures": [ - "619d2a1a77f55b4181493b8cfdf09be5261e539115752af2e4938f5ac04af132" - ], - "target": ".build/win32-arm64/user-setup/VSCodeUserSetup-arm64-1.97.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2018", - "createdDate": "2025-01-30 19:21:17Z", - "expirationDate": "2025-07-19 21:12:48Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-01-30 21:12:48Z" - }, - "71b8515b2eb51cfd5eace11cedb15189d51ce9e479095a5938334416088cbc03": { - "signature": "71b8515b2eb51cfd5eace11cedb15189d51ce9e479095a5938334416088cbc03", - "alternativeSignatures": [ - "b34279fc5fec828b8dcd9ca873804e85d7d9cd78554ec109d2dd493351a7a244" - ], - "target": ".build/win32-x64/system-setup/VSCodeSetup-x64-1.97.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2009", - "createdDate": "2025-01-30 19:51:51Z", - "expirationDate": "2025-07-19 21:12:48Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-01-30 21:12:48Z" - }, - "9238de77a5320039def14694d1b6f501cc2288f13c9c688d2e0501fc5a56ee61": { - "signature": "9238de77a5320039def14694d1b6f501cc2288f13c9c688d2e0501fc5a56ee61", - "alternativeSignatures": [ - "1d17616a549e9f36d814c4e802d651b1af453ce0a23d4478eef39be81adcc16b" - ], - "target": ".build/win32-x64/system-setup/VSCodeSetup-x64-1.97.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2018", - "createdDate": "2025-01-30 19:51:51Z", - "expirationDate": "2025-07-19 21:12:48Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-01-30 21:12:48Z" - }, - "bad8b698b48c1da9ece953903581c66bf98bc829ae1a6adcd3b5c2056a6fcd01": { - "signature": "bad8b698b48c1da9ece953903581c66bf98bc829ae1a6adcd3b5c2056a6fcd01", - "alternativeSignatures": [ - "057376d31b97e8ce3ecf6a180a553b932d7e5be6e2b07a08027d5dfabe35e82c" - ], - "target": ".build/win32-x64/user-setup/VSCodeUserSetup-x64-1.97.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2009", - "createdDate": "2025-01-30 19:53:13Z", - "expirationDate": "2025-07-19 21:12:48Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-01-30 21:12:48Z" - }, - "cc7c248b0fd4c105e9a393ae232bf0d314ec50e65357a5e7e7d68f6f10c77077": { - "signature": "cc7c248b0fd4c105e9a393ae232bf0d314ec50e65357a5e7e7d68f6f10c77077", - "alternativeSignatures": [ - "f3867098aff3368682df9926e85a35ec05cf905f27d0c157430021c3169f899d" - ], - "target": ".build/win32-x64/user-setup/VSCodeUserSetup-x64-1.97.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2018", - "createdDate": "2025-01-30 19:53:13Z", - "expirationDate": "2025-07-19 21:12:48Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-01-30 21:12:48Z" - }, - "8c53250a171412b84dedcbb22cdab9ec365d9b52d74b09c070097fff45372de0": { - "signature": "8c53250a171412b84dedcbb22cdab9ec365d9b52d74b09c070097fff45372de0", - "alternativeSignatures": [ - "314267784b0ea867006e00b809a93498fae3264e42d1a3a7745ab13180a5b6ef" - ], - "target": ".build/win32-arm64/system-setup/VSCodeSetup-arm64-1.98.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2009", - "createdDate": "2025-02-04 06:16:33Z", - "expirationDate": "2025-07-24 07:25:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-02-04 07:25:17Z" - }, - "a6a58d971da858f4af219672cef73ffd0aacc47f1e2c12b8b44a428e1330d3de": { - "signature": "a6a58d971da858f4af219672cef73ffd0aacc47f1e2c12b8b44a428e1330d3de", - "alternativeSignatures": [ - "4e40f2f1683f0bf2245f35d0ebbcf2f446274d84b1db09d8e76ddfdcad5d4479" - ], - "target": ".build/win32-arm64/system-setup/VSCodeSetup-arm64-1.98.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2018", - "createdDate": "2025-02-04 06:16:33Z", - "expirationDate": "2025-07-24 07:25:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-02-04 07:25:17Z" - }, - "90e0f060e01e4a55620f609ac3241b62e8f54a059e9f4d292e93a4305fd3c39e": { - "signature": "90e0f060e01e4a55620f609ac3241b62e8f54a059e9f4d292e93a4305fd3c39e", - "alternativeSignatures": [ - "377fe43ff8404d07f4a6ca763175004f360397ded6cf5d55b655646ada90e39c" - ], - "target": ".build/win32-arm64/user-setup/VSCodeUserSetup-arm64-1.98.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2009", - "createdDate": "2025-02-04 06:17:54Z", - "expirationDate": "2025-07-24 07:25:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-02-04 07:25:17Z" - }, - "f36c3dc19566098a923877d16d6ebfcbd971f8fcd8210afb8f5558fb5ba1f203": { - "signature": "f36c3dc19566098a923877d16d6ebfcbd971f8fcd8210afb8f5558fb5ba1f203", - "alternativeSignatures": [ - "1af1f475c1617701e3d7a8fd465916bcc60c3125b8807af5d47d49137d9d468c" - ], - "target": ".build/win32-arm64/user-setup/VSCodeUserSetup-arm64-1.98.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2018", - "createdDate": "2025-02-04 06:17:54Z", - "expirationDate": "2025-07-24 07:25:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-02-04 07:25:17Z" - }, - "71193d108c53bb802f5c491276365bcff0645fb380be57288f3fbd6896166d3a": { - "signature": "71193d108c53bb802f5c491276365bcff0645fb380be57288f3fbd6896166d3a", - "alternativeSignatures": [ - "420cae2e6e34b93d7b74fc1ffddfdf23b57650ae989d838bb2d67f28e4e1db0e" - ], - "target": ".build/win32-x64/system-setup/VSCodeSetup-x64-1.98.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2009", - "createdDate": "2025-02-04 07:11:19Z", - "expirationDate": "2025-07-24 07:25:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-02-04 07:25:17Z" - }, - "444c302f49bdedcafe772322a09727b2279e3265d99deb2e307defeae3ef200b": { - "signature": "444c302f49bdedcafe772322a09727b2279e3265d99deb2e307defeae3ef200b", - "alternativeSignatures": [ - "4ff6ccbdb0745d43d3b61f82fb2f4d8a64fe9787525df81a6d7b825e79282085" - ], - "target": ".build/win32-x64/system-setup/VSCodeSetup-x64-1.98.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2018", - "createdDate": "2025-02-04 07:11:19Z", - "expirationDate": "2025-07-24 07:25:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-02-04 07:25:17Z" - }, - "4670c7c096a69ca428429ffa1f5250aac9f2e07beac0ffe587ffb37bdb1da4d4": { - "signature": "4670c7c096a69ca428429ffa1f5250aac9f2e07beac0ffe587ffb37bdb1da4d4", - "alternativeSignatures": [ - "7cead96cb508ab6e37e27bcc0f8b7ed8d0761b77f4793958c46c5ff3892ab1b6" - ], - "target": ".build/win32-x64/user-setup/VSCodeUserSetup-x64-1.98.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2009", - "createdDate": "2025-02-04 07:13:22Z", - "expirationDate": "2025-07-24 07:25:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-02-04 07:25:17Z" - }, - "a359b4a5ed2378a73f3bba93e3fb1c595db7423c3082635d12d101bbeb0a51b8": { - "signature": "a359b4a5ed2378a73f3bba93e3fb1c595db7423c3082635d12d101bbeb0a51b8", - "alternativeSignatures": [ - "125b52a21ef619a95e695085deb9492280bcf2c1decdd5e87e6416af5982d02d" - ], - "target": ".build/win32-x64/user-setup/VSCodeUserSetup-x64-1.98.0-insider.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2018", - "createdDate": "2025-02-04 07:13:22Z", - "expirationDate": "2025-07-24 07:25:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-02-04 07:25:17Z" - }, - "6216d3477ad4f56cb4ec316a9aaff02e9530a10d56469a4ef4063b8d02fe344b": { - "signature": "6216d3477ad4f56cb4ec316a9aaff02e9530a10d56469a4ef4063b8d02fe344b", - "alternativeSignatures": [ - "46ad210995b2ff199f3bee5f271938a4251ed7a60058041ace1beaa53e36b51c" - ], - "target": "file:///D:/a/_work/1/vscode-server-win32-x64/node.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2008", - "createdDate": "2025-06-02 21:46:49Z", - "expirationDate": "2025-11-19 21:48:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-06-02 21:48:17Z" - }, - "b8a4702fb4b855719e5e5033c3b629fbe6267d516ce8a18bd8f3be3b9962434b": { - "signature": "b8a4702fb4b855719e5e5033c3b629fbe6267d516ce8a18bd8f3be3b9962434b", - "alternativeSignatures": [ - "52d986be88f1c5696fc87d7794279d02f5084c645440e2dd2c3b5a2176b6bf52" - ], - "target": "file:///D:/a/_work/1/vscode-server-win32-x64-web/node.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2008", - "createdDate": "2025-06-02 21:46:49Z", - "expirationDate": "2025-11-19 21:48:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-06-02 21:48:17Z" - }, - "471f3e40d545a9d29754f7169c1e4e5b44c067f60ace4c4750b7e9abbaa76e4a": { - "signature": "471f3e40d545a9d29754f7169c1e4e5b44c067f60ace4c4750b7e9abbaa76e4a", - "alternativeSignatures": [ - "d8d66858e7ba56494a7b5cdc42278362e5191797dc9622de92f494928e0d8fa0" - ], - "target": "file:///D:/a/_work/1/vscode-server-win32-x64/node_modules/@vscode/ripgrep/bin/rg.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2008", - "createdDate": "2025-06-02 21:46:49Z", - "expirationDate": "2025-11-19 21:48:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-06-02 21:48:17Z" - }, - "1d4a48ebc63e3b652146bc16309b2d960a7168d299c7ac94cf794347c06265ef": { - "signature": "1d4a48ebc63e3b652146bc16309b2d960a7168d299c7ac94cf794347c06265ef", - "alternativeSignatures": [ - "679d725f3dda5ced7103a135600f67fb2b4ee66b286aa995205feb4eafa2e3b0" - ], - "target": "file:///D:/a/_work/1/vscode-server-win32-x64-web/node_modules/@vscode/ripgrep/bin/rg.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2008", - "createdDate": "2025-06-02 21:46:49Z", - "expirationDate": "2025-11-19 21:48:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-06-02 21:48:17Z" - }, - "21b8091cf937b1be55c7a300483182fec206bc0cd8e2666727b29c8c200aa101": { - "signature": "21b8091cf937b1be55c7a300483182fec206bc0cd8e2666727b29c8c200aa101", - "alternativeSignatures": [ - "09571db1cc8ea8e8292e9fcb6da3592a734a2314b4fc98ea97a87a7559ecdeea" - ], - "target": "file:///D:/a/_work/1/vscode-server-win32-x64/node_modules/@parcel/watcher/build/Release/watcher.node", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2007", - "createdDate": "2025-06-02 21:46:49Z", - "expirationDate": "2025-11-19 21:48:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-06-02 21:48:17Z" - }, - "4b3cc578ca0d51fe370dfe0fb2a654dc70cd8d498a62f1efa74028df9637d53b": { - "signature": "4b3cc578ca0d51fe370dfe0fb2a654dc70cd8d498a62f1efa74028df9637d53b", - "alternativeSignatures": [ - "ea934f0443da15b7f887884d4bf909c876fe2f689893677d63f95ee12f2b34ab" - ], - "target": "file:///D:/a/_work/1/vscode-server-win32-x64/node_modules/@parcel/watcher/build/Release/watcher.node", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2008", - "createdDate": "2025-06-02 21:46:49Z", - "expirationDate": "2025-11-19 21:48:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-06-02 21:48:17Z" - }, - "5fbddbcdd6dae78e3e2876c3198594a1f23b03bc4a2d34d1054c907e90f0f525": { - "signature": "5fbddbcdd6dae78e3e2876c3198594a1f23b03bc4a2d34d1054c907e90f0f525", - "alternativeSignatures": [ - "9b7cbe3971924b7a57556d4d37b67bc6d6624a19cdab5d0be6f70b588d25d273" - ], - "target": "file:///D:/a/_work/1/vscode-server-win32-x64-web/node_modules/@parcel/watcher/build/Release/watcher.node", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2007", - "createdDate": "2025-06-02 21:46:49Z", - "expirationDate": "2025-11-19 21:48:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-06-02 21:48:17Z" - }, - "5dade33533dba7144ce169a70ac1858e734d5bb95fd056b107b1dc7955ac140b": { - "signature": "5dade33533dba7144ce169a70ac1858e734d5bb95fd056b107b1dc7955ac140b", - "alternativeSignatures": [ - "d899dddcd071df9a15daf7cb3c5dd0a69d86903e5199f06afa07f1ca10de9ff2" - ], - "target": "file:///D:/a/_work/1/vscode-server-win32-x64-web/node_modules/@parcel/watcher/build/Release/watcher.node", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2008", - "createdDate": "2025-06-02 21:46:49Z", - "expirationDate": "2025-11-19 21:48:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-06-02 21:48:17Z" - }, - "8314c7866547b515200d8a02a7ad2d86053f170172ca056ae2b71ace5fd79a04": { - "signature": "8314c7866547b515200d8a02a7ad2d86053f170172ca056ae2b71ace5fd79a04", - "alternativeSignatures": [ - "d33a0c2987713428bb7fc768be76919de7d7ea04d44d0339f561a044a2616eb8" - ], - "target": "file:///D:/a/_work/1/VSCode-win32-x64/resources/app/node_modules/@vscode/ripgrep/bin/rg.exe", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2008", - "createdDate": "2025-06-02 21:46:49Z", - "expirationDate": "2025-11-19 21:48:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-06-02 21:48:17Z" - }, - "d23a7cc83e649f9a9c5831255cb7569d363799adb5490ff7e299685ea7cf5000": { - "signature": "d23a7cc83e649f9a9c5831255cb7569d363799adb5490ff7e299685ea7cf5000", - "alternativeSignatures": [ - "e4084ce79a4fed95d29189c3b10811b131a35328957ed32f16366d110fcfeafd" - ], - "target": "file:///D:/a/_work/1/VSCode-win32-x64/resources/app/node_modules/@parcel/watcher/build/Release/watcher.node", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2007", - "createdDate": "2025-06-02 21:46:49Z", - "expirationDate": "2025-11-19 21:48:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-06-02 21:48:17Z" - }, - "bb5daeeb456c17015d07ff8744bc8058ee8c66d6542554fc0d81296c179c3e1f": { - "signature": "bb5daeeb456c17015d07ff8744bc8058ee8c66d6542554fc0d81296c179c3e1f", - "alternativeSignatures": [ - "40b43227b215520ac1cb5683304d90b739718b97e8863c69164e2871065b6720" - ], - "target": "file:///D:/a/_work/1/VSCode-win32-x64/resources/app/node_modules/@parcel/watcher/build/Release/watcher.node", - "memberOf": [ - "default" - ], - "tool": "binskim", - "ruleId": "BA2008", - "createdDate": "2025-06-02 21:46:49Z", - "expirationDate": "2025-11-19 21:48:17Z", - "justification": "This error is baselined with an expiration date of 180 days from 2025-06-02 21:48:17Z" - } - } -} diff --git a/.config/guardian/.gdnsuppress b/.config/guardian/.gdnsuppress index d1d93c2afbc89..3ac8361e5fc31 100644 --- a/.config/guardian/.gdnsuppress +++ b/.config/guardian/.gdnsuppress @@ -1,5 +1,5 @@ { - "hydrated": false, + "hydrated": true, "properties": { "helpUri": "https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/suppressions" }, @@ -7,40 +7,62 @@ "suppressionSets": { "default": { "name": "default", - "createdDate": "2025-03-17 11:52:32Z", - "lastUpdatedDate": "2025-03-17 11:52:32Z" + "createdDate": "2025-01-28 06:29:05Z", + "lastUpdatedDate": "2025-08-25 17:55:20Z" } }, "results": { - "216e2ac9cb596796224b47799f656570a01fa0d9b5f935608b47d15ab613c8e8": { - "signature": "216e2ac9cb596796224b47799f656570a01fa0d9b5f935608b47d15ab613c8e8", + "6216d3477ad4f56cb4ec316a9aaff02e9530a10d56469a4ef4063b8d02fe344b": { + "signature": "6216d3477ad4f56cb4ec316a9aaff02e9530a10d56469a4ef4063b8d02fe344b", "alternativeSignatures": [ - "07746898f43afab7cc50931b33154c2d9e1a35f82a649dbe8aecf785b3d5a813" + "46ad210995b2ff199f3bee5f271938a4251ed7a60058041ace1beaa53e36b51c" ], + "target": "file:///D:/a/_work/1/vscode-server-win32-x64/node.exe", "memberOf": [ "default" ], - "createdDate": "2025-03-17 11:52:32Z" + "tool": "binskim", + "ruleId": "BA2008", + "createdDate": "2025-08-25 17:54:06Z" }, - "77797a3e44634bb2994bd13ccc95ff4575bba474585dbd2cf3068a1c16bc0624": { - "signature": "77797a3e44634bb2994bd13ccc95ff4575bba474585dbd2cf3068a1c16bc0624", + "b8a4702fb4b855719e5e5033c3b629fbe6267d516ce8a18bd8f3be3b9962434b": { + "signature": "b8a4702fb4b855719e5e5033c3b629fbe6267d516ce8a18bd8f3be3b9962434b", "alternativeSignatures": [ - "4a6cb67bd4b401e9669c13a2162660aaefc0a94a4122e5b50c198414db545672" + "52d986be88f1c5696fc87d7794279d02f5084c645440e2dd2c3b5a2176b6bf52" ], + "target": "file:///D:/a/_work/1/vscode-server-win32-x64-web/node.exe", "memberOf": [ "default" ], - "createdDate": "2025-03-17 11:52:32Z" + "tool": "binskim", + "ruleId": "BA2008", + "createdDate": "2025-08-25 17:54:06Z" }, - "30418bcc5269eaeb2832a2404465784431d4e72a2af332320c2b1db4768902ad": { - "signature": "30418bcc5269eaeb2832a2404465784431d4e72a2af332320c2b1db4768902ad", + "4dbc45d0405de2e83d53f10227e36b2a9d15eceb2c5e6934da5c4a1bffbfad89": { + "signature": "4dbc45d0405de2e83d53f10227e36b2a9d15eceb2c5e6934da5c4a1bffbfad89", "alternativeSignatures": [ - "b7b9eb974d7d3a4ae14df8695ca5a62592c8c9d20b7eda70a6535d50cbda3e7f" + "b6bab85ba5e97bc4e6ff2e8a7913cb9f4f3346f7bda435d176e0b1e3cfb883cf" ], + "target": "file:///D:/a/_work/1/vscode-server-win32-arm64/node.exe", "memberOf": [ "default" ], - "createdDate": "2025-03-17 11:52:32Z" + "tool": "binskim", + "ruleId": "BA2008", + "createdDate": "2025-08-25 17:45:35Z" + }, + "024ff37af329b63d2d9c83784cc071badf63b7729e3dd0969ab921d2f04d8e09": { + "signature": "024ff37af329b63d2d9c83784cc071badf63b7729e3dd0969ab921d2f04d8e09", + "alternativeSignatures": [ + "b46b7d6ed331f3e62eff23c57d3a074f76ef618f108929851065904200f5a572" + ], + "target": "file:///D:/a/_work/1/vscode-server-win32-arm64-web/node.exe", + "memberOf": [ + "default" + ], + "tool": "binskim", + "ruleId": "BA2008", + "createdDate": "2025-08-25 17:45:35Z" } } } diff --git a/.eslint-ignore b/.eslint-ignore index e493198185e31..c65ccc2baacda 100644 --- a/.eslint-ignore +++ b/.eslint-ignore @@ -10,6 +10,7 @@ **/extensions/markdown-language-features/media/** **/extensions/markdown-language-features/notebook-out/** **/extensions/markdown-math/notebook-out/** +**/extensions/mermaid-chat-features/chat-webview-out/** **/extensions/notebook-renderers/renderer-out/index.js **/extensions/simple-browser/media/index.js **/extensions/terminal-suggest/src/completions/upstream/** @@ -24,9 +25,8 @@ **/extensions/vscode-api-tests/testWorkspace2/** **/fixtures/** **/node_modules/** -**/out-*/**/*.js -**/out-editor-*/** -**/out/**/*.js +**/out/** +**/out-*/** **/src/**/dompurify.js **/src/**/marked.js **/src/**/semver.js diff --git a/.eslint-plugin-local/README.md b/.eslint-plugin-local/README.md new file mode 100644 index 0000000000000..1d100cfba07c8 --- /dev/null +++ b/.eslint-plugin-local/README.md @@ -0,0 +1,125 @@ +# Custom ESLint rules + +We use a set of custom [ESLint](http://eslint.org) to enforce repo specific coding rules and styles. These custom rules are run in addition to many standard ESLint rules we enable in the project. Some example custom rules includes: + +- Enforcing proper code layering +- Preventing checking in of `test.only(...)` +- Enforcing conventions in `vscode.d.ts` + +Custom rules are mostly used for enforcing or banning certain coding patterns. We tend to leave stylistic choices up to area owners unless there's a good reason to enforce something project wide. + +This doc provides a brief overview of how these rules are setup and how you can add a new one. + +# Resources +- [ESLint rules](https://eslint.org/docs/latest/extend/custom-rules) — General documentation about writing eslint rules +- [TypeScript ASTs and eslint](https://typescript-eslint.io/blog/asts-and-typescript-eslint/) — Look at how ESLint works with TS programs +- [ESTree selectors](https://eslint.org/docs/latest/extend/selectors) — Info about the selector syntax rules use to target specific nodes in an AST. Works similarly to css selectors. +- [TypeScript ESLint playground](https://typescript-eslint.io/play/#showAST=es) — Useful tool for figuring out the structure of TS programs and debugging custom rule selectors + + +# Custom Rule Configuration + +Custom rules are defined in the `.eslint-plugin-local` folder. Each rule is defined in its own TypeScript file. These follow the naming convention: + +- `code-RULE-NAME.ts` — General rules that apply to the entire repo. +- `vscode-dts-RULE-NAME.ts` — Rules that apply just to `vscode.d.ts`. + +These rules are then enabled in the `eslint.config.js` file. This is the main eslint configuration for our repo. It defines a set of file scopes which rules should apply to files in those scopes. + +For example, here's a configuration that enables the no `test.only` rule in all `*.test.ts` files in the VS Code repo: + +```ts +{ + // Define which files these rules apply to + files: [ + '**/*.test.ts' + ], + languageOptions: { parser: tseslint.parser, }, + plugins: { + 'local': pluginLocal, + }, + rules: { + // Enable the rule from .eslint-plugin-local/code-no-test-only.ts + 'local/code-no-test-only': 'error', + } +} +``` + +# Creating a new custom rule +This walks through the steps to create a new eslint rule: + +1. Create a new rule file under `.eslint-plugin-local`. Generally you should call it `code-YOUR-RULE-NAME.ts`, for example, `.eslint-plugin-local/code-no-not-null-assertions-on-undefined-values.ts` + +2. In this file, add the rule. Here's a template: + + ```ts + /*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + import * as eslint from 'eslint'; + + export = new class YourRuleName implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + messages: { + customMessageName: 'message text shown in errors/warnings', + }, + schema: false, + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + return { + [SELECTOR]: (node: any) => { + // Report errors if needed + return context.report({ + node, + messageId: 'customMessageName' + }); + } + }; + } + }; + ``` + + - Update the name of the class to match the name of your rule + - Add message entries for any errors you want to report + - Update `SELECTOR` with the [ESTree selector](https://eslint.org/docs/latest/extend/selectors) needed to target the nodes you are interested in. Use the [TypeScript ESLint playground](https://typescript-eslint.io/play/#showAST=es) to figure out which nodes you need and debug selectors + +3. Register the rule in `eslint.config.js` + + Generally this is just turning on the rule in the rule list like so: + + ```js + rules: { + // Name should match file name + 'local/code-no-not-null-assertions-on-undefined-values': 'warn', + ... + } + ``` + +Rules can also take custom arguments. For example, here's how we can pass arguments to a custom rule in the `eslint.config.js`: + +``` +rules: { + 'local/code-no-not-null-assertions-on-undefined-values': ['warn', { testsOk: true }], + ... +} +``` + +In these cases make sure to update the `meta.schema` property on your rule with the JSON schema for the arguments. You can access these arguments using `context.options` in the rule `create` function + + +## Adding fixes to custom rules +Fixes are a useful way to mechanically fix basic linting issues, such as auto inserting semicolons. These fixes typically work at the AST level, so they are a more reliable way to perform bulk fixes compared to find/replaces. + +To add a fix for a custom rule: + +1. On the `meta` for your rule, add `fixable: 'code'` + +2. When reporting an error in the rule, also include a `fix`. This is a function that takes a `fixer` argument and returns one or more fixes. + +See the [Double quoted to single quoted string covert fix](https://github.com/microsoft/vscode/blob/b074375e1884ae01033967bf0bbceeaa4795354a/.eslint-plugin-local/code-no-unexternalized-strings.ts#L128) for an example. The ESLint docs also have [details on adding fixes and the fixer api](https://eslint.org/docs/latest/extend/custom-rules#applying-fixes) + +The fixes can be run using `npx eslint --fix` in the VS Code repo diff --git a/.eslint-plugin-local/code-amd-node-module.ts b/.eslint-plugin-local/code-amd-node-module.ts index b622c98a89a7a..eb6a40c5e30f9 100644 --- a/.eslint-plugin-local/code-amd-node-module.ts +++ b/.eslint-plugin-local/code-amd-node-module.ts @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; +import { readFileSync } from 'fs'; import { join } from 'path'; -export = new class ApiProviderNaming implements eslint.Rule.RuleModule { +export default new class ApiProviderNaming implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { @@ -21,7 +23,8 @@ export = new class ApiProviderNaming implements eslint.Rule.RuleModule { const modules = new Set(); try { - const { dependencies, optionalDependencies } = require(join(__dirname, '../package.json')); + const packageJson = JSON.parse(readFileSync(join(import.meta.dirname, '../package.json'), 'utf-8')); + const { dependencies, optionalDependencies } = packageJson; const all = Object.keys(dependencies).concat(Object.keys(optionalDependencies)); for (const key of all) { modules.add(key); @@ -33,13 +36,13 @@ export = new class ApiProviderNaming implements eslint.Rule.RuleModule { } - const checkImport = (node: any) => { + const checkImport = (node: ESTree.Literal & { parent?: ESTree.Node & { importKind?: string } }) => { - if (node.type !== 'Literal' || typeof node.value !== 'string') { + if (typeof node.value !== 'string') { return; } - if (node.parent.importKind === 'type') { + if (node.parent?.type === 'ImportDeclaration' && node.parent.importKind === 'type') { return; } diff --git a/.eslint-plugin-local/code-declare-service-brand.ts b/.eslint-plugin-local/code-declare-service-brand.ts index 85cf067154522..a077e7b38c6df 100644 --- a/.eslint-plugin-local/code-declare-service-brand.ts +++ b/.eslint-plugin-local/code-declare-service-brand.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; -export = new class DeclareServiceBrand implements eslint.Rule.RuleModule { +export default new class DeclareServiceBrand implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { fixable: 'code', @@ -14,7 +15,7 @@ export = new class DeclareServiceBrand implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { return { - ['PropertyDefinition[key.name="_serviceBrand"][value]']: (node: any) => { + ['PropertyDefinition[key.name="_serviceBrand"][value]']: (node: ESTree.PropertyDefinition) => { return context.report({ node, message: `The '_serviceBrand'-property should not have a value`, diff --git a/.eslint-plugin-local/code-ensure-no-disposables-leak-in-test.ts b/.eslint-plugin-local/code-ensure-no-disposables-leak-in-test.ts index c657df9bd307b..7f1d20482b878 100644 --- a/.eslint-plugin-local/code-ensure-no-disposables-leak-in-test.ts +++ b/.eslint-plugin-local/code-ensure-no-disposables-leak-in-test.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; -import { Node } from 'estree'; +import type * as estree from 'estree'; -export = new class EnsureNoDisposablesAreLeakedInTestSuite implements eslint.Rule.RuleModule { +export default new class EnsureNoDisposablesAreLeakedInTestSuite implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { type: 'problem', @@ -18,7 +18,7 @@ export = new class EnsureNoDisposablesAreLeakedInTestSuite implements eslint.Rul }; create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - const config = <{ exclude: string[] }>context.options[0]; + const config = context.options[0] as { exclude: string[] }; const needle = context.getFilename().replace(/\\/g, '/'); if (config.exclude.some((e) => needle.endsWith(e))) { @@ -26,7 +26,7 @@ export = new class EnsureNoDisposablesAreLeakedInTestSuite implements eslint.Rul } return { - [`Program > ExpressionStatement > CallExpression[callee.name='suite']`]: (node: Node) => { + [`Program > ExpressionStatement > CallExpression[callee.name='suite']`]: (node: estree.Node) => { const src = context.getSourceCode().getText(node); if (!src.includes('ensureNoDisposablesAreLeakedInTestSuite(')) { context.report({ diff --git a/.eslint-plugin-local/code-import-patterns.ts b/.eslint-plugin-local/code-import-patterns.ts index e4beb9a473872..419e26d41acdd 100644 --- a/.eslint-plugin-local/code-import-patterns.ts +++ b/.eslint-plugin-local/code-import-patterns.ts @@ -7,9 +7,9 @@ import * as eslint from 'eslint'; import { TSESTree } from '@typescript-eslint/utils'; import * as path from 'path'; import minimatch from 'minimatch'; -import { createImportRuleListener } from './utils'; +import { createImportRuleListener } from './utils.ts'; -const REPO_ROOT = path.normalize(path.join(__dirname, '../')); +const REPO_ROOT = path.normalize(path.join(import.meta.dirname, '../')); interface ConditionalPattern { when?: 'hasBrowser' | 'hasNode' | 'hasElectron' | 'test'; @@ -31,7 +31,7 @@ interface LayerAllowRule { type RawOption = RawImportPatternsConfig | LayerAllowRule; function isLayerAllowRule(option: RawOption): option is LayerAllowRule { - return !!((option).when && (option).allow); + return !!((option as LayerAllowRule).when && (option as LayerAllowRule).allow); } interface ImportPatternsConfig { @@ -39,7 +39,7 @@ interface ImportPatternsConfig { restrictions: string[]; } -export = new class implements eslint.Rule.RuleModule { +export default new class implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { @@ -55,7 +55,7 @@ export = new class implements eslint.Rule.RuleModule { }; create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - const options = context.options; + const options = context.options as RawOption[]; const configs = this._processOptions(options); const relativeFilename = getRelativeFilename(context); @@ -217,7 +217,7 @@ export = new class implements eslint.Rule.RuleModule { configs.push(testConfig); } } else { - configs.push({ target, restrictions: restrictions.filter(r => typeof r === 'string') }); + configs.push({ target, restrictions: restrictions.filter(r => typeof r === 'string') as string[] }); } } this._optionsCache.set(options, configs); diff --git a/.eslint-plugin-local/code-layering.ts b/.eslint-plugin-local/code-layering.ts index f8b769a1bf63d..ac77eb97cf069 100644 --- a/.eslint-plugin-local/code-layering.ts +++ b/.eslint-plugin-local/code-layering.ts @@ -5,14 +5,14 @@ import * as eslint from 'eslint'; import { join, dirname } from 'path'; -import { createImportRuleListener } from './utils'; +import { createImportRuleListener } from './utils.ts'; type Config = { allowed: Set; disallowed: Set; }; -export = new class implements eslint.Rule.RuleModule { +export default new class implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { @@ -38,8 +38,7 @@ export = new class implements eslint.Rule.RuleModule { const fileDirname = dirname(context.getFilename()); const parts = fileDirname.split(/\\|\//); - const ruleArgs = >context.options[0]; - + const ruleArgs = context.options[0] as Record; let config: Config | undefined; for (let i = parts.length - 1; i >= 0; i--) { if (ruleArgs[parts[i]]) { @@ -91,4 +90,3 @@ export = new class implements eslint.Rule.RuleModule { }); } }; - diff --git a/.eslint-plugin-local/code-limited-top-functions.ts b/.eslint-plugin-local/code-limited-top-functions.ts index 7b48d02a0fe3b..8c6abacc9d8f5 100644 --- a/.eslint-plugin-local/code-limited-top-functions.ts +++ b/.eslint-plugin-local/code-limited-top-functions.ts @@ -6,8 +6,9 @@ import * as eslint from 'eslint'; import { dirname, relative } from 'path'; import minimatch from 'minimatch'; +import type * as ESTree from 'estree'; -export = new class implements eslint.Rule.RuleModule { +export default new class implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { @@ -28,11 +29,11 @@ export = new class implements eslint.Rule.RuleModule { }; create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - let fileRelativePath = relative(dirname(__dirname), context.getFilename()); + let fileRelativePath = relative(dirname(import.meta.dirname), context.getFilename()); if (!fileRelativePath.endsWith('/')) { fileRelativePath += '/'; } - const ruleArgs = >context.options[0]; + const ruleArgs = context.options[0] as Record; const matchingKey = Object.keys(ruleArgs).find(key => fileRelativePath.startsWith(key) || minimatch(fileRelativePath, key)); if (!matchingKey) { @@ -43,8 +44,8 @@ export = new class implements eslint.Rule.RuleModule { const restrictedFunctions = ruleArgs[matchingKey]; return { - FunctionDeclaration: (node: any) => { - const isTopLevel = node.parent.type === 'Program'; + FunctionDeclaration: (node: ESTree.FunctionDeclaration & { parent?: ESTree.Node }) => { + const isTopLevel = node.parent?.type === 'Program'; const functionName = node.id.name; if (isTopLevel && !restrictedFunctions.includes(node.id.name)) { context.report({ @@ -53,10 +54,10 @@ export = new class implements eslint.Rule.RuleModule { }); } }, - ExportNamedDeclaration(node: any) { + ExportNamedDeclaration(node: ESTree.ExportNamedDeclaration & { parent?: ESTree.Node }) { if (node.declaration && node.declaration.type === 'FunctionDeclaration') { const functionName = node.declaration.id.name; - const isTopLevel = node.parent.type === 'Program'; + const isTopLevel = node.parent?.type === 'Program'; if (isTopLevel && !restrictedFunctions.includes(node.declaration.id.name)) { context.report({ node, diff --git a/.eslint-plugin-local/code-must-use-result.ts b/.eslint-plugin-local/code-must-use-result.ts index e249f36dccf7d..b97396c7e5226 100644 --- a/.eslint-plugin-local/code-must-use-result.ts +++ b/.eslint-plugin-local/code-must-use-result.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; const VALID_USES = new Set([ @@ -11,22 +12,22 @@ const VALID_USES = new Set([ TSESTree.AST_NODE_TYPES.VariableDeclarator, ]); -export = new class MustUseResults implements eslint.Rule.RuleModule { +export default new class MustUseResults implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { schema: false }; create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - const config = <{ message: string; functions: string[] }[]>context.options[0]; + const config = context.options[0] as { message: string; functions: string[] }[]; const listener: eslint.Rule.RuleListener = {}; for (const { message, functions } of config) { for (const fn of functions) { const query = `CallExpression[callee.property.name='${fn}'], CallExpression[callee.name='${fn}']`; - listener[query] = (node: any) => { - const cast: TSESTree.CallExpression = node; - if (!VALID_USES.has(cast.parent?.type)) { + listener[query] = (node: ESTree.Node) => { + const callExpression = node as TSESTree.CallExpression; + if (!VALID_USES.has(callExpression.parent?.type)) { context.report({ node, message }); } }; diff --git a/.eslint-plugin-local/code-must-use-super-dispose.ts b/.eslint-plugin-local/code-must-use-super-dispose.ts index ca776d8a2ad53..0213d20095731 100644 --- a/.eslint-plugin-local/code-must-use-super-dispose.ts +++ b/.eslint-plugin-local/code-must-use-super-dispose.ts @@ -3,18 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { TSESTree } from '@typescript-eslint/utils'; import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; -export = new class NoAsyncSuite implements eslint.Rule.RuleModule { +export default new class NoAsyncSuite implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - function doesCallSuperDispose(node: any) { + function doesCallSuperDispose(node: TSESTree.MethodDefinition) { if (!node.override) { return; } - const body = context.getSourceCode().getText(node); + const body = context.getSourceCode().getText(node as ESTree.Node); if (body.includes('super.dispose')) { return; diff --git a/.eslint-plugin-local/code-no-any-casts.ts b/.eslint-plugin-local/code-no-any-casts.ts new file mode 100644 index 0000000000000..87c3c9466cd18 --- /dev/null +++ b/.eslint-plugin-local/code-no-any-casts.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; +import { TSESTree } from '@typescript-eslint/utils'; + +export default new class NoAnyCasts implements eslint.Rule.RuleModule { + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + return { + 'TSTypeAssertion[typeAnnotation.type="TSAnyKeyword"], TSAsExpression[typeAnnotation.type="TSAnyKeyword"]': (node: TSESTree.TSTypeAssertion | TSESTree.TSAsExpression) => { + context.report({ + node, + message: `Avoid casting to 'any' type. Consider using a more specific type or type guards for better type safety.` + }); + } + }; + } +}; diff --git a/.eslint-plugin-local/code-no-dangerous-type-assertions.ts b/.eslint-plugin-local/code-no-dangerous-type-assertions.ts index f900d778a9469..b2e97943670ea 100644 --- a/.eslint-plugin-local/code-no-dangerous-type-assertions.ts +++ b/.eslint-plugin-local/code-no-dangerous-type-assertions.ts @@ -4,20 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; -export = new class NoDangerousTypeAssertions implements eslint.Rule.RuleModule { +export default new class NoDangerousTypeAssertions implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - // Disable in tests for now - if (context.getFilename().includes('.test')) { - return {}; - } - return { // Disallow type assertions on object literals: { ... } or {} as T - ['TSTypeAssertion > ObjectExpression, TSAsExpression > ObjectExpression']: (node: any) => { - const objectNode = node as TSESTree.Node; + ['TSTypeAssertion > ObjectExpression, TSAsExpression > ObjectExpression']: (node: ESTree.ObjectExpression) => { + const objectNode = node as TSESTree.ObjectExpression; const parent = objectNode.parent as TSESTree.TSTypeAssertion | TSESTree.TSAsExpression; if ( diff --git a/.eslint-plugin-local/code-no-deep-import-of-internal.ts b/.eslint-plugin-local/code-no-deep-import-of-internal.ts index 3f54665b49ac8..cb2d450d2ee1a 100644 --- a/.eslint-plugin-local/code-no-deep-import-of-internal.ts +++ b/.eslint-plugin-local/code-no-deep-import-of-internal.ts @@ -5,9 +5,9 @@ import * as eslint from 'eslint'; import { join, dirname } from 'path'; -import { createImportRuleListener } from './utils'; +import { createImportRuleListener } from './utils.ts'; -export = new class implements eslint.Rule.RuleModule { +export default new class implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { @@ -28,8 +28,8 @@ export = new class implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { const patterns = context.options[0] as Record; - const internalModulePattern = Object.entries(patterns).map(([key, v]) => v ? key : undefined).filter(v => !!v); - const allowedPatterns = Object.entries(patterns).map(([key, v]) => !v ? key : undefined).filter(v => !!v); + const internalModulePattern = Object.entries(patterns).map(([key, v]) => v ? key : undefined).filter((v): v is string => !!v); + const allowedPatterns = Object.entries(patterns).map(([key, v]) => !v ? key : undefined).filter((v): v is string => !!v); return createImportRuleListener((node, path) => { const importerModuleDir = dirname(context.filename); diff --git a/.eslint-plugin-local/code-no-global-document-listener.ts b/.eslint-plugin-local/code-no-global-document-listener.ts index 049426a5a0308..ad4ec0da820b6 100644 --- a/.eslint-plugin-local/code-no-global-document-listener.ts +++ b/.eslint-plugin-local/code-no-global-document-listener.ts @@ -5,7 +5,7 @@ import * as eslint from 'eslint'; -export = new class NoGlobalDocumentListener implements eslint.Rule.RuleModule { +export default new class NoGlobalDocumentListener implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { return { diff --git a/.eslint-plugin-local/code-no-in-operator.ts b/.eslint-plugin-local/code-no-in-operator.ts new file mode 100644 index 0000000000000..026a8f5fe7a3b --- /dev/null +++ b/.eslint-plugin-local/code-no-in-operator.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; +import { TSESTree } from '@typescript-eslint/utils'; + +/** + * Disallows the use of the `in` operator in TypeScript code, except within + * type predicate functions (functions with `arg is Type` return types). + * + * The `in` operator can lead to runtime errors and type safety issues. + * Consider using Object.hasOwn(), hasOwnProperty(), or other safer patterns. + * + * Exception: Type predicate functions are allowed to use the `in` operator + * since they are the standard way to perform runtime type checking. + */ +export default new class NoInOperator implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + messages: { + noInOperator: 'The "in" operator should not be used. Use type discriminator properties and classes instead or the `hasKey`-utility.', + }, + schema: false, + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + + function checkInOperator(inNode: ESTree.BinaryExpression) { + const node = inNode as TSESTree.BinaryExpression; + // Check if we're inside a type predicate function + const ancestors = context.sourceCode.getAncestors(node as ESTree.Node); + + for (const ancestor of ancestors) { + if (ancestor.type === 'FunctionDeclaration' || + ancestor.type === 'FunctionExpression' || + ancestor.type === 'ArrowFunctionExpression') { + + // Check if this function has a type predicate return type + // Type predicates have the form: `arg is SomeType` + if ((ancestor as { returnType?: any }).returnType?.typeAnnotation?.type === 'TSTypePredicate') { + // This is a type predicate function, allow the "in" operator + return; + } + } + } + + context.report({ + node, + messageId: 'noInOperator' + }); + } + + return { + ['BinaryExpression[operator="in"]']: checkInOperator, + }; + } +}; diff --git a/.eslint-plugin-local/code-no-localization-template-literals.ts b/.eslint-plugin-local/code-no-localization-template-literals.ts new file mode 100644 index 0000000000000..30a5de7f364ff --- /dev/null +++ b/.eslint-plugin-local/code-no-localization-template-literals.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; +import { TSESTree } from '@typescript-eslint/utils'; + +/** + * Prevents the use of template literals in localization function calls. + * + * vscode.l10n.t() and nls.localize() cannot handle string templating. + * Use placeholders instead: vscode.l10n.t('Message {0}', value) + * + * Examples: + * ❌ vscode.l10n.t(`Message ${value}`) + * ✅ vscode.l10n.t('Message {0}', value) + * + * ❌ nls.localize('key', `Message ${value}`) + * ✅ nls.localize('key', 'Message {0}', value) + */ +export default new class NoLocalizationTemplateLiterals implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + messages: { + noTemplateLiteral: 'Template literals cannot be used in localization calls. Use placeholders like {0}, {1} instead.' + }, + docs: { + description: 'Prevents template literals in vscode.l10n.t() and nls.localize() calls', + }, + schema: false, + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + + function checkCallExpression(node: TSESTree.CallExpression) { + const callee = node.callee; + let isLocalizationCall = false; + let isNlsLocalize = false; + + // Check for vscode.l10n.t() + if (callee.type === 'MemberExpression') { + const object = callee.object; + const property = callee.property; + + // vscode.l10n.t + if (object.type === 'MemberExpression') { + const outerObject = object.object; + const outerProperty = object.property; + if (outerObject.type === 'Identifier' && outerObject.name === 'vscode' && + outerProperty.type === 'Identifier' && outerProperty.name === 'l10n' && + property.type === 'Identifier' && property.name === 't') { + isLocalizationCall = true; + } + } + + // l10n.t or nls.localize or any *.localize + if (object.type === 'Identifier' && property.type === 'Identifier') { + if (object.name === 'l10n' && property.name === 't') { + isLocalizationCall = true; + } else if (property.name === 'localize') { + isLocalizationCall = true; + isNlsLocalize = true; + } + } + } + + if (!isLocalizationCall) { + return; + } + + // For vscode.l10n.t(message, ...args) - check the first argument (message) + // For nls.localize(key, message, ...args) - check first two arguments (key and message) + const argsToCheck = isNlsLocalize ? 2 : 1; + for (let i = 0; i < argsToCheck && i < node.arguments.length; i++) { + const arg = node.arguments[i]; + if (arg && arg.type === 'TemplateLiteral' && arg.expressions.length > 0) { + context.report({ + node: arg, + messageId: 'noTemplateLiteral' + }); + } + } + } + + return { + CallExpression: (node: any) => checkCallExpression(node as TSESTree.CallExpression) + }; + } +}; diff --git a/.eslint-plugin-local/code-no-localized-model-description.ts b/.eslint-plugin-local/code-no-localized-model-description.ts new file mode 100644 index 0000000000000..a624aeb8619cf --- /dev/null +++ b/.eslint-plugin-local/code-no-localized-model-description.ts @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TSESTree } from '@typescript-eslint/utils'; +import * as eslint from 'eslint'; +import * as visitorKeys from 'eslint-visitor-keys'; +import type * as ESTree from 'estree'; + +const MESSAGE_ID = 'noLocalizedModelDescription'; +type NodeWithChildren = TSESTree.Node & { + [key: string]: TSESTree.Node | TSESTree.Node[] | null | undefined; +}; +type PropertyKeyNode = TSESTree.Property['key'] | TSESTree.MemberExpression['property']; +type AssignmentTarget = TSESTree.AssignmentExpression['left']; + +export default new class NoLocalizedModelDescriptionRule implements eslint.Rule.RuleModule { + meta: eslint.Rule.RuleMetaData = { + messages: { + [MESSAGE_ID]: 'modelDescription values describe behavior to the language model and must not use localized strings.' + }, + type: 'problem', + schema: false + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + const reportIfLocalized = (expression: TSESTree.Expression | null | undefined) => { + if (expression && containsLocalizedCall(expression)) { + context.report({ node: expression, messageId: MESSAGE_ID }); + } + }; + + return { + Property: (node: ESTree.Property) => { + const propertyNode = node as TSESTree.Property; + if (!isModelDescriptionKey(propertyNode.key, propertyNode.computed)) { + return; + } + reportIfLocalized(propertyNode.value as TSESTree.Expression); + }, + AssignmentExpression: (node: ESTree.AssignmentExpression) => { + const assignment = node as TSESTree.AssignmentExpression; + if (!isModelDescriptionAssignmentTarget(assignment.left)) { + return; + } + reportIfLocalized(assignment.right); + } + }; + } +}; + +function isModelDescriptionKey(key: PropertyKeyNode, computed: boolean | undefined): boolean { + if (!computed && key.type === 'Identifier') { + return key.name === 'modelDescription'; + } + if (key.type === 'Literal' && key.value === 'modelDescription') { + return true; + } + return false; +} + +function isModelDescriptionAssignmentTarget(target: AssignmentTarget): target is TSESTree.MemberExpression { + if (target.type === 'MemberExpression') { + return isModelDescriptionKey(target.property, target.computed); + } + return false; +} + +function containsLocalizedCall(expression: TSESTree.Expression): boolean { + let found = false; + + const visit = (node: TSESTree.Node) => { + if (found) { + return; + } + + if (isLocalizeCall(node)) { + found = true; + return; + } + + for (const key of visitorKeys.KEYS[node.type] ?? []) { + const value = (node as NodeWithChildren)[key]; + if (Array.isArray(value)) { + for (const child of value) { + if (child) { + visit(child); + if (found) { + return; + } + } + } + } else if (value) { + visit(value); + } + } + }; + + visit(expression); + return found; +} + +function isLocalizeCall(node: TSESTree.Node): boolean { + if (node.type === 'CallExpression') { + return isLocalizeCallee(node.callee); + } + if (node.type === 'ChainExpression') { + return isLocalizeCall(node.expression); + } + return false; +} + + +function isLocalizeCallee(callee: TSESTree.CallExpression['callee']): boolean { + if (callee.type === 'Identifier') { + return callee.name === 'localize'; + } + if (callee.type === 'MemberExpression') { + if (!callee.computed && callee.property.type === 'Identifier') { + return callee.property.name === 'localize'; + } + if (callee.property.type === 'Literal' && callee.property.value === 'localize') { + return true; + } + } + return false; +} diff --git a/.eslint-plugin-local/code-no-native-private.ts b/.eslint-plugin-local/code-no-native-private.ts index e2d20694ca8f1..5d945ec34f7a5 100644 --- a/.eslint-plugin-local/code-no-native-private.ts +++ b/.eslint-plugin-local/code-no-native-private.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; -export = new class ApiProviderNaming implements eslint.Rule.RuleModule { +export default new class ApiProviderNaming implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { @@ -17,13 +18,13 @@ export = new class ApiProviderNaming implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { return { - ['PropertyDefinition PrivateIdentifier']: (node: any) => { + ['PropertyDefinition PrivateIdentifier']: (node: ESTree.Node) => { context.report({ node, messageId: 'slow' }); }, - ['MethodDefinition PrivateIdentifier']: (node: any) => { + ['MethodDefinition PrivateIdentifier']: (node: ESTree.Node) => { context.report({ node, messageId: 'slow' diff --git a/.eslint-plugin-local/code-no-nls-in-standalone-editor.ts b/.eslint-plugin-local/code-no-nls-in-standalone-editor.ts index c0d60985604c0..2b3896795a827 100644 --- a/.eslint-plugin-local/code-no-nls-in-standalone-editor.ts +++ b/.eslint-plugin-local/code-no-nls-in-standalone-editor.ts @@ -5,9 +5,9 @@ import * as eslint from 'eslint'; import { join } from 'path'; -import { createImportRuleListener } from './utils'; +import { createImportRuleListener } from './utils.ts'; -export = new class NoNlsInStandaloneEditorRule implements eslint.Rule.RuleModule { +export default new class NoNlsInStandaloneEditorRule implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { diff --git a/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts b/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts new file mode 100644 index 0000000000000..94d3a1b4ead0d --- /dev/null +++ b/.eslint-plugin-local/code-no-observable-get-in-reactive-context.ts @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TSESTree } from '@typescript-eslint/utils'; +import * as eslint from 'eslint'; +import * as visitorKeys from 'eslint-visitor-keys'; +import type * as ESTree from 'estree'; + +export default new class NoObservableGetInReactiveContext implements eslint.Rule.RuleModule { + meta: eslint.Rule.RuleMetaData = { + type: 'problem', + docs: { + description: 'Disallow calling .get() on observables inside reactive contexts in favor of .read(undefined).', + }, + fixable: 'code', + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + return { + 'CallExpression': (node: ESTree.CallExpression) => { + const callExpression = node as TSESTree.CallExpression; + + if (!isReactiveFunctionWithReader(callExpression.callee)) { + return; + } + + const functionArg = callExpression.arguments.find(arg => + arg.type === 'ArrowFunctionExpression' || arg.type === 'FunctionExpression' + ) as TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression | undefined; + + if (!functionArg) { + return; + } + + const readerName = getReaderParameterName(functionArg); + if (!readerName) { + return; + } + + checkFunctionForObservableGetCalls(functionArg, readerName, context); + } + }; + } +}; + +function checkFunctionForObservableGetCalls( + fn: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, + readerName: string, + context: eslint.Rule.RuleContext +) { + const visited = new Set(); + + function traverse(node: TSESTree.Node) { + if (visited.has(node)) { + return; + } + visited.add(node); + + if (node.type === 'CallExpression' && isObservableGetCall(node)) { + // Flag .get() calls since we're always in a reactive context here + context.report({ + node: node, + message: `Observable '.get()' should not be used in reactive context. Use '.read(${readerName})' instead to properly track dependencies or '.read(undefined)' to be explicit about an untracked read.`, + fix: (fixer) => { + const memberExpression = node.callee as TSESTree.MemberExpression; + return fixer.replaceText(node, `${context.getSourceCode().getText(memberExpression.object as ESTree.Node)}.read(undefined)`); + } + }); + } + + walkChildren(node, traverse); + } + + if (fn.body) { + traverse(fn.body); + } +} + +function isObservableGetCall(node: TSESTree.CallExpression): boolean { + // Look for pattern: something.get() + if (node.callee.type === 'MemberExpression' && + node.callee.property.type === 'Identifier' && + node.callee.property.name === 'get' && + node.arguments.length === 0) { + + // This is a .get() call with no arguments, which is likely an observable + return true; + } + return false; +} + +const reactiveFunctions = new Set([ + 'derived', + 'derivedDisposable', + 'derivedHandleChanges', + 'derivedOpts', + 'derivedWithSetter', + 'derivedWithStore', + 'autorun', + 'autorunOpts', + 'autorunHandleChanges', + 'autorunSelfDisposable', + 'autorunDelta', + 'autorunWithStore', + 'autorunWithStoreHandleChanges', + 'autorunIterableDelta' +]); + +function getReaderParameterName(fn: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression): string | null { + if (fn.params.length === 0) { + return null; + } + const firstParam = fn.params[0]; + if (firstParam.type === 'Identifier') { + // Accept any parameter name as a potential reader parameter + // since reactive functions should always have the reader as the first parameter + return firstParam.name; + } + return null; +} + +function isReactiveFunctionWithReader(callee: TSESTree.Node): boolean { + if (callee.type === 'Identifier') { + return reactiveFunctions.has(callee.name); + } + return false; +} + +function walkChildren(node: TSESTree.Node, cb: (child: TSESTree.Node) => void) { + const keys = visitorKeys.KEYS[node.type] || []; + for (const key of keys) { + const child = (node as Record)[key]; + if (Array.isArray(child)) { + for (const item of child) { + if (item && typeof item === 'object' && item.type) { + cb(item); + } + } + } else if (child && typeof child === 'object' && child.type) { + cb(child); + } + } +} diff --git a/.eslint-plugin-local/code-no-potentially-unsafe-disposables.ts b/.eslint-plugin-local/code-no-potentially-unsafe-disposables.ts index 699762750519c..bc250df11824f 100644 --- a/.eslint-plugin-local/code-no-potentially-unsafe-disposables.ts +++ b/.eslint-plugin-local/code-no-potentially-unsafe-disposables.ts @@ -4,23 +4,24 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; /** * Checks for potentially unsafe usage of `DisposableStore` / `MutableDisposable`. * * These have been the source of leaks in the past. */ -export = new class implements eslint.Rule.RuleModule { +export default new class implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - function checkVariableDeclaration(inNode: any) { + function checkVariableDeclaration(inNode: ESTree.Node) { context.report({ node: inNode, message: `Use const for 'DisposableStore' to avoid leaks by accidental reassignment.` }); } - function checkProperty(inNode: any) { + function checkProperty(inNode: ESTree.Node) { context.report({ node: inNode, message: `Use readonly for DisposableStore/MutableDisposable to avoid leaks through accidental reassignment.` @@ -28,10 +29,10 @@ export = new class implements eslint.Rule.RuleModule { } return { - 'VariableDeclaration[kind!="const"] NewExpression[callee.name="DisposableStore"]': checkVariableDeclaration, + 'VariableDeclaration[kind!="const"] > VariableDeclarator > NewExpression[callee.name="DisposableStore"]': checkVariableDeclaration, 'PropertyDefinition[readonly!=true][typeAnnotation.typeAnnotation.typeName.name=/DisposableStore|MutableDisposable/]': checkProperty, - 'PropertyDefinition[readonly!=true] NewExpression[callee.name=/DisposableStore|MutableDisposable/]': checkProperty, + 'PropertyDefinition[readonly!=true] > NewExpression[callee.name=/DisposableStore|MutableDisposable/]': checkProperty, }; } }; diff --git a/.eslint-plugin-local/code-no-reader-after-await.ts b/.eslint-plugin-local/code-no-reader-after-await.ts new file mode 100644 index 0000000000000..6d0e8d39b0614 --- /dev/null +++ b/.eslint-plugin-local/code-no-reader-after-await.ts @@ -0,0 +1,168 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { TSESTree } from '@typescript-eslint/utils'; +import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; + +export default new class NoReaderAfterAwait implements eslint.Rule.RuleModule { + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + return { + 'CallExpression': (node: ESTree.CallExpression) => { + const callExpression = node as TSESTree.CallExpression; + + if (!isFunctionWithReader(callExpression.callee)) { + return; + } + + const functionArg = callExpression.arguments.find(arg => + arg.type === 'ArrowFunctionExpression' || arg.type === 'FunctionExpression' + ) as TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression | undefined; + + if (!functionArg) { + return; + } + + const readerName = getReaderParameterName(functionArg); + if (!readerName) { + return; + } + + checkFunctionForAwaitBeforeReader(functionArg, readerName, context); + } + }; + } +}; + +function checkFunctionForAwaitBeforeReader( + fn: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, + readerName: string, + context: eslint.Rule.RuleContext +) { + const awaitPositions: { line: number; column: number }[] = []; + const visited = new Set(); + + function collectPositions(node: TSESTree.Node) { + if (visited.has(node)) { + return; + } + visited.add(node); + + if (node.type === 'AwaitExpression') { + awaitPositions.push({ + line: node.loc?.start.line || 0, + column: node.loc?.start.column || 0 + }); + } else if (node.type === 'CallExpression' && isReaderMethodCall(node, readerName)) { + if (awaitPositions.length > 0) { + const methodName = getMethodName(node); + context.report({ + node: node, + message: `Reader method '${methodName}' should not be called after 'await'. The reader becomes invalid after async operations.` + }); + } + } + + // Safely traverse known node types only + switch (node.type) { + case 'BlockStatement': + node.body.forEach(stmt => collectPositions(stmt)); + break; + case 'ExpressionStatement': + collectPositions(node.expression); + break; + case 'VariableDeclaration': + node.declarations.forEach(decl => { + if (decl.init) { collectPositions(decl.init); } + }); + break; + case 'AwaitExpression': + if (node.argument) { collectPositions(node.argument); } + break; + case 'CallExpression': + node.arguments.forEach(arg => collectPositions(arg)); + break; + case 'IfStatement': + collectPositions(node.test); + collectPositions(node.consequent); + if (node.alternate) { collectPositions(node.alternate); } + break; + case 'TryStatement': + collectPositions(node.block); + if (node.handler) { collectPositions(node.handler.body); } + if (node.finalizer) { collectPositions(node.finalizer); } + break; + case 'ReturnStatement': + if (node.argument) { collectPositions(node.argument); } + break; + case 'BinaryExpression': + case 'LogicalExpression': + collectPositions(node.left); + collectPositions(node.right); + break; + case 'MemberExpression': + collectPositions(node.object); + if (node.computed) { collectPositions(node.property); } + break; + case 'AssignmentExpression': + collectPositions(node.left); + collectPositions(node.right); + break; + } + } + + if (fn.body) { + collectPositions(fn.body); + } +} + +function getMethodName(callExpression: TSESTree.CallExpression): string { + if (callExpression.callee.type === 'MemberExpression' && + callExpression.callee.property.type === 'Identifier') { + return callExpression.callee.property.name; + } + return 'read'; +} + +function isReaderMethodCall(node: TSESTree.CallExpression, readerName: string): boolean { + if (node.callee.type === 'MemberExpression') { + // Pattern 1: reader.read() or reader.readObservable() + if (node.callee.object.type === 'Identifier' && + node.callee.object.name === readerName && + node.callee.property.type === 'Identifier') { + return ['read', 'readObservable'].includes(node.callee.property.name); + } + + // Pattern 2: observable.read(reader) or observable.readObservable(reader) + if (node.callee.property.type === 'Identifier' && + ['read', 'readObservable'].includes(node.callee.property.name)) { + // Check if the reader is passed as the first argument + return node.arguments.length > 0 && + node.arguments[0].type === 'Identifier' && + node.arguments[0].name === readerName; + } + } + return false; +} + +const readerFunctions = new Set(['derived', 'autorun', 'autorunOpts', 'autorunHandleChanges', 'autorunSelfDisposable']); + +function getReaderParameterName(fn: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression): string | null { + if (fn.params.length === 0) { + return null; + } + const firstParam = fn.params[0]; + if (firstParam.type === 'Identifier') { + return firstParam.name; + } + return null; +} + +function isFunctionWithReader(callee: TSESTree.Node): boolean { + if (callee.type === 'Identifier') { + return readerFunctions.has(callee.name); + } + return false; +} diff --git a/.eslint-plugin-local/code-no-runtime-import.ts b/.eslint-plugin-local/code-no-runtime-import.ts index afebe0b0d68b9..2c53d84f973cc 100644 --- a/.eslint-plugin-local/code-no-runtime-import.ts +++ b/.eslint-plugin-local/code-no-runtime-import.ts @@ -7,9 +7,9 @@ import { TSESTree } from '@typescript-eslint/typescript-estree'; import * as eslint from 'eslint'; import { dirname, join, relative } from 'path'; import minimatch from 'minimatch'; -import { createImportRuleListener } from './utils'; +import { createImportRuleListener } from './utils.ts'; -export = new class implements eslint.Rule.RuleModule { +export default new class implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { @@ -30,11 +30,11 @@ export = new class implements eslint.Rule.RuleModule { }; create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - let fileRelativePath = relative(dirname(__dirname), context.getFilename()); + let fileRelativePath = relative(dirname(import.meta.dirname), context.getFilename()); if (!fileRelativePath.endsWith('/')) { fileRelativePath += '/'; } - const ruleArgs = >context.options[0]; + const ruleArgs = context.options[0] as Record; const matchingKey = Object.keys(ruleArgs).find(key => fileRelativePath.startsWith(key) || minimatch(fileRelativePath, key)); if (!matchingKey) { diff --git a/.eslint-plugin-local/code-no-standalone-editor.ts b/.eslint-plugin-local/code-no-standalone-editor.ts index 36bf48b141734..dca4e22bfb058 100644 --- a/.eslint-plugin-local/code-no-standalone-editor.ts +++ b/.eslint-plugin-local/code-no-standalone-editor.ts @@ -5,9 +5,9 @@ import * as eslint from 'eslint'; import { join } from 'path'; -import { createImportRuleListener } from './utils'; +import { createImportRuleListener } from './utils.ts'; -export = new class NoNlsInStandaloneEditorRule implements eslint.Rule.RuleModule { +export default new class NoNlsInStandaloneEditorRule implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { diff --git a/.eslint-plugin-local/code-no-static-self-ref.ts b/.eslint-plugin-local/code-no-static-self-ref.ts index f620645565ad4..9a47f87b9c186 100644 --- a/.eslint-plugin-local/code-no-static-self-ref.ts +++ b/.eslint-plugin-local/code-no-static-self-ref.ts @@ -4,19 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; /** * WORKAROUND for https://github.com/evanw/esbuild/issues/3823 */ -export = new class implements eslint.Rule.RuleModule { +export default new class implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - function checkProperty(inNode: any) { + function checkProperty(inNode: TSESTree.PropertyDefinition) { - const classDeclaration = context.sourceCode.getAncestors(inNode).find(node => node.type === 'ClassDeclaration'); - const propertyDefinition = inNode; + const classDeclaration = context.sourceCode.getAncestors(inNode as ESTree.Node).find(node => node.type === 'ClassDeclaration'); + const propertyDefinition = inNode; if (!classDeclaration || !classDeclaration.id?.name) { return; @@ -28,15 +29,14 @@ export = new class implements eslint.Rule.RuleModule { const classCtor = classDeclaration.body.body.find(node => node.type === 'MethodDefinition' && node.kind === 'constructor'); - if (!classCtor) { + if (!classCtor || classCtor.type === 'StaticBlock') { return; } const name = classDeclaration.id.name; - const valueText = context.sourceCode.getText(propertyDefinition.value); + const valueText = context.sourceCode.getText(propertyDefinition.value as ESTree.Node); if (valueText.includes(name + '.')) { - if (classCtor.value?.type === 'FunctionExpression' && !classCtor.value.params.find((param: any) => param.type === 'TSParameterProperty' && param.decorators?.length > 0)) { return; } diff --git a/.eslint-plugin-local/code-no-test-async-suite.ts b/.eslint-plugin-local/code-no-test-async-suite.ts index 7d5fadfad0dd7..b53747390b066 100644 --- a/.eslint-plugin-local/code-no-test-async-suite.ts +++ b/.eslint-plugin-local/code-no-test-async-suite.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TSESTree } from '@typescript-eslint/utils'; import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; +import { TSESTree } from '@typescript-eslint/utils'; function isCallExpression(node: TSESTree.Node): node is TSESTree.CallExpression { return node.type === 'CallExpression'; @@ -14,13 +15,14 @@ function isFunctionExpression(node: TSESTree.Node): node is TSESTree.FunctionExp return node.type.includes('FunctionExpression'); } -export = new class NoAsyncSuite implements eslint.Rule.RuleModule { +export default new class NoAsyncSuite implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - function hasAsyncSuite(node: any) { - if (isCallExpression(node) && node.arguments.length >= 2 && isFunctionExpression(node.arguments[1]) && node.arguments[1].async) { + function hasAsyncSuite(node: ESTree.Node) { + const tsNode = node as TSESTree.Node; + if (isCallExpression(tsNode) && tsNode.arguments.length >= 2 && isFunctionExpression(tsNode.arguments[1]) && tsNode.arguments[1].async) { return context.report({ - node: node as any, + node: tsNode, message: 'suite factory function should never be async' }); } diff --git a/.eslint-plugin-local/code-no-test-only.ts b/.eslint-plugin-local/code-no-test-only.ts index d4751eef2ee77..389d32fe13ba3 100644 --- a/.eslint-plugin-local/code-no-test-only.ts +++ b/.eslint-plugin-local/code-no-test-only.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; -export = new class NoTestOnly implements eslint.Rule.RuleModule { +export default new class NoTestOnly implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { return { - ['MemberExpression[object.name=/^(test|suite)$/][property.name="only"]']: (node: any) => { + ['MemberExpression[object.name=/^(test|suite)$/][property.name="only"]']: (node: ESTree.MemberExpression) => { return context.report({ node, message: 'only is a dev-time tool and CANNOT be pushed' diff --git a/.eslint-plugin-local/code-no-unexternalized-strings.ts b/.eslint-plugin-local/code-no-unexternalized-strings.ts index abb3980eb54a2..a7065cb2a0db9 100644 --- a/.eslint-plugin-local/code-no-unexternalized-strings.ts +++ b/.eslint-plugin-local/code-no-unexternalized-strings.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; import * as eslint from 'eslint'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/utils'; +import type * as ESTree from 'estree'; -function isStringLiteral(node: TSESTree.Node | null | undefined): node is TSESTree.StringLiteral { +function isStringLiteral(node: TSESTree.Node | ESTree.Node | null | undefined): node is TSESTree.StringLiteral { return !!node && node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string'; } @@ -14,7 +15,16 @@ function isDoubleQuoted(node: TSESTree.StringLiteral): boolean { return node.raw[0] === '"' && node.raw[node.raw.length - 1] === '"'; } -export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { +/** + * Enable bulk fixing double-quoted strings to single-quoted strings with the --fix eslint flag + * + * Disabled by default as this is often not the desired fix. Instead the string should be localized. However it is + * useful for bulk conversations of existing code. + */ +const enableDoubleToSingleQuoteFixes = false; + + +export default new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { private static _rNlsKeys = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/; @@ -26,6 +36,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { badMessage: 'Message argument to \'{{message}}\' must be a string literal.' }, schema: false, + fixable: enableDoubleToSingleQuoteFixes ? 'code' : undefined, }; create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { @@ -33,7 +44,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { const externalizedStringLiterals = new Map(); const doubleQuotedStringLiterals = new Set(); - function collectDoubleQuotedStrings(node: TSESTree.Literal) { + function collectDoubleQuotedStrings(node: ESTree.Literal) { if (isStringLiteral(node) && isDoubleQuoted(node)) { doubleQuotedStringLiterals.add(node); } @@ -42,7 +53,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { function visitLocalizeCall(node: TSESTree.CallExpression) { // localize(key, message) - const [keyNode, messageNode] = (node).arguments; + const [keyNode, messageNode] = node.arguments; // (1) // extract key so that it can be checked later @@ -81,7 +92,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { context.report({ loc: messageNode.loc, messageId: 'badMessage', - data: { message: context.getSourceCode().getText(node) } + data: { message: context.getSourceCode().getText(node as ESTree.Node) } }); } } @@ -89,9 +100,7 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { function visitL10NCall(node: TSESTree.CallExpression) { // localize(key, message) - const [messageNode] = (node).arguments; - - // remove message-argument from doubleQuoted list and make + const [messageNode] = (node as TSESTree.CallExpression).arguments; // remove message-argument from doubleQuoted list and make // sure it is a string-literal if (isStringLiteral(messageNode)) { doubleQuotedStringLiterals.delete(messageNode); @@ -111,7 +120,30 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { // (1) // report all strings that are in double quotes for (const node of doubleQuotedStringLiterals) { - context.report({ loc: node.loc, messageId: 'doubleQuoted' }); + context.report({ + loc: node.loc, + messageId: 'doubleQuoted', + fix: enableDoubleToSingleQuoteFixes ? (fixer) => { + // Get the raw string content, unescaping any escaped quotes + const content = (node as ESTree.SimpleLiteral).raw! + .slice(1, -1) + .replace(/(? 1) { for (let i = 1; i < values.length; i++) { - if (context.getSourceCode().getText(values[i - 1].message) !== context.getSourceCode().getText(values[i].message)) { + if (context.getSourceCode().getText(values[i - 1].message as ESTree.Node) !== context.getSourceCode().getText(values[i].message as ESTree.Node)) { context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } }); } } @@ -137,23 +169,23 @@ export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { } return { - ['Literal']: (node: any) => collectDoubleQuotedStrings(node), - ['ExpressionStatement[directive] Literal:exit']: (node: any) => doubleQuotedStringLiterals.delete(node), + ['Literal']: (node: ESTree.Literal) => collectDoubleQuotedStrings(node), + ['ExpressionStatement[directive] Literal:exit']: (node: TSESTree.Literal) => doubleQuotedStringLiterals.delete(node), // localize(...) - ['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize"]:exit']: (node: any) => visitLocalizeCall(node), + ['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize"]:exit']: (node: TSESTree.CallExpression) => visitLocalizeCall(node), // localize2(...) - ['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize2"]:exit']: (node: any) => visitLocalizeCall(node), + ['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize2"]:exit']: (node: TSESTree.CallExpression) => visitLocalizeCall(node), // vscode.l10n.t(...) - ['CallExpression[callee.type="MemberExpression"][callee.object.property.name="l10n"][callee.property.name="t"]:exit']: (node: any) => visitL10NCall(node), + ['CallExpression[callee.type="MemberExpression"][callee.object.property.name="l10n"][callee.property.name="t"]:exit']: (node: TSESTree.CallExpression) => visitL10NCall(node), // l10n.t(...) - ['CallExpression[callee.object.name="l10n"][callee.property.name="t"]:exit']: (node: any) => visitL10NCall(node), + ['CallExpression[callee.object.name="l10n"][callee.property.name="t"]:exit']: (node: TSESTree.CallExpression) => visitL10NCall(node), - ['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node: any) => visitLocalizeCall(node), - ['CallExpression[callee.name="localize2"][arguments.length>=2]:exit']: (node: any) => visitLocalizeCall(node), + ['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node: TSESTree.CallExpression) => visitLocalizeCall(node), + ['CallExpression[callee.name="localize2"][arguments.length>=2]:exit']: (node: TSESTree.CallExpression) => visitLocalizeCall(node), ['Program:exit']: reportBadStringsAndBadKeys, }; } diff --git a/.eslint-plugin-local/code-no-unused-expressions.ts b/.eslint-plugin-local/code-no-unused-expressions.ts index bd632884dbd60..c481313a9a21c 100644 --- a/.eslint-plugin-local/code-no-unused-expressions.ts +++ b/.eslint-plugin-local/code-no-unused-expressions.ts @@ -11,15 +11,15 @@ * @author Michael Ficarra */ -import * as eslint from 'eslint'; import { TSESTree } from '@typescript-eslint/utils'; -import * as ESTree from 'estree'; +import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = { +export default { meta: { type: 'suggestion', @@ -58,7 +58,7 @@ module.exports = { allowTernary = config.allowTernary || false, allowTaggedTemplates = config.allowTaggedTemplates || false; - + /** * @param node any node * @returns whether the given node structurally represents a directive @@ -68,7 +68,7 @@ module.exports = { node.expression.type === 'Literal' && typeof node.expression.value === 'string'; } - + /** * @param predicate ([a] -> Boolean) the function used to make the determination * @param list the input list @@ -83,7 +83,7 @@ module.exports = { return list.slice(); } - + /** * @param node a Program or BlockStatement node * @returns the leading sequence of directive nodes in the given node's body @@ -92,7 +92,7 @@ module.exports = { return takeWhile(looksLikeDirective, node.body); } - + /** * @param node any node * @param ancestors the given node's ancestors @@ -141,8 +141,8 @@ module.exports = { return { ExpressionStatement(node: TSESTree.ExpressionStatement) { - if (!isValidExpression(node.expression) && !isDirective(node, context.sourceCode.getAncestors(node))) { - context.report({ node: node, message: `Expected an assignment or function call and instead saw an expression. ${node.expression}` }); + if (!isValidExpression(node.expression) && !isDirective(node, context.sourceCode.getAncestors(node as ESTree.Node) as TSESTree.Node[])) { + context.report({ node: node as ESTree.Node, message: `Expected an assignment or function call and instead saw an expression. ${node.expression}` }); } } }; diff --git a/.eslint-plugin-local/code-parameter-properties-must-have-explicit-accessibility.ts b/.eslint-plugin-local/code-parameter-properties-must-have-explicit-accessibility.ts index c9837052fa52c..f00d3e1435c6c 100644 --- a/.eslint-plugin-local/code-parameter-properties-must-have-explicit-accessibility.ts +++ b/.eslint-plugin-local/code-parameter-properties-must-have-explicit-accessibility.ts @@ -3,19 +3,18 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as eslint from 'eslint'; import { TSESTree } from '@typescript-eslint/utils'; +import * as eslint from 'eslint'; /** * Enforces that all parameter properties have an explicit access modifier (public, protected, private). * * This catches a common bug where a service is accidentally made public by simply writing: `readonly prop: Foo` */ -export = new class implements eslint.Rule.RuleModule { +export default new class implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - function check(inNode: any) { - const node: TSESTree.TSParameterProperty = inNode; + function check(node: TSESTree.TSParameterProperty) { // For now, only apply to injected services const firstDecorator = node.decorators?.at(0); @@ -28,7 +27,7 @@ export = new class implements eslint.Rule.RuleModule { if (!node.accessibility) { context.report({ - node: inNode, + node: node, message: 'Parameter properties must have an explicit access modifier.' }); } diff --git a/.eslint-plugin-local/code-policy-localization-key-match.ts b/.eslint-plugin-local/code-policy-localization-key-match.ts new file mode 100644 index 0000000000000..10749d5bb006b --- /dev/null +++ b/.eslint-plugin-local/code-policy-localization-key-match.ts @@ -0,0 +1,146 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; + +/** + * Ensures that localization keys in policy blocks match the keys used in nls.localize() calls. + * + * For example, in a policy block with: + * ``` + * localization: { + * description: { + * key: 'autoApprove2.description', + * value: nls.localize('autoApprove2.description', '...') + * } + * } + * ``` + * + * The key property ('autoApprove2.description') must match the first argument + * to nls.localize() ('autoApprove2.description'). + */ +export default new class PolicyLocalizationKeyMatch implements eslint.Rule.RuleModule { + + readonly meta: eslint.Rule.RuleMetaData = { + messages: { + mismatch: 'Localization key "{{keyValue}}" does not match the key used in nls.localize("{{localizeKey}}", ...). They must be identical.' + }, + docs: { + description: 'Ensures that localization keys in policy blocks match the keys used in nls.localize() calls', + }, + schema: false, + }; + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + + function checkLocalizationObject(node: ESTree.ObjectExpression) { + // Look for objects with structure: { key: '...', value: nls.localize('...', '...') } + + let keyProperty: ESTree.Property | undefined; + let valueProperty: ESTree.Property | undefined; + + for (const property of node.properties) { + if (property.type !== 'Property') { + continue; + } + + const propertyKey = property.key; + if (propertyKey.type === 'Identifier') { + if (propertyKey.name === 'key') { + keyProperty = property; + } else if (propertyKey.name === 'value') { + valueProperty = property; + } + } + } + + if (!keyProperty || !valueProperty) { + return; + } + + // Extract the key value (should be a string literal) + let keyValue: string | undefined; + if (keyProperty.value.type === 'Literal' && typeof keyProperty.value.value === 'string') { + keyValue = keyProperty.value.value; + } + + if (!keyValue) { + return; + } + + // Check if value is a call to localize or any namespace's localize method + if (valueProperty.value.type === 'CallExpression') { + const callee = valueProperty.value.callee; + + // Check if it's .localize or just localize + let isLocalizeCall = false; + if (callee.type === 'MemberExpression') { + const object = callee.object; + const property = callee.property; + if (object.type === 'Identifier' && + property.type === 'Identifier' && property.name === 'localize') { + isLocalizeCall = true; + } + } else if (callee.type === 'Identifier' && callee.name === 'localize') { + // Direct localize() call + isLocalizeCall = true; + } + + if (isLocalizeCall) { + // Get the first argument to localize (the key) + const args = valueProperty.value.arguments; + if (args.length > 0) { + const firstArg = args[0]; + if (firstArg.type === 'Literal' && typeof firstArg.value === 'string') { + const localizeKey = firstArg.value; + + // Compare the keys + if (keyValue !== localizeKey) { + context.report({ + node: keyProperty.value, + messageId: 'mismatch', + data: { + keyValue, + localizeKey + } + }); + } + } + } + } + } + } + + function isInPolicyBlock(node: ESTree.Node): boolean { + // Walk up the AST to see if we're inside a policy object + const ancestors = context.sourceCode.getAncestors(node); + + for (const ancestor of ancestors) { + if (ancestor.type === 'Property') { + // eslint-disable-next-line local/code-no-any-casts + const property = ancestor as any; + if (property.key && property.key.type === 'Identifier' && property.key.name === 'policy') { + return true; + } + } + } + + return false; + } + + return { + 'ObjectExpression': (node: ESTree.ObjectExpression) => { + // Only check objects inside policy blocks + if (!isInPolicyBlock(node)) { + return; + } + + // Check if this object has the pattern we're looking for + checkLocalizationObject(node); + } + }; + } +}; diff --git a/.eslint-plugin-local/code-translation-remind.ts b/.eslint-plugin-local/code-translation-remind.ts index cceaba4c419bd..4203232116710 100644 --- a/.eslint-plugin-local/code-translation-remind.ts +++ b/.eslint-plugin-local/code-translation-remind.ts @@ -6,10 +6,10 @@ import * as eslint from 'eslint'; import { TSESTree } from '@typescript-eslint/utils'; import { readFileSync } from 'fs'; -import { createImportRuleListener } from './utils'; +import { createImportRuleListener } from './utils.ts'; -export = new class TranslationRemind implements eslint.Rule.RuleModule { +export default new class TranslationRemind implements eslint.Rule.RuleModule { private static NLS_MODULE = 'vs/nls'; diff --git a/.eslint-plugin-local/index.js b/.eslint-plugin-local/index.js deleted file mode 100644 index 3646c8c41571c..0000000000000 --- a/.eslint-plugin-local/index.js +++ /dev/null @@ -1,18 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -const glob = require('glob'); -const path = require('path'); - -require('ts-node').register({ experimentalResolver: true, transpileOnly: true }); - -// Re-export all .ts files as rules -/** @type {Record} */ -const rules = {}; -glob.sync(`${__dirname}/*.ts`).forEach((file) => { - rules[path.basename(file, '.ts')] = require(file); -}); - -exports.rules = rules; diff --git a/.eslint-plugin-local/index.ts b/.eslint-plugin-local/index.ts new file mode 100644 index 0000000000000..101733773f01a --- /dev/null +++ b/.eslint-plugin-local/index.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import type { LooseRuleDefinition } from '@typescript-eslint/utils/ts-eslint'; +import glob from 'glob'; +import { createRequire } from 'module'; +import path from 'path'; + +const require = createRequire(import.meta.url); + +// Re-export all .ts files as rules +const rules: Record = {}; +glob.sync(`${import.meta.dirname}/*.ts`) + .filter(file => !file.endsWith('index.ts') && !file.endsWith('utils.ts')) + .map(file => { + rules[path.basename(file, '.ts')] = require(file).default; + }); + +export { rules }; diff --git a/.eslint-plugin-local/package.json b/.eslint-plugin-local/package.json index a0df0c867783a..90e7facf0a06a 100644 --- a/.eslint-plugin-local/package.json +++ b/.eslint-plugin-local/package.json @@ -1,3 +1,7 @@ { - "type": "commonjs" + "private": true, + "type": "module", + "scripts": { + "typecheck": "tsgo -p tsconfig.json --noEmit" + } } diff --git a/.eslint-plugin-local/tests/code-no-observable-get-in-reactive-context-test.ts b/.eslint-plugin-local/tests/code-no-observable-get-in-reactive-context-test.ts new file mode 100644 index 0000000000000..fd92a45e22bcf --- /dev/null +++ b/.eslint-plugin-local/tests/code-no-observable-get-in-reactive-context-test.ts @@ -0,0 +1,202 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Test file to verify the code-no-observable-get-in-reactive-context ESLint rule works correctly + +import { observableValue, derived, autorun } from '../../src/vs/base/common/observable.js'; + +export function testValidUsage() { + const obs = observableValue('test', 0); + + // Valid: Using .read(reader) in derived + const validDerived = derived(reader => { + const value = obs.read(reader); + return value * 2; + }); + + // Valid: Using .read(reader) in autorun + autorun(rdr => { + const value = validDerived.read(rdr); + console.log('Value:', value); + }); + + // Valid: Using .get() outside reactive context + const outsideValue = obs.get(); + console.log('Outside value:', outsideValue); +} + +export function testInvalidUsage() { + const obs = observableValue('test', 0); + + // Invalid: Using .get() in derived instead of .read(reader) + const invalidDerived = derived(rdr => { + // This should use obs.read(reader) instead + // eslint-disable-next-line local/code-no-observable-get-in-reactive-context + const value = obs.get(); + // Use reader for something valid to avoid unused var warning + const validValue = obs.read(rdr); + + obs.read(undefined); + + return value * 2 + validValue; + }); + + // Invalid: Using .get() in autorun instead of .read(reader) + autorun(reader => { + // This should use invalidDerived.read(reader) instead + // eslint-disable-next-line local/code-no-observable-get-in-reactive-context + const value = invalidDerived.get(); + // Use reader for something valid to avoid unused var warning + const validValue = obs.read(reader); + console.log('Value:', value, validValue); + }); + + // Invalid: Using .get() in derivedWithStore + derived(reader => { + // eslint-disable-next-line local/code-no-observable-get-in-reactive-context + const value = obs.get(); + reader.store.add({ dispose: () => { } }); + return value; + }); +} + +export function testComplexCases() { + const obs1 = observableValue('test1', 0); + const obs2 = observableValue('test2', 10); + + // Invalid: Using .get() in conditional within derived + derived(reader => { + const initial = obs1.read(reader); + + if (initial > 0) { + // eslint-disable-next-line local/code-no-observable-get-in-reactive-context + return obs2.get(); + } + + return initial; + }); + + // Invalid: Using .get() in nested function call within autorun + autorun(reader => { + const process = () => { + // eslint-disable-next-line local/code-no-observable-get-in-reactive-context + return obs1.get() + obs2.get(); + }; + + // Use reader for something valid to avoid unused var warning + const validValue = obs1.read(reader); + const result = process(); + console.log('Result:', result, validValue); + }); + + // Invalid: Using .get() in try-catch within derived + derived(reader => { + try { + // eslint-disable-next-line local/code-no-observable-get-in-reactive-context + const value = obs1.get(); + // Use reader for something valid to avoid unused var warning + const validValue = obs2.read(reader); + return value * 2 + validValue; + } catch (e) { + return obs2.read(reader); + } + }); +} + +export function testValidComplexCases() { + const obs1 = observableValue('test1', 0); + const obs2 = observableValue('test2', 10); + + // Valid: Proper usage with .read(reader) + derived(reader => { + const value1 = obs1.read(reader); + const value2 = obs2.read(undefined); + + if (value1 > 0) { + return value2; + } + + return value1; + }); + + // Valid: Using .get() outside reactive context + function processValues() { + const val1 = obs1.get(); + const val2 = obs2.get(); + return val1 + val2; + } + + // Valid: Mixed usage - .read(reader) inside reactive, .get() outside + autorun(reader => { + const reactiveValue = obs1.read(reader); + const outsideValue = processValues(); + console.log('Values:', reactiveValue, outsideValue); + }); +} + +export function testEdgeCases() { + const obs = observableValue('test', 0); + + // Valid: Function with no reader parameter + derived(() => { + const value = obs.get(); + return value; + }); + + // Invalid: Function with differently named parameter (now also flagged) + derived(_someOtherName => { + // eslint-disable-next-line local/code-no-observable-get-in-reactive-context + const value = obs.get(); + return value; + }); + + // Invalid: Correctly named reader parameter + derived(reader => { + // eslint-disable-next-line local/code-no-observable-get-in-reactive-context + const value = obs.get(); + // Use reader for something valid to avoid unused var warning + const validValue = obs.read(reader); + return value + validValue; + }); +} + +export function testQuickFixScenarios() { + const obs = observableValue('test', 0); + const obs2 = observableValue('test2', 10); + + // These examples show what the quick fix should transform: + + // Example 1: Simple case with 'reader' parameter name + derived(_reader => { + const value = obs.read(undefined); // This should be the auto-fix result + return value; + }); + + // Example 2: Different parameter name + derived(rdr => { + // Before fix: obs2.get() + // After fix: obs2.read(rdr) + const value = obs2.read(rdr); // This should be the auto-fix result + return value; + }); + + // Example 3: Complex expression + derived(ctx => { + // Before fix: (someCondition ? obs : obs2).get() + // After fix: (someCondition ? obs : obs2).read(ctx) + const someCondition = true; + const value = (someCondition ? obs : obs2).read(ctx); // This should be the auto-fix result + return value; + }); + + // Example 4: Multiple calls in same function + autorun(reader => { + // Before fix: obs.get() and obs2.get() + // After fix: obs.read(reader) and obs2.read(reader) + const val1 = obs.read(reader); // This should be the auto-fix result + const val2 = obs2.read(reader); // This should be the auto-fix result + console.log(val1, val2); + }); +} diff --git a/.eslint-plugin-local/tests/code-no-reader-after-await-test.ts b/.eslint-plugin-local/tests/code-no-reader-after-await-test.ts new file mode 100644 index 0000000000000..2dc0f457a7fef --- /dev/null +++ b/.eslint-plugin-local/tests/code-no-reader-after-await-test.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Test file to verify the code-no-reader-after-await ESLint rule works correctly + +import { observableValue, derived, autorun } from '../../src/vs/base/common/observable.js'; + +export function testValidUsage() { + const obs = observableValue('test', 0); + + const validDerived = derived(reader => { + const value = obs.read(reader); + return value * 2; + }); + + autorun(reader => { + const value = validDerived.read(reader); + console.log('Value:', value); + }); +} + +export function testInvalidUsage() { + const obs = observableValue('test', 0); + + const invalidDerived = derived(async reader => { + await Promise.resolve(); + // eslint-disable-next-line local/code-no-reader-after-await + const value = obs.read(reader); + return value * 2; + }); + + autorun(async reader => { + await Promise.resolve(); + // eslint-disable-next-line local/code-no-reader-after-await + const value = invalidDerived.read(reader); + console.log('Value:', value); + }); + + autorun(async reader => { + await Promise.resolve(); + // eslint-disable-next-line local/code-no-reader-after-await + const value = reader.readObservable(obs); + console.log('Value:', value); + }); +} + +export function testComplexCases() { + const obs = observableValue('test', 0); + + derived(async reader => { + const initial = obs.read(reader); + + if (initial > 0) { + await Promise.resolve(); + } + + // eslint-disable-next-line local/code-no-reader-after-await + const final = obs.read(reader); + return final; + }); + + autorun(async reader => { + try { + await Promise.resolve(); + } catch (e) { + } finally { + // eslint-disable-next-line local/code-no-reader-after-await + const value = obs.read(reader); + console.log(value); + } + }); +} + +export function testValidComplexCases() { + const obs = observableValue('test', 0); + + derived(async reader => { + const value1 = obs.read(reader); + const value2 = reader.readObservable(obs); + const result = value1 + value2; + await Promise.resolve(result); + return result; + }); +} diff --git a/.eslint-plugin-local/tsconfig.json b/.eslint-plugin-local/tsconfig.json index 7676f59a78166..0de6dacc14643 100644 --- a/.eslint-plugin-local/tsconfig.json +++ b/.eslint-plugin-local/tsconfig.json @@ -4,23 +4,26 @@ "lib": [ "ES2024" ], - "module": "commonjs", - "esModuleInterop": true, - "alwaysStrict": true, - "allowJs": true, + "rootDir": ".", + "module": "esnext", + "allowImportingTsExtensions": true, + "erasableSyntaxOnly": true, + "verbatimModuleSyntax": true, + "noEmit": true, "strict": true, "exactOptionalPropertyTypes": false, "useUnknownInCatchVariables": false, "noUnusedLocals": true, "noUnusedParameters": true, - "newLine": "lf", - "noEmit": true + "typeRoots": [ + "." + ] }, "include": [ - "**/*.ts", - "**/*.js" + "./**/*.ts", ], "exclude": [ - "node_modules/**" + "node_modules/**", + "./tests/**" ] } diff --git a/.eslint-plugin-local/utils.ts b/.eslint-plugin-local/utils.ts index b7457884f8517..e956e67914896 100644 --- a/.eslint-plugin-local/utils.ts +++ b/.eslint-plugin-local/utils.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; export function createImportRuleListener(validateImport: (node: TSESTree.Literal, value: string) => any): eslint.Rule.RuleListener { @@ -16,24 +17,24 @@ export function createImportRuleListener(validateImport: (node: TSESTree.Literal return { // import ??? from 'module' - ImportDeclaration: (node: any) => { - _checkImport((node).source); + ImportDeclaration: (node: ESTree.ImportDeclaration) => { + _checkImport((node as TSESTree.ImportDeclaration).source); }, // import('module').then(...) OR await import('module') - ['CallExpression[callee.type="Import"][arguments.length=1] > Literal']: (node: any) => { + ['CallExpression[callee.type="Import"][arguments.length=1] > Literal']: (node: TSESTree.Literal) => { _checkImport(node); }, // import foo = ... - ['TSImportEqualsDeclaration > TSExternalModuleReference > Literal']: (node: any) => { + ['TSImportEqualsDeclaration > TSExternalModuleReference > Literal']: (node: TSESTree.Literal) => { _checkImport(node); }, // export ?? from 'module' - ExportAllDeclaration: (node: any) => { - _checkImport((node).source); + ExportAllDeclaration: (node: ESTree.ExportAllDeclaration) => { + _checkImport((node as TSESTree.ExportAllDeclaration).source); }, // export {foo} from 'module' - ExportNamedDeclaration: (node: any) => { - _checkImport((node).source); + ExportNamedDeclaration: (node: ESTree.ExportNamedDeclaration) => { + _checkImport((node as TSESTree.ExportNamedDeclaration).source); }, }; diff --git a/.eslint-plugin-local/vscode-dts-cancellation.ts b/.eslint-plugin-local/vscode-dts-cancellation.ts index 5e8e875af212f..dd5ca293727bf 100644 --- a/.eslint-plugin-local/vscode-dts-cancellation.ts +++ b/.eslint-plugin-local/vscode-dts-cancellation.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as eslint from 'eslint'; import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; +import * as eslint from 'eslint'; -export = new class ApiProviderNaming implements eslint.Rule.RuleModule { +export default new class ApiProviderNaming implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { @@ -18,10 +18,10 @@ export = new class ApiProviderNaming implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { return { - ['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature[key.name=/^(provide|resolve).+/]']: (node: any) => { + ['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature[key.name=/^(provide|resolve).+/]']: (node: TSESTree.Node) => { let found = false; - for (const param of (node).params) { + for (const param of (node as TSESTree.TSMethodSignature).params) { if (param.type === AST_NODE_TYPES.Identifier) { found = found || param.name === 'token'; } diff --git a/.eslint-plugin-local/vscode-dts-create-func.ts b/.eslint-plugin-local/vscode-dts-create-func.ts index 01db244ce76e5..91589f91584c3 100644 --- a/.eslint-plugin-local/vscode-dts-create-func.ts +++ b/.eslint-plugin-local/vscode-dts-create-func.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/utils'; -export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule { +export default new class ApiLiteralOrTypes implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#creating-objects' }, @@ -17,9 +18,9 @@ export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { return { - ['TSDeclareFunction Identifier[name=/create.*/]']: (node: any) => { + ['TSDeclareFunction Identifier[name=/create.*/]']: (node: ESTree.Node) => { - const decl = (node).parent; + const decl = (node as TSESTree.Identifier).parent as TSESTree.FunctionDeclaration; if (decl.returnType?.typeAnnotation.type !== AST_NODE_TYPES.TSTypeReference) { return; diff --git a/.eslint-plugin-local/vscode-dts-event-naming.ts b/.eslint-plugin-local/vscode-dts-event-naming.ts index c27d934f4f989..6f75c50ca1267 100644 --- a/.eslint-plugin-local/vscode-dts-event-naming.ts +++ b/.eslint-plugin-local/vscode-dts-event-naming.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/utils'; -export = new class ApiEventNaming implements eslint.Rule.RuleModule { +export default new class ApiEventNaming implements eslint.Rule.RuleModule { private static _nameRegExp = /on(Did|Will)([A-Z][a-z]+)([A-Z][a-z]+)?/; @@ -25,14 +26,14 @@ export = new class ApiEventNaming implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - const config = <{ allowed: string[]; verbs: string[] }>context.options[0]; + const config = context.options[0] as { allowed: string[]; verbs: string[] }; const allowed = new Set(config.allowed); const verbs = new Set(config.verbs); return { - ['TSTypeAnnotation TSTypeReference Identifier[name="Event"]']: (node: any) => { + ['TSTypeAnnotation TSTypeReference Identifier[name="Event"]']: (node: ESTree.Identifier) => { - const def = (node).parent?.parent?.parent; + const def = (node as TSESTree.Identifier).parent?.parent?.parent; const ident = this.getIdent(def); if (!ident) { diff --git a/.eslint-plugin-local/vscode-dts-interface-naming.ts b/.eslint-plugin-local/vscode-dts-interface-naming.ts index 6b33f9c534335..d6591b97d8dcc 100644 --- a/.eslint-plugin-local/vscode-dts-interface-naming.ts +++ b/.eslint-plugin-local/vscode-dts-interface-naming.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; -export = new class ApiInterfaceNaming implements eslint.Rule.RuleModule { +export default new class ApiInterfaceNaming implements eslint.Rule.RuleModule { private static _nameRegExp = /^I[A-Z]/; @@ -20,9 +21,9 @@ export = new class ApiInterfaceNaming implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { return { - ['TSInterfaceDeclaration Identifier']: (node: any) => { + ['TSInterfaceDeclaration Identifier']: (node: ESTree.Identifier) => { - const name = (node).name; + const name = (node as TSESTree.Identifier).name; if (ApiInterfaceNaming._nameRegExp.test(name)) { context.report({ node, diff --git a/.eslint-plugin-local/vscode-dts-literal-or-types.ts b/.eslint-plugin-local/vscode-dts-literal-or-types.ts index 44ef0fd2a7cd5..0815720cf9283 100644 --- a/.eslint-plugin-local/vscode-dts-literal-or-types.ts +++ b/.eslint-plugin-local/vscode-dts-literal-or-types.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as eslint from 'eslint'; import { TSESTree } from '@typescript-eslint/utils'; +import * as eslint from 'eslint'; -export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule { +export default new class ApiLiteralOrTypes implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#enums' }, @@ -16,8 +16,8 @@ export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { return { - ['TSTypeAnnotation TSUnionType']: (node: any) => { - if ((node).types.every(value => value.type === 'TSLiteralType')) { + ['TSTypeAnnotation TSUnionType']: (node: TSESTree.TSUnionType) => { + if (node.types.every(value => value.type === 'TSLiteralType')) { context.report({ node: node, messageId: 'useEnum' diff --git a/.eslint-plugin-local/vscode-dts-provider-naming.ts b/.eslint-plugin-local/vscode-dts-provider-naming.ts index db8350dd9bc3a..64e0101a71e47 100644 --- a/.eslint-plugin-local/vscode-dts-provider-naming.ts +++ b/.eslint-plugin-local/vscode-dts-provider-naming.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as eslint from 'eslint'; import { TSESTree } from '@typescript-eslint/utils'; +import * as eslint from 'eslint'; -export = new class ApiProviderNaming implements eslint.Rule.RuleModule { +export default new class ApiProviderNaming implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { @@ -19,20 +19,18 @@ export = new class ApiProviderNaming implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - const config = <{ allowed: string[] }>context.options[0]; + const config = context.options[0] as { allowed: string[] }; const allowed = new Set(config.allowed); return { - ['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature']: (node: any) => { - - - const interfaceName = ((node).parent?.parent).id.name; + ['TSInterfaceDeclaration[id.name=/.+Provider/] TSMethodSignature']: (node: TSESTree.Node) => { + const interfaceName = ((node as TSESTree.Identifier).parent?.parent as TSESTree.TSInterfaceDeclaration).id.name; if (allowed.has(interfaceName)) { // allowed return; } - const methodName = ((node).key).name; + const methodName = ((node as TSESTree.TSMethodSignatureNonComputedName).key as TSESTree.Identifier).name; if (!ApiProviderNaming._providerFunctionNames.test(methodName)) { context.report({ diff --git a/.eslint-plugin-local/vscode-dts-string-type-literals.ts b/.eslint-plugin-local/vscode-dts-string-type-literals.ts index 0f6d711a3dbfc..ee70d663281db 100644 --- a/.eslint-plugin-local/vscode-dts-string-type-literals.ts +++ b/.eslint-plugin-local/vscode-dts-string-type-literals.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; import { TSESTree } from '@typescript-eslint/utils'; -export = new class ApiTypeDiscrimination implements eslint.Rule.RuleModule { +export default new class ApiTypeDiscrimination implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines' }, @@ -18,8 +19,8 @@ export = new class ApiTypeDiscrimination implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { return { - ['TSPropertySignature[optional=false] TSTypeAnnotation TSLiteralType Literal']: (node: any) => { - const raw = String((node).raw); + ['TSPropertySignature[optional=false] TSTypeAnnotation TSLiteralType Literal']: (node: ESTree.Literal) => { + const raw = String((node as TSESTree.Literal).raw); if (/^('|").*\1$/.test(raw)) { diff --git a/.eslint-plugin-local/vscode-dts-use-export.ts b/.eslint-plugin-local/vscode-dts-use-export.ts index 904feaeec36bc..798572d4f2119 100644 --- a/.eslint-plugin-local/vscode-dts-use-export.ts +++ b/.eslint-plugin-local/vscode-dts-use-export.ts @@ -5,8 +5,9 @@ import { TSESTree } from '@typescript-eslint/utils'; import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; -export = new class VscodeDtsUseExport implements eslint.Rule.RuleModule { +export default new class VscodeDtsUseExport implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { @@ -17,8 +18,8 @@ export = new class VscodeDtsUseExport implements eslint.Rule.RuleModule { create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { return { - ['TSModuleDeclaration :matches(TSInterfaceDeclaration, ClassDeclaration, VariableDeclaration, TSEnumDeclaration, TSTypeAliasDeclaration)']: (node: any) => { - const parent = (node).parent; + ['TSModuleDeclaration :matches(TSInterfaceDeclaration, ClassDeclaration, VariableDeclaration, TSEnumDeclaration, TSTypeAliasDeclaration)']: (node: ESTree.Node) => { + const parent = (node as TSESTree.Node).parent; if (parent && parent.type !== TSESTree.AST_NODE_TYPES.ExportNamedDeclaration) { context.report({ node, diff --git a/.eslint-plugin-local/vscode-dts-use-thenable.ts b/.eslint-plugin-local/vscode-dts-use-thenable.ts index 683394ad11567..2c1ff4c92960b 100644 --- a/.eslint-plugin-local/vscode-dts-use-thenable.ts +++ b/.eslint-plugin-local/vscode-dts-use-thenable.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; +import type * as ESTree from 'estree'; -export = new class ApiEventNaming implements eslint.Rule.RuleModule { +export default new class ApiEventNaming implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { @@ -19,7 +20,7 @@ export = new class ApiEventNaming implements eslint.Rule.RuleModule { return { - ['TSTypeAnnotation TSTypeReference Identifier[name="Promise"]']: (node: any) => { + ['TSTypeAnnotation TSTypeReference Identifier[name="Promise"]']: (node: ESTree.Identifier) => { context.report({ node, diff --git a/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts b/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts index 80d3b7003d799..ab3c338096c76 100644 --- a/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts +++ b/.eslint-plugin-local/vscode-dts-vscode-in-comments.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as eslint from 'eslint'; -import type * as estree from 'estree'; +import type * as ESTree from 'estree'; -export = new class ApiVsCodeInComments implements eslint.Rule.RuleModule { +export default new class ApiVsCodeInComments implements eslint.Rule.RuleModule { readonly meta: eslint.Rule.RuleMetaData = { messages: { @@ -20,7 +20,7 @@ export = new class ApiVsCodeInComments implements eslint.Rule.RuleModule { const sourceCode = context.getSourceCode(); return { - ['Program']: (_node: any) => { + ['Program']: (_node: ESTree.Program) => { for (const comment of sourceCode.getAllComments()) { if (comment.type !== 'Block') { @@ -40,8 +40,8 @@ export = new class ApiVsCodeInComments implements eslint.Rule.RuleModule { } // Types for eslint seem incorrect - const start = sourceCode.getLocFromIndex(startIndex + match.index) as any as estree.Position; - const end = sourceCode.getLocFromIndex(startIndex + match.index + match[0].length) as any as estree.Position; + const start = sourceCode.getLocFromIndex(startIndex + match.index); + const end = sourceCode.getLocFromIndex(startIndex + match.index + match[0].length); context.report({ messageId: 'comment', loc: { start, end } diff --git a/.github/CODENOTIFY b/.github/CODENOTIFY new file mode 100644 index 0000000000000..ac22ac40d266b --- /dev/null +++ b/.github/CODENOTIFY @@ -0,0 +1,148 @@ +# Base Utilities +src/vs/base/common/extpath.ts @bpasero +src/vs/base/common/fuzzyScorer.ts @bpasero +src/vs/base/common/glob.ts @bpasero +src/vs/base/common/oauth.ts @TylerLeonhardt +src/vs/base/common/path.ts @bpasero +src/vs/base/common/stream.ts @bpasero +src/vs/base/common/uri.ts @jrieken +src/vs/base/browser/domSanitize.ts @mjbvz +src/vs/base/browser/** @bpasero +src/vs/base/node/pfs.ts @bpasero +src/vs/base/node/unc.ts @bpasero +src/vs/base/parts/contextmenu/** @bpasero +src/vs/base/parts/ipc/** @bpasero +src/vs/base/parts/quickinput/** @TylerLeonhardt +src/vs/base/parts/sandbox/** @bpasero +src/vs/base/parts/storage/** @bpasero + +# Base Widgets +src/vs/base/browser/ui/grid/** @joaomoreno @benibenj +src/vs/base/browser/ui/list/** @joaomoreno @benibenj +src/vs/base/browser/ui/sash/** @joaomoreno @benibenj +src/vs/base/browser/ui/splitview/** @joaomoreno @benibenj +src/vs/base/browser/ui/table/** @joaomoreno @benibenj +src/vs/base/browser/ui/tree/** @joaomoreno @benibenj + +# Platform +src/vs/platform/auxiliaryWindow/** @bpasero +src/vs/platform/backup/** @bpasero +src/vs/platform/dialogs/** @bpasero +src/vs/platform/editor/** @bpasero +src/vs/platform/environment/** @bpasero +src/vs/platform/files/** @bpasero +src/vs/platform/ipc/** @bpasero +src/vs/platform/launch/** @bpasero +src/vs/platform/lifecycle/** @bpasero +src/vs/platform/menubar/** @bpasero +src/vs/platform/native/** @bpasero +src/vs/platform/quickinput/** @TylerLeonhardt +src/vs/platform/secrets/** @TylerLeonhardt +src/vs/platform/sharedProcess/** @bpasero +src/vs/platform/state/** @bpasero +src/vs/platform/storage/** @bpasero +src/vs/platform/terminal/electron-main/** @Tyriar +src/vs/platform/terminal/node/** @Tyriar +src/vs/platform/utilityProcess/** @bpasero +src/vs/platform/window/** @bpasero +src/vs/platform/windows/** @bpasero +src/vs/platform/workspace/** @bpasero +src/vs/platform/workspaces/** @bpasero +src/vs/platform/actions/common/menuService.ts @jrieken +src/vs/platform/instantiation/** @jrieken + +# Editor Core +src/vs/editor/contrib/snippet/** @jrieken +src/vs/editor/contrib/suggest/** @jrieken +src/vs/editor/contrib/format/** @jrieken + +# Bootstrap +src/*.ts @bpasero + +# Electron Main +src/vs/code/** @bpasero @deepak1556 + +# Workbench Services +src/vs/workbench/services/activity/** @bpasero +src/vs/workbench/services/authentication/** @TylerLeonhardt +src/vs/workbench/services/auxiliaryWindow/** @bpasero +src/vs/workbench/services/chat/** @bpasero +src/vs/workbench/services/contextmenu/** @bpasero +src/vs/workbench/services/dialogs/** @alexr00 @bpasero +src/vs/workbench/services/editor/** @bpasero +src/vs/workbench/services/editor/common/customEditorLabelService.ts @benibenj +src/vs/workbench/services/environment/** @bpasero +src/vs/workbench/services/files/** @bpasero +src/vs/workbench/services/filesConfiguration/** @bpasero +src/vs/workbench/services/history/** @bpasero +src/vs/workbench/services/host/** @bpasero +src/vs/workbench/services/label/** @bpasero +src/vs/workbench/services/languageDetection/** @TylerLeonhardt +src/vs/workbench/services/layout/** @bpasero +src/vs/workbench/services/lifecycle/** @bpasero +src/vs/workbench/services/notification/** @bpasero +src/vs/workbench/services/path/** @bpasero +src/vs/workbench/services/progress/** @bpasero +src/vs/workbench/services/storage/** @bpasero +src/vs/workbench/services/textfile/** @bpasero +src/vs/workbench/services/textmodelResolver/** @bpasero +src/vs/workbench/services/untitled/** @bpasero +src/vs/workbench/services/utilityProcess/** @bpasero +src/vs/workbench/services/views/** @sandy081 @benibenj @bpasero +src/vs/workbench/services/workingCopy/** @bpasero +src/vs/workbench/services/workspaces/** @bpasero + +# Workbench Core +src/vs/workbench/common/** @bpasero +src/vs/workbench/browser/** @bpasero +src/vs/workbench/electron-browser/** @bpasero + +# Workbench Contributions +src/vs/workbench/contrib/authentication/** @TylerLeonhardt +src/vs/workbench/contrib/files/** @bpasero +src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @roblourens +src/vs/workbench/contrib/chat/browser/chatSetup/** @bpasero +src/vs/workbench/contrib/chat/browser/chatStatus/** @bpasero +src/vs/workbench/contrib/chat/browser/chatViewPane.ts @bpasero +src/vs/workbench/contrib/chat/browser/media/chatViewPane.css @bpasero +src/vs/workbench/contrib/chat/browser/chatViewTitleControl.ts @bpasero +src/vs/workbench/contrib/chat/browser/media/chatViewTitleControl.css @bpasero +src/vs/workbench/contrib/chat/browser/chatManagement/chatUsageWidget.ts @bpasero +src/vs/workbench/contrib/chat/browser/chatManagement/media/chatUsageWidget.css @bpasero +src/vs/workbench/contrib/chat/browser/agentSessions/** @bpasero +src/vs/workbench/contrib/chat/browser/chatSessions/** @bpasero +src/vs/workbench/contrib/localization/** @TylerLeonhardt +src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @TylerLeonhardt +src/vs/workbench/contrib/scm/** @lszomoru +src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts @alexr00 @joaomoreno +src/vs/workbench/contrib/preferences/** @rzhao271 + +# Build +build/azure-pipelines/** @lszomoru +build/lib/i18n.ts @TylerLeonhardt +resources/linux/debian/** @rzhao271 +resources/linux/rpm/** @rzhao271 + +# Editor contrib +src/vs/editor/standalone/browser/quickInput/** @TylerLeonhardt + +# Workbench API +src/vs/workbench/api/common/extHostLocalizationService.ts @TylerLeonhardt +src/vs/workbench/api/browser/mainThreadAuthentication.ts @TylerLeonhardt +src/vs/workbench/api/common/extHostAuthentication.ts @TylerLeonhardt +src/vs/workbench/api/node/extHostAuthentication.ts @TylerLeonhardt +src/vs/workbench/api/common/extHostMcp.ts @TylerLeonhardt +src/vs/workbench/api/browser/mainThreadMcp.ts @TylerLeonhardt +src/vs/workbench/api/common/extHostQuickOpen.ts @TylerLeonhardt +src/vs/workbench/api/browser/mainThreadSecretState.ts @TylerLeonhardt + +# Extensions +extensions/microsoft-authentication/** @TylerLeonhardt +extensions/github-authentication/** @TylerLeonhardt +extensions/git/** @lszomoru +extensions/git-base/** @lszomoru +extensions/github/** @lszomoru + +# Chat Editing, Inline Chat +src/vs/workbench/contrib/chat/browser/chatEditing/** @jrieken +src/vs/workbench/contrib/inlineChat/** @jrieken diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7f2fa40b93506..0b3388f9aeb05 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,15 +1,19 @@ # GitHub actions required reviewers .github/workflows/monaco-editor.yml @hediet @alexdima @lszomoru @joaomoreno -.github/workflows/no-package-lock-changes.yml @lszomoru @joaomoreno -.github/workflows/no-yarn-lock-changes.yml @lszomoru @joaomoreno -.github/workflows/pr-darwin-test.yml @lszomoru @joaomoreno -.github/workflows/pr-linux-cli-test.yml @lszomoru @joaomoreno -.github/workflows/pr-linux-test.yml @lszomoru @joaomoreno -.github/workflows/pr-node-modules.yml @lszomoru @joaomoreno -.github/workflows/pr-win32-test.yml @lszomoru @joaomoreno -.github/workflows/pr.yml @lszomoru @joaomoreno +.github/workflows/no-package-lock-changes.yml @lszomoru @joaomoreno @TylerLeonhardt @rzhao271 +.github/workflows/no-yarn-lock-changes.yml @lszomoru @joaomoreno @TylerLeonhardt @rzhao271 +.github/workflows/pr-darwin-test.yml @lszomoru @joaomoreno @TylerLeonhardt @rzhao271 +.github/workflows/pr-linux-cli-test.yml @lszomoru @joaomoreno @TylerLeonhardt @rzhao271 +.github/workflows/pr-linux-test.yml @lszomoru @joaomoreno @TylerLeonhardt @rzhao271 +.github/workflows/pr-node-modules.yml @lszomoru @joaomoreno @TylerLeonhardt @rzhao271 +.github/workflows/pr-win32-test.yml @lszomoru @joaomoreno @TylerLeonhardt @rzhao271 +.github/workflows/pr.yml @lszomoru @joaomoreno @TylerLeonhardt @rzhao271 .github/workflows/telemetry.yml @lramos15 @lszomoru @joaomoreno -# ensure the API police is aware of changes to the vscode-dts file +# Ensure those that manage generated policy are aware of changes +build/lib/policies/policyData.jsonc @joshspicer @rebornix @joaomoreno @pwang347 @sandy081 + +# VS Code API +# Ensure the API team is aware of changes to the vscode-dts file # this is only about the final API, not about proposed API changes -src/vscode-dts/vscode.d.ts @jrieken @mjbvz +src/vscode-dts/vscode.d.ts @jrieken @mjbvz @alexr00 diff --git a/.github/agents/data.md b/.github/agents/data.md new file mode 100644 index 0000000000000..5809fb06d861a --- /dev/null +++ b/.github/agents/data.md @@ -0,0 +1,43 @@ +--- +name: Data +description: Answer telemetry questions with data queries using Kusto Query Language (KQL) +tools: + ['vscode/extensions', 'execute/runInTerminal', 'read/readFile', 'search', 'web/githubRepo', 'azure-mcp/kusto_query', 'todo'] +--- + +# Role and Objective + +You are a Azure Data Explorer data analyst with expert knowledge in Kusto Query Language (KQL) and data analysis. Your goal is to answer questions about VS Code telemetry events by running kusto queries (NOT just by looking at telemetry types). + +# Workflow + +1. Read `vscode-telemetry-docs/.github/copilot-instructions.md` to understand how to access VS Code's telemetry + - If the `vscode-telemetry-docs` folder doesn't exist (just check your workspace_info, no extra tool call needed), run `npm run mixin-telemetry-docs` to clone the telemetry documentation. +2. Analyze data using kusto queries: Don't just describe what could be queried - actually execute Kusto queries to provide real data and insights: + - If the `kusto_query` tool doesn't exist (just check your provided tools, no need to run it!), install the `ms-azuretools.vscode-azure-mcp-server` VS Code extension + - Use the appropriate Kusto cluster and database for the data type + - Always include proper time filtering to limit data volume + - Default to a rolling 28-day window if no specific timeframe is requested + - Format and present the query results clearly to answer the user's question + - Track progress of your kusto analysis using todos + - If kusto queries keep failing (up to 3 repeated attempts of fixing parameters or queries), stop and inform the user. + +# Kusto Best Practices + +When writing Kusto queries, follow these best practices: +- **Explore data efficiently.** Use 1d (1-day) time window and `sample` operator to quickly understand data shape and volume +- **Aggregate usage in proper time windows.** When no specific timeframe is provided: + - Default to a rolling 28-day window (standard practice in VS Code telemetry) + - Use full day boundaries to avoid partial day data + - Follow the time filtering patterns from the telemetry documentation +- **Correctly map names and keys.** EventName is the prefix (`monacoworkbench/` for vscode) and lowercase event name. Properties/Measurements keys are lowercase. Any properties marked `isMeasurement` are in the Measurements bag. +- **Parallelize queries when possible.** Run multiple independent queries as parallel tool calls to speed up analysis. + +# Output Format + +Your response should include: +- The actual Kusto query executed (formatted nicely) +- Real query results with data to answer the user's question +- Interpretation and analysis of the results +- References to specific documentation files when applicable +- Additional context or insights from the telemetry data diff --git a/.github/agents/demonstrate.md b/.github/agents/demonstrate.md new file mode 100644 index 0000000000000..7b2e66cda93a5 --- /dev/null +++ b/.github/agents/demonstrate.md @@ -0,0 +1,130 @@ +--- +name: Demonstrate +description: Agent for demonstrating VS Code features +target: github-copilot +tools: +- "view" +- "create" +- "edit" +- "glob" +- "grep" +- "bash" +- "read_bash" +- "write_bash" +- "stop_bash" +- "list_bash" +- "report_intent" +- "fetch_documentation" +- "agents" +- "read" +- "search" +- "todo" +- "web" +- "github-mcp-server/*" +- "GitHub/*" +- "github/*" +- "vscode-playwright-mcp/*" +--- + +# Role and Objective + +You are a QA testing agent. Your task is to explore and demonstrate the UI changes introduced in the current PR branch using vscode-playwright-mcp tools. Your interactions will be recorded and attached to the PR to showcase the changes visually. + +# Core Requirements + +## Setup Phase + +1. Use GitHub MCP tools to get PR details (description, linked issues, comments) +2. Search the `microsoft/vscode-docs` repository for relevant documentation about the feature area +3. Examine changed files and commit messages to understand the scope +4. Identify what UI features or behaviors were modified +5. Start VS Code automation using `vscode_automation_start` +6. ALWAYS start by setting the setting `"chat.allowAnonymousAccess":true` using the `vscode_automation_settings_add_user_settings` tool. This will ensure that Chat works without requiring sign-in. + +## Testing Phase + +1. Use `browser_snapshot` to capture the current state +2. Execute the user workflows affected by the PR changes + +## Demonstration Goals + +- Show the new or modified UI in action +- Exercise the changed code paths through realistic user interactions +- Capture clear visual evidence of the improvements or changes +- Test edge cases or variations if applicable + +# Important Guidelines + +- Focus on DEMONSTRATING the changes, not verifying correctness +- You are NOT writing playwright tests - use the tools interactively to explore +- If the PR description or commits mention specific scenarios, prioritize testing those +- Make multiple passes if needed to capture different aspects of the changes +- You may make temporary modifications to facilitate better demonstration (e.g., adjusting settings, opening specific views) + +## GitHub MCP Tools + +**Prefer using GitHub MCP tools over `gh` CLI commands** - these provide structured data and better integration: + +### Pull Request Tools +- `pull_request_read` - Get PR details, diff, status, files, reviews, and comments + - Use `method="get"` for PR metadata (title, description, labels, etc.) + - Use `method="get_diff"` for the full diff + - Use `method="get_files"` for list of changed files + - Use `method="get_reviews"` for review summaries + - Use `method="get_review_comments"` for line-specific review comments +- `search_pull_requests` - Search PRs with filters (author, state, etc.) + +### Issue Tools +- `get_issue` - Get full issue details (description, labels, assignees, etc.) +- `get_issue_comments` - Get all comments on an issue +- `search_issues` - Search issues with filters +- `list_sub_issues` - Get sub-issues if using issue hierarchies + +## Pointers for Controlling VS Code + +- **Prefer `vscode_automation_*` tools over `browser_*` tools** when available - these are designed specifically for VS Code interactions and provide more reliable control. For example: + - `vscode_automation_chat_send_message` over using `browser_*` tools to send chat messages + - `vscode_automation_editor_type_text` over using `browser_*` tools to type in editors + +If you are typing into a monaco input and you can't use the standard methods, follow this sequence: + +**Monaco editors (used throughout VS Code) DO NOT work with standard Playwright methods like `.click()` on textareas or `.fill()` / `.type()`** + +**YOU MUST follow this exact sequence:** + +1. **Take a page snapshot** to identify the editor structure in the accessibility tree +2. **Find the parent `code` role element** that wraps the Monaco editor + - ❌ DO NOT click on `textarea` or `textbox` elements - these are overlaid by Monaco's rendering + - ✅ DO click on the `code` role element that is the parent container +3. **Click on the `code` element** to focus the editor - this properly delegates focus to Monaco's internal text handling +4. **Verify focus** by checking that the nested textbox element has the `[active]` attribute in a new snapshot +5. **Use `page.keyboard.press()` for EACH character individually** - standard Playwright `type()` or `fill()` methods don't work with Monaco editors since they intercept keyboard events at the page level + +**Example:** +```js +// ❌ WRONG - this will fail with timeout +await page.locator('textarea').click(); +await page.locator('textarea').fill('text'); + +// ✅ CORRECT +await page.locator('[role="code"]').click(); +await page.keyboard.press('t'); +await page.keyboard.press('e'); +await page.keyboard.press('x'); +await page.keyboard.press('t'); +``` + +**Why this is required:** Monaco editors intercept keyboard events at the page level and use a virtualized rendering system. Clicking textareas directly or using `.fill()` bypasses Monaco's event handling, causing timeouts and failures. + +# Workflow Pattern + +1. Gather context: + - Retrieve PR details using GitHub MCP (description, linked issues, review comments) + - Search microsoft/vscode-docs for documentation on the affected feature areas + - Examine changed files and commit messages +2. Plan which user interactions will best showcase the changes +3. Start automation and navigate to the relevant area +4. Perform the interactions +5. Document what you're demonstrating as you go +6. Ensure the recording clearly shows the before/after or new functionality +7. **ALWAYS stop the automation** by calling `vscode_automation_stop` - this is REQUIRED whether you successfully demonstrated the feature or encountered issues that prevented testing diff --git a/.github/agents/engineering.md b/.github/agents/engineering.md new file mode 100644 index 0000000000000..1cfad832f7a47 --- /dev/null +++ b/.github/agents/engineering.md @@ -0,0 +1,18 @@ +--- +name: Engineering +description: The VS Code Engineering Agent helps with engineering-related tasks in the VS Code repository. +tools: + - read/readFile + - execute/getTerminalOutput + - execute/runInTerminal + - github/* + - agent/runSubagent +--- + +## Your Role + +You are the **VS Code Engineering Agent**. Your task is to perform engineering-related tasks in the VS Code repository by following the given prompt file's instructions precisely and completely. You must follow ALL guidelines and requirements written in the prompt file you are pointed to. + +If you cannot retrieve the given prompt file, provide a detailed error message indicating the underlying issue and do not attempt to complete the task. + +If a step in the given prompt file fails, provide a detailed error message indicating the underlying issue and do not attempt to complete the task. diff --git a/.github/classifier.json b/.github/classifier.json index be82395f59872..1ca855d5074bb 100644 --- a/.github/classifier.json +++ b/.github/classifier.json @@ -16,6 +16,8 @@ "bracket-pair-guides": {"assign": ["hediet"]}, "breadcrumbs": {"assign": ["jrieken"]}, "callhierarchy": {"assign": ["jrieken"]}, + "chat-terminal": {"assign": ["Tyriar"]}, + "chat-terminal-output-monitor": {"assign": ["meganrogge"]}, "chrome-devtools": {"assign": ["deepak1556"]}, "cloud-changes": {"assign": ["joyceerhl"]}, "code-cli": {"assign": ["connor4312"]}, @@ -111,7 +113,7 @@ "interactive-window": {"assign": ["amunger", "rebornix"]}, "ipc": {"assign": ["joaomoreno"]}, "issue-bot": {"assign": ["chrmarti"]}, - "issue-reporter": {"assign": ["justschen"]}, + "issue-reporter": {"assign": ["yoyokrazy"]}, "javascript": {"assign": ["mjbvz"]}, "json": {"assign": ["aeschli"]}, "json-sorting": {"assign": ["aiday-mar"]}, @@ -208,7 +210,7 @@ "settings-editor": {"assign": ["rzhao271"]}, "settings-search": {"assign": ["rzhao271"]}, "settings-sync": {"assign": ["sandy081"]}, - "settings-sync-server": {"assign": ["Tyriar", "lszomoru"]}, + "settings-sync-server": {"assign": ["rzhao271"]}, "shared-process": {"assign": []}, "simple-file-dialog": {"assign": ["alexr00"]}, "smart-select": {"assign": ["jrieken"]}, @@ -220,29 +222,36 @@ "tasks": {"assign": ["meganrogge"], "accuracy": 0.85}, "telemetry": {"assign": ["lramos15"]}, "terminal": {"assign": ["meganrogge"]}, + "terminal-accessibility": {"assign": ["meganrogge"]}, "terminal-conpty": {"assign": ["meganrogge"]}, - "terminal-editors": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-external": {"assign": ["meganrogge"]}, - "terminal-find": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-input": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-layout": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-links": {"assign": ["Tyriar"]}, - "terminal-local-echo": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-persistence": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-process": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-profiles": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-quick-fix": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-rendering": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-search": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-bash": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-cmd": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-fish": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-git-bash": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-integration": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-pwsh": {"assign": ["Tyriar", "meganrogge"]}, - "terminal-shell-zsh": {"assign": ["Tyriar", "meganrogge"]}, + "terminal-editors": {"assign": ["meganrogge"]}, + "terminal-env-collection": {"assign": ["anthonykim1"]}, + "terminal-external": {"assign": ["anthonykim1"]}, + "terminal-find": {"assign": ["anthonykim1"]}, + "terminal-inline-chat": {"assign": ["Tyriar", "meganrogge"]}, + "terminal-input": {"assign": ["Tyriar"]}, + "terminal-layout": {"assign": ["anthonykim1"]}, + "terminal-ligatures": {"assign": ["Tyriar"]}, + "terminal-links": {"assign": ["anthonykim1"]}, + "terminal-local-echo": {"assign": ["anthonykim1"]}, + "terminal-parser": {"assign": ["Tyriar"]}, + "terminal-persistence": {"assign": ["Tyriar"]}, + "terminal-process": {"assign": ["anthonykim1"]}, + "terminal-profiles": {"assign": ["meganrogge"]}, + "terminal-quick-fix": {"assign": ["meganrogge"]}, + "terminal-rendering": {"assign": ["Tyriar"]}, + "terminal-shell-bash": {"assign": ["anthonykim1"]}, + "terminal-shell-cmd": {"assign": ["anthonykim1"]}, + "terminal-shell-fish": {"assign": ["anthonykim1"]}, + "terminal-shell-git-bash": {"assign": ["anthonykim1"]}, + "terminal-shell-integration": {"assign": ["anthonykim1"]}, + "terminal-shell-pwsh": {"assign": ["anthonykim1"]}, + "terminal-shell-sh": {"assign": ["anthonykim1"]}, + "terminal-shell-zsh": {"assign": ["anthonykim1"]}, + "terminal-sticky-scroll": {"assign": ["anthonykim1"]}, + "terminal-suggest": {"assign": ["meganrogge"]}, "terminal-tabs": {"assign": ["meganrogge"]}, - "terminal-winpty": {"assign": ["Tyriar", "meganrogge"]}, + "terminal-winpty": {"assign": ["anthonykim1"]}, "testing": {"assign": ["connor4312"]}, "themes": {"assign": ["aeschli"]}, "timeline": {"assign": ["lramos15"]}, diff --git a/.github/commands.json b/.github/commands.json index c62f620b5d207..29288f1309b82 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -586,7 +586,9 @@ "action": "close", "reason": "not_planned", "comment": "Please look at the following meta issue: https://github.com/microsoft/vscode/issues/253132, if the bug you are experiencing is not there, please comment on this closed issue thread so we can re-open it.", - "assign": ["TylerLeonhardt"] + "assign": [ + "TylerLeonhardt" + ] }, { "type": "label", @@ -600,7 +602,7 @@ "type": "label", "name": "~chat-billing", "removeLabel": "~chat-billing", - "addLabel":"chat-billing", + "addLabel": "chat-billing", "action": "close", "reason": "not_planned", "comment": "Please look at the following meta issue: https://github.com/microsoft/vscode/issues/252230. Please refer to that issue for updates and discussions. Feel free to open a new issue if you think this is a different problem." @@ -609,7 +611,7 @@ "type": "label", "name": "~chat-infinite-response-loop", "removeLabel": "~chat-infinite-response-loop", - "addLabel":"chat-infinite-response-loop", + "addLabel": "chat-infinite-response-loop", "action": "close", "reason": "not_planned", "comment": "Please look at the following meta issue: https://github.com/microsoft/vscode/issues/253134. Please refer to that issue for updates and discussions. Feel free to open a new issue if you think this is a different problem." @@ -622,6 +624,51 @@ "action": "close", "reason": "not_planned", "comment": "Thank you for your submission. This issue has been closed as it doesn't meet our community guidelines or appears to be spam.\n\n**If you believe this was closed in error:**\n- Please review our [Code of Conduct](https://opensource.microsoft.com/codeofconduct/)\n- Ensure your issue contains a clear description of the problem or feature request\n- Feel free to open a new issue with appropriate detail if this was a legitimate concern\n\n**For legitimate issues, please include:**\n- Clear description of the problem\n- Steps to reproduce (for bugs)\n- Expected vs actual behavior\n- VS Code version and environment details\n\nThank you for helping us maintain a welcoming and productive community." + }, + { + "type": "label", + "name": "~capi", + "addLabel": "capi", + "removeLabel": "~capi", + "assign": [ + "samvantran", + "sharonlo" + ], + "comment": "Thank you for creating this issue! Please provide one or more `requestIds` to help the platform team investigate. You can follow instructions [found here](https://github.com/microsoft/vscode/wiki/Copilot-Issues#language-model-requests-and-responses) to locate the `requestId` value.\n\nHappy Coding!" + }, + { + "type": "label", + "name": "*edu", + "action": "close", + "reason": "not_planned", + "comment": "We closed this issue because it seems to be about coursework or grading. This issue tracker is for issues about VS Code itself. For coursework-related issues, or issues related to your course's specific VS Code setup, please consider engaging directly with your course instructor.\n\nHappy Coding!" + }, + { + "type": "comment", + "name": "edu", + "allowUsers": [ + "cleidigh", + "usernamehw", + "gjsjohnmurray", + "IllusionMH" + ], + "action": "updateLabels", + "addLabel": "*edu" + }, + { + "type": "label", + "name": "~agent-behavior", + "action": "close", + "reason": "not_planned", + "addLabel": "agent-behavior", + "removeLabel": "~agent-behavior", + "comment": "Unfortunately I think you are hitting a AI quality issue that is not actionable enough for us to track a bug. We would recommend that you try other available models and look at the [Tips and tricks for Copilot in VS Code](https://code.visualstudio.com/docs/copilot/copilot-tips-and-tricks) doc page.\n\nWe are constantly improving AI quality in every release, thank you for the feedback! If you believe this is a technical bug, we recommend you report a new issue including logs described on the [Copilot Issues](https://github.com/microsoft/vscode/wiki/Copilot-Issues) wiki page." + }, + { + "type": "label", + "name": "~accessibility-sla", + "addLabel": "accessibility-sla", + "removeLabel": "~accessibility-sla", + "comment": "The Visual Studio and VS Code teams have an agreement with the Accessibility team that 3:1 contrast is enough for inside the editor." } - ] diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index bf7d42ba5a386..4dad865443cca 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -46,6 +46,22 @@ Each extension follows the standard VS Code extension structure with `package.js 3. **Follow imports**: Check what files import the problematic module 4. **Check test files**: Often reveal usage patterns and expected behavior +## Validating TypeScript changes + +MANDATORY: Always check the `VS Code - Build` watch task output via #runTasks/getTaskOutput for compilation errors before running ANY script or declaring work complete, then fix all compilation errors before moving forward. + +- NEVER run tests if there are compilation errors +- NEVER use `npm run compile` to compile TypeScript files but call #runTasks/getTaskOutput instead + +### TypeScript compilation steps +- Monitor the `VS Code - Build` task outputs for real-time compilation errors as you make changes +- This task runs `Core - Build` and `Ext - Build` to incrementally compile VS Code TypeScript sources and built-in extensions +- Start the task if it's not already running in the background + +### TypeScript validation steps +- Use the run test tool if you need to run tests. If that tool is not available, then you can use `scripts/test.sh` (or `scripts\test.bat` on Windows) for unit tests (add `--grep ` to filter tests) or `scripts/test-integration.sh` (or `scripts\test-integration.bat` on Windows) for integration tests (integration tests end with .integrationTest.ts or are in /extensions/). +- Use `npm run valid-layers-check` to check for layering issues + ## Coding Guidelines ### Indentation @@ -73,7 +89,8 @@ We use tabs, not spaces. - Use "double quotes" for strings shown to the user that need to be externalized (localized) - Use 'single quotes' otherwise -- All strings visible to the user need to be externalized +- All strings visible to the user need to be externalized using the `vs/nls` module +- Externalized strings must not use string concatenation. Use placeholders instead (`{0}`). ### UI labels - Use title-style capitalization for command labels, buttons and menu items (each word is capitalized). @@ -113,4 +130,9 @@ function f(x: number, y: string): void { } - Don't add tests to the wrong test suite (e.g., adding to end of file instead of inside relevant suite) - Look for existing test patterns before creating new structures - Use `describe` and `test` consistently with existing patterns +- Prefer regex capture groups with names over numbered capture groups. - If you create any temporary new files, scripts, or helper files for iteration, clean up these files by removing them at the end of the task +- Never duplicate imports. Always reuse existing imports if they are present. +- Do not use `any` or `unknown` as the type for variables, parameters, or return values unless absolutely necessary. If they need type annotations, they should have proper types or interfaces defined. +- When adding file watching, prefer correlated file watchers (via fileService.createWatcher) to shared ones. +- When adding tooltips to UI elements, prefer the use of IHoverService service. diff --git a/.github/instructions/disposable.instructions.md b/.github/instructions/disposable.instructions.md new file mode 100644 index 0000000000000..06e1b62f7e38b --- /dev/null +++ b/.github/instructions/disposable.instructions.md @@ -0,0 +1,20 @@ +--- +description: Guidelines for writing code using IDisposable +--- + +Core symbols: +* `IDisposable` + * `dispose(): void` - dispose the object +* `Disposable` (implements `IDisposable`) - base class for disposable objects + * `this._store: DisposableStore` + * `this._register(t: T): T` + * Try to immediately register created disposables! E.g. `const someDisposable = this._register(new SomeDisposable())` +* `DisposableStore` (implements `IDisposable`) + * `add(t: T): T` + * `clear()` +* `toDisposable(fn: () => void): IDisposable` - helper to create a disposable from a function + +* `MutableDisposable` (implements `IDisposable`) + * `value: IDisposable | undefined` + * `clear()` + * A value that enters a mutable disposable (at least once) will be disposed the latest when the mutable disposable is disposed (or when the value is replaced or cleared). diff --git a/.github/instructions/learnings.instructions.md b/.github/instructions/learnings.instructions.md new file mode 100644 index 0000000000000..78a9f52a06e6e --- /dev/null +++ b/.github/instructions/learnings.instructions.md @@ -0,0 +1,32 @@ +--- +applyTo: ** +description: This document describes how to deal with learnings that you make. (meta instruction) +--- + +This document describes how to deal with learnings that you make. +It is a meta-instruction file. + +Structure of learnings: +* Each instruction file has a "Learnings" section. +* Each learning has a counter that indicates how often that learning was useful (initially 1). +* Each learning has a 1-4 sentences description of the learning. + +Example: +```markdown +## Learnings +* Prefer `const` over `let` whenever possible (1) +* Avoid `any` type (3) +``` + +When the user tells you "learn!", you should: +* extract a learning from the recent conversation + * identify the problem that you created + * identify why it was a problem + * identify how you were told to fix it/how the user fixed it +* create a learning (1-4 sentences) from that + * Write this out to the user and reflect over these sentences + * then, add the reflected learning to the "Learnings" section of the most appropriate instruction file + + + Important: Whenever a learning was really useful, increase the counter!! + When a learning was not useful and just caused more problems, decrease the counter. diff --git a/.github/instructions/observables.instructions.md b/.github/instructions/observables.instructions.md new file mode 100644 index 0000000000000..2aedc290be167 --- /dev/null +++ b/.github/instructions/observables.instructions.md @@ -0,0 +1,72 @@ +--- +description: Guidelines for writing code using observables and deriveds. +--- + +```ts +class MyService extends Disposable { + private _myData1 = observableValue(/* always put `this` here */ this, /* initial value*/ 0); + private _myData2 = observableValue(/* always put `this` here */ this, /* initial value*/ 42); + + // Deriveds can combine/derive from other observables/deriveds + private _myDerivedData = derived(this, reader => { + // Use observable.read(reader) to access the value and track the dependency. + return this._myData1.read(reader) * this._myData2.read(reader); + }); + + private _myDerivedDataWithLifetime = derived(this, reader => { + // The reader.store will get cleared just before the derived is re-evaluated or gets unsubscribed. + return reader.store.add(new SomeDisposable(this._myDerivedData.read(reader))); + }); + + constructor() { + this._register(autorun((reader) => { // like mobx autorun, they run immediately and on change + const data = this._myData1.read(reader); // but you only get the data if you pass in the reader! + + console.log(data); + + // also has reader.store + })) + } + + getData(): number { + return this._myData1.get(); // use get if you don't have a reader, but try to avoid it since the dependency is not tracked. + } + + setData1() { + this._myData1.set(42, undefined); // use set to update the value. The second paramater is the transaction, which is undefined here. + } + + setData2() { + transaction(tx => { + // you can use transaction to batch updates, so they are only notified once. + // Whenever multiple observables are synchronously updated together, use transaction! + this._myData1.set(42, tx); + this._myData2.set(43, tx); + }); + } +} +``` + + +Most important symbols: +* `observableValue` +* `disposableObservableValue` +* `derived` +* `autorun` +* `transaction` +* `observableFromEvent` +* `observableSignalFromEvent` +* `observableSignal(...): IObservable` - use `.trigger(tx)` to trigger a change + + +* Check src\vs\base\common\observableInternal\index.ts for a list of all observable utitilies + + +* Important learnings: + * [1] Avoid glitches + * [2] **Choose the right observable value type:** + * Use `observableValue(owner, initialValue)` for regular values + * Use `disposableObservableValue(owner, initialValue)` when storing disposable values - it automatically disposes the previous value when a new one is set, and disposes the current value when the observable itself is disposed (similar to `MutableDisposable` behavior) + * [3] **Choose the right event observable pattern:** + * Use `observableFromEvent(owner, event, valueComputer)` when you need to track a computed value that changes with the event, and you want updates only when the computed value actually changes + * Use `observableSignalFromEvent(owner, event)` when you need to force re-computation every time the event fires, regardless of value stability. This is important when the computed value might not change but dependent computations need fresh context (e.g., workspace folder changes where the folder array reference might be the same but file path calculations need to be refreshed) diff --git a/.github/instructions/telemetry.instructions.md b/.github/instructions/telemetry.instructions.md new file mode 100644 index 0000000000000..1455a219f681e --- /dev/null +++ b/.github/instructions/telemetry.instructions.md @@ -0,0 +1,113 @@ +--- +description: Use when asked to work on telemetry events +--- + +Patterns for GDPR-compliant telemetry in VS Code with proper type safety and privacy protection. + +## Implementation Pattern + +### 1. Define Types +```typescript +type MyFeatureEvent = { + action: string; + duration: number; + success: boolean; + errorCode?: string; +}; + +type MyFeatureClassification = { + action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The action performed.' }; + duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Time in milliseconds.' }; + success: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether action succeeded.' }; + errorCode: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Error code if action failed.' }; + owner: 'yourGitHubUsername'; + comment: 'Tracks MyFeature usage and performance.'; +}; +``` + +### 2.1. Send Event +```typescript +this.telemetryService.publicLog2('myFeatureAction', { + action: 'buttonClick', + duration: 150, + success: true +}); +``` + +### 2.2. Error Events +For error-specific telemetry with stack traces or error messages: +```typescript +type MyErrorEvent = { + operation: string; + errorMessage: string; + duration?: number; +}; + +type MyErrorClassification = { + operation: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The operation that failed.' }; + errorMessage: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; comment: 'The error message.' }; + duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Time until failure.' }; + owner: 'yourGitHubUsername'; + comment: 'Tracks MyFeature errors for reliability.'; +}; + +this.telemetryService.publicLogError2('myFeatureError', { + operation: 'fileRead', + errorMessage: error.message, + duration: 1200 +}); +``` + +### 3. Service Injection +```typescript +constructor( + @ITelemetryService private readonly telemetryService: ITelemetryService, +) { super(); } +``` + +## GDPR Classifications & Purposes + +**Classifications (choose the most restrictive):** +- `SystemMetaData` - **Most common.** Non-personal system info, user preferences, feature usage, identifiers (extension IDs, language types, counts, durations, success flags) +- `CallstackOrException` - Error messages, stack traces, exception details. **Only for actual error information.** +- `PublicNonPersonalData` - Data already publicly available (rare) + +**Purposes (combine with different classifications):** +- `FeatureInsight` - **Default.** Understanding how features are used, user behavior patterns, feature adoption +- `PerformanceAndHealth` - **For errors & performance.** Metrics, error rates, performance measurements, diagnostics + +**Required Properties:** +- `comment` - Clear explanation of what the field contains and why it's collected +- `owner` - GitHub username (infer from branch or ask) +- `isMeasurement: true` - **Required** for all numeric values flags used in calculations + +## Error Events + +Use `publicLogError2` for errors with `CallstackOrException` classification: + +```typescript +this.telemetryService.publicLogError2('myFeatureError', { + errorMessage: error.message, + errorCode: 'MYFEATURE_001', + context: 'initialization' +}); +``` + +## Naming & Privacy Rules + +**Naming Conventions:** +- Event names: `camelCase` with context (`extensionActivationError`, `chatMessageSent`) +- Property names: specific and descriptive (`agentId` not `id`, `durationMs` not `duration`) +- Common patterns: `success/hasError/isEnabled`, `sessionId/extensionId`, `type/kind/source` + +**Critical Don'ts:** +- ❌ No PII (usernames, emails, file paths, content) +- ❌ Missing `owner` field in classification (infer from branch name or ask user) +- ❌ Vague comments ("user data" → "selected language identifier") +- ❌ Wrong classification +- ❌ Missing `isMeasurement` on numeric metrics + +**Privacy Requirements:** +- Minimize data collection to essential insights only +- Use hashes/categories instead of raw values when possible +- Document clear purpose for each data point diff --git a/.github/instructions/tree-widgets.instructions.md b/.github/instructions/tree-widgets.instructions.md new file mode 100644 index 0000000000000..b5df37f75e69b --- /dev/null +++ b/.github/instructions/tree-widgets.instructions.md @@ -0,0 +1,157 @@ +--- +description: Use when asked to consume workbench tree widgets in VS Code. +--- + +# Workbench Tree Widgets Overview + +**Location**: `src/vs/platform/list/browser/listService.ts` +**Type**: Platform Services +**Layer**: Platform + +## Purpose + +The Workbench Tree Widgets provide high-level, workbench-integrated tree components that extend the base tree implementations with VS Code-specific functionality like context menus, keyboard navigation, theming, accessibility, and dependency injection integration. These widgets serve as the primary tree components used throughout the VS Code workbench for file explorers, debug views, search results, and other hierarchical data presentations. + +## Scope + +### Included Functionality +- **Context Integration**: Automatic context key management, focus handling, and VS Code theme integration +- **Resource Navigation**: Built-in support for opening files and resources with proper editor integration +- **Accessibility**: Complete accessibility provider integration with screen reader support +- **Keyboard Navigation**: Smart keyboard navigation with search-as-you-type functionality +- **Multi-selection**: Configurable multi-selection behavior with platform-appropriate modifier keys +- **Dependency Injection**: Full integration with VS Code's service container for automatic service injection +- **Configuration**: Automatic integration with user settings for tree behavior customization + +### Integration Points +- **IInstantiationService**: For service injection and component creation +- **IContextKeyService**: For managing focus, selection, and tree state context keys +- **IListService**: For registering trees and managing workbench list lifecycle +- **IConfigurationService**: For reading tree configuration settings +- **Resource Navigators**: For handling file/resource opening with proper editor integration + +### Out of Scope +- Low-level tree rendering and virtualization (handled by base tree classes) +- Data management and async loading logic (provided by data sources) +- Custom styling beyond workbench theming integration + +## Architecture + +### Key Classes & Interfaces + +- **WorkbenchTreeInternals**: Encapsulates common workbench functionality across all tree types +- **ResourceNavigator**: Handles file/resource opening with proper editor integration +- **IOpenEvent**: Event interface for resource opening with editor options +- **IWorkbench*TreeOptions**: Configuration interfaces extending base options with workbench features +- **IResourceNavigatorOptions**: Configuration for resource opening behavior + +### Key Files + +- **`src/vs/platform/list/browser/listService.ts`**: Contains all workbench tree widget implementations, shared workbench functionality (`WorkbenchTreeInternals`), and configuration utilities + - `src/vs/platform/list/browser/test/listService.test.ts`: Unit tests for workbench trees +- **`src/vs/base/browser/ui/tree/objectTree.ts`**: Base implementation for static trees and compressible trees + - `src/vs/base/test/browser/ui/tree/objectTree.test.ts`: Base tree tests +- **`src/vs/base/browser/ui/tree/asyncDataTree.ts`**: Base implementation for async trees with lazy loading support + - `src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts`: Async tree tests +- **`src/vs/base/browser/ui/tree/dataTree.ts`**: Base implementation for data-driven trees with explicit data sources + - `src/vs/base/test/browser/ui/tree/dataTree.test.ts`: Data tree tests +- **`src/vs/base/browser/ui/tree/abstractTree.ts`**: Base tree foundation +- **`src/vs/base/browser/ui/tree/tree.ts`**: Core interfaces and types + +## Development Guidelines + +### Choosing the Right Tree Widget + +1. **WorkbenchObjectTree**: Use for simple, static hierarchical data that doesn't change frequently + ```typescript + // Example: Timeline items, loaded scripts + const tree = instantiationService.createInstance( + WorkbenchObjectTree, + 'TimelineView', container, delegate, renderers, options + ); + ``` + +2. **WorkbenchAsyncDataTree**: Use for dynamic data that loads asynchronously + ```typescript + // Example: Debug variables, file contents + const tree = instantiationService.createInstance( + WorkbenchAsyncDataTree, + 'VariablesView', container, delegate, renderers, dataSource, options + ); + ``` + +3. **WorkbenchCompressible*Tree**: Use when you need path compression for deep hierarchies + ```typescript + // Example: File explorer, call stack + const tree = instantiationService.createInstance( + WorkbenchCompressibleAsyncDataTree, + 'FileExplorer', container, delegate, compressionDelegate, renderers, dataSource, options + ); + ``` + +### Construction Pattern + +**Always use IInstantiationService.createInstance()** to ensure proper dependency injection: + +```typescript +constructor( + @IInstantiationService private instantiationService: IInstantiationService +) { + this.tree = this.instantiationService.createInstance( + WorkbenchAsyncDataTree, + 'UniqueTreeId', // Used for settings and context keys + container, // DOM container element + delegate, // IListVirtualDelegate for item height/template + renderers, // Array of tree renderers + dataSource, // Data source (async trees only) + options // Tree configuration options + ); +} +``` + +### Required Options + +All workbench trees require an **accessibilityProvider**: +```typescript +const options: IWorkbenchAsyncDataTreeOptions = { + accessibilityProvider: { + getAriaLabel: (element: T) => element.name, + getRole: () => 'treeitem' + } + // ... other options +}; +``` + +### Common Configuration Patterns + +```typescript +// Standard tree setup with search, identity, and navigation +const options = { + accessibilityProvider: new MyAccessibilityProvider(), + identityProvider: { getId: (element) => element.id }, + keyboardNavigationLabelProvider: { + getKeyboardNavigationLabel: (element) => element.name + }, + multipleSelectionController: { + isSelectionSingleChangeEvent: (e) => e.ctrlKey || e.metaKey, + isSelectionRangeChangeEvent: (e) => e.shiftKey + }, + overrideStyles: this.getLocationBasedColors().listOverrideStyles +}; +``` + +### Lifecycle Management + +- **Always register trees as disposables** in the containing component +- **Use the tree's `setInput()` method** to provide initial data +- **Always call `layout()` when the container initializes and when its size changes** +- **Handle selection and open events** through the tree's event system + +### Performance Considerations + +- Use **compression** for deep hierarchies to reduce DOM nodes +- Implement **efficient data sources** that avoid unnecessary data fetching +- Consider **virtualization settings** for large datasets +- Use **identity providers** for efficient updates and state preservation + +--- diff --git a/.github/instructions/typescript.instructions.md b/.github/instructions/typescript.instructions.md deleted file mode 100644 index bf7f7f394c59d..0000000000000 --- a/.github/instructions/typescript.instructions.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -applyTo: '**/*.ts' ---- - -# VS Code Copilot Development Instructions for TypeScript - -You MUST check compilation output before running ANY script or declaring work complete! - -1. **ALWAYS** check the "Core - Build" task output for compilation errors -2. **ALWAYS** check the "Ext - Build" task output for compilation errors -3. **NEVER** run tests if there are compilation errors -3. **NEVER** use `npm run compile` to compile TypeScript files, always check task output -4. **FIX** all compilation errors before moving forward - -## TypeScript compilation steps - -Typescript compilation errors can be found by running the "Core - Build" and "Ext - Build" tasks: -- **Core - Build**: Compiles the main VS Code TypeScript sources -- **Ext - Build**: Compiles the built-in extensions -- These background tasks may already be running from previous development sessions -- If not already running, start them to get real-time compilation feedback -- The tasks provide incremental compilation, so they will automatically recompile when files change - -## TypeScript validation steps -- Use `scripts/test.sh` (or `scripts\test.bat` on Windows) for unit tests (add `--grep ` to filter tests) -- Use `scripts/test-integration.sh` (or `scripts\test-integration.bat` on Windows) for integration tests -- Use `npm run valid-layers-check` to check for layering issues - diff --git a/.github/prompts/build-champ.prompt.md b/.github/prompts/build-champ.prompt.md new file mode 100644 index 0000000000000..58d22ecc41dc9 --- /dev/null +++ b/.github/prompts/build-champ.prompt.md @@ -0,0 +1,50 @@ +--- +agent: agent +tools: ['github/github-mcp-server/*', 'microsoft/azure-devops-mcp/*', 'todos'] +--- +# Role +You are the build champion for the VS Code team. Your task is to triage a {{build}} by following these steps: + +# Instructions +1. Display the warning message written below. +2. Investigate the failing jobs of a given {{build}}. + - **Prioritize investigating failing unit test steps first** - these often reveal the root cause of failures +3. Find the most recent {{successful-build}} prior to the failed {{build}}, then identify the {{first-failing-build}} after the {{successful-build}}. Note the commit ids of {{successful-build}} and {{first-failing-build}}. + - Ensure the branch is the same for all builds involved. +4. Using the commit id between the two builds, identify all PRs that were merged in that range. +5. For each PR, analyze the changes to determine if they could have caused the failure. +6. Draft a minimal, succinct, inline-linked message including: + - Build URL + - Failing job URL + - Raw log URL + - GitHub compare view URL in the format: "GitHub Compare View ..." + - List of possible root cause PRs. Ensure the PR numbers are linked to the actual PRs. +7. If no PRs seem to be the cause, suggest rerunning the failed tests and filing an issue on GitHub if the problem persists. + +# Variables +- {{build}}: Provided by the user. If the build is provided as a github url, decode the build URL from it. +- {{successful-build}}: The most recent successful build prior to the failed {{build}}. +- {{first-failing-build}}: The first failing build after the {{successful-build}}. + +## Guidelines +- Include links to relevant PRs, commits, and builds in your output. +- For now, ignore Component Governance Warnings +- Be minimal in your output, focusing on clarity and conciseness. + +## Warning Message + +**⚠️ Known Issues with Build Champion Agent ⚠️** +This agent should be used in parallel while investigating build failures, as it has some known issues: +1. **Double check the error discovered by the agent:** The agent often confuses missing `.build/logs` as an infrastructure issue. This is incorrect, as the missing logs are typically caused by test or build failures. +2. **Pay attention to the build numbers discovered by the agent:** The agent sometimes incorrectly finds the previous successful build. +3. **Double check the list of PRs:** The agent sometimes fails to list all PRs merged between builds. Use the github compare link provided. + +**Please update this prompt file as you discover ways it can be improved.** + +--- + + +## Known Scenarios + +### Expired Approval Step +If a build appears to have an elapsed time of 30 days, this indicates this build was meant to be a release build, but no one approved the release. There is no action needed in this scenario. diff --git a/.github/prompts/codenotify.prompt.md b/.github/prompts/codenotify.prompt.md new file mode 100644 index 0000000000000..23b4215739729 --- /dev/null +++ b/.github/prompts/codenotify.prompt.md @@ -0,0 +1,81 @@ +--- +agent: agent +tools: ['edit', 'search', 'runCommands', 'fetch', 'todos'] +--- + +# Add My Contributions to CODENOTIFY + +This prompt helps you add your code contributions to the `.github/CODENOTIFY` file based on git blame history. + +## Instructions + +**Before running this prompt, provide the following information:** + +1. **Your GitHub handle:** (e.g., `@YOURHANDLE`) +2. **Alternative usernames in git blame:** (e.g., `Erich Gamma`, `ALIAS@microsoft.com`, or any other names/emails that might appear in git commits) + +## What This Prompt Does + +This prompt will: +1. Search through the repository's git blame history for files you've significantly contributed to +2. Analyze which files and directories have your contributions +3. **Follow the existing structure** in the `.github/CODENOTIFY` file, here are some examples: + - `src/vs/base/common/**` → Add to **Base Utilities** section + - `src/vs/base/browser/ui/**` → Add to **Base Widgets** section + - `src/vs/base/parts/**` → Add to **Base Utilities** section + - `src/vs/platform/**` → Add to **Platform** section + - `src/bootstrap-*.ts`, `src/main.ts`, etc. → Add to **Bootstrap** section + - `src/vs/code/**` → Add to **Electron Main** section + - `src/vs/workbench/services/**` → Add to **Workbench Services** section + - `src/vs/workbench/common/**`, `src/vs/workbench/browser/**` → Add to **Workbench Core** section + - `src/vs/workbench/contrib/**` → Add to **Workbench Contributions** section + - `src/vs/workbench/api/**` → Add to **Workbench API** section + - `extensions/**` → Add to **Extensions** section +4. Add appropriate entries in the format: + - Individual files: `path/to/file.ts @yourusername` + - Directories: `path/to/directory/** @yourusername` +5. Place entries within existing sections, maintaining alphabetical or logical order +6. Create new sections only if contributions don't fit existing categories +7. Avoid duplicating existing entries + +## Expected Output Format + +Entries will be added to **existing sections** based on their path. For example: + +``` +# Base Utilities +src/vs/base/common/extpath.ts @bpasero +src/vs/base/common/oauth.ts @yourusername # ← Your contribution added here +src/vs/base/parts/quickinput/** @yourusername # ← Your contribution added here + +# Platform +src/vs/platform/quickinput/** @yourusername # ← Your contribution added here +src/vs/platform/secrets/** @yourusername # ← Your contribution added here + +# Workbench Services +src/vs/workbench/services/authentication/** @yourusername # ← Your contribution added here + +# Workbench Contributions +src/vs/workbench/contrib/authentication/** @yourusername # ← Your contribution added here +src/vs/workbench/contrib/localization/** @yourusername # ← Your contribution added here +``` + +If you have contributions that don't fit existing sections (e.g., `foo/bar/**`), new sections can be created at the end: + +``` +# Foo Bar +foo/bar/baz/** @yourusername +foo/bar/biz/** @yourusername +``` + +## Notes + +- **CRITICAL**: Entries must be added to the appropriate existing section based on their path +- Respect the existing organizational structure of the CODENOTIFY file +- If you're already listed for certain files/directories, those won't be duplicated +- Use `**` wildcard for directories where you've touched multiple files +- Maintain alphabetical or logical order within each section + +--- + +**Now, provide your GitHub handle and any alternative usernames found in git blame, and I'll help you update the CODENOTIFY file.** diff --git a/.github/prompts/component.prompt.md b/.github/prompts/component.prompt.md new file mode 100644 index 0000000000000..5297e2b6ad4fe --- /dev/null +++ b/.github/prompts/component.prompt.md @@ -0,0 +1,58 @@ +--- +agent: agent +description: 'Help author a component specification for an agent.' +tools: ['edit', 'search', 'usages', 'vscodeAPI', 'fetch', 'extensions', 'todos'] +--- + + +Your goal is to create a component overview in markdown given the context provided by the user. The overview should include a brief description of the component, its main features, an architectural diagram and layout of important code files and their relationships. The purpose of this overview is to enable a developer to attach it to a feature request and ensure the agent has enough context to make correct code changes without breaking functionality. + + + +# [Component Name] Overview + +**Location**: `src/vs/[path/to/component]` +**Type**: [Service/Contribution/Extension/API/etc.] +**Layer (if applicable)**: [base/platform/editor/workbench/code/server] + +## Purpose + +Brief description of what this component does and why it exists. + +## Scope +- What functionality is included +- What is explicitly out of scope +- Integration points with other components + +## Architecture + +### High-Level Design +[Architectural diagram or description of key patterns used] + +### Key Classes & Interfaces +- **[ClassName]**: Brief description of responsibility +- **[InterfaceName]**: Purpose and main methods +- **[ServiceName]**: Service responsibilities + +### Key Files +List all the key files and a brief description of their purpose: +- **`src/vs/[path/to/component]/[filename.ts]`**: [Purpose and main exports] +- **`src/vs/[path/to/component]/[service.ts]`**: [Service implementation details] +- **`src/vs/[path/to/component]/[contribution.ts]`**: [Workbench contributions] + +## Development Guidelines + +- Reserve a section for any specific development practices or patterns relevant to this component. These will be edited by a developer or agent as needed. + +--- + + + +- **Create** a new overview file if one is not specified: `.components/[component-name].md` +- **Fill** each section with component-specific details +- **Gather** information from the attached context and use available tools if needed to complete your understanding +- **Ask** the user for clarification if you cannot fill out a section with accurate information +- **Use complete file paths** from repository root (e.g., `src/vs/workbench/services/example/browser/exampleService.ts`) +- **Keep** descriptions concise but comprehensive +- **Use file references** instead of code snippets when making references to code as otherwise the code may become outdated + diff --git a/.github/prompts/doc-comments.prompt.md b/.github/prompts/doc-comments.prompt.md new file mode 100644 index 0000000000000..b384843590111 --- /dev/null +++ b/.github/prompts/doc-comments.prompt.md @@ -0,0 +1,30 @@ +--- +agent: agent +description: 'Update doc comments' +tools: ['edit', 'search', 'new', 'runCommands', 'runTasks', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'extensions', 'todos', 'runTests'] +--- +# Role + +You are an expert technical documentation editor specializing in public API documentation. + +## Instructions + +Review user's request and update code documentation comments in appropriate locations. + +## Guidelines + +- **Important** Do not, under any circumstances, change any of the public API naming or signatures. +- **Important** Fetch and review relevant code context (i.e. implementation source code) before making changes or adding comments. +- **Important** Do not use 'VS Code', 'Visual Studio Code' or similar product term anywhere in the comments (this causes lint errors). +- Follow American English grammar, orthography, and punctuation. +- Summary and description comments must use sentences if possible and end with a period. +- Use {@link \} where possible **and reasonable** to refer to code symbols. +- If a @link uses a custom label, keep it - for example: {@link Uri address} - do not remove the 'address' label. +- Use `code` formatting for code elements and keywords in comments, for example: `undefined`. +- Limit the maximum line length of comments to 120 characters. + +## Cleanup Mode + +If the user instructed you to "clean up" doc comments (e.g. by passing in "cleanup" as their prompt), +it is **very important** that you limit your changes to only fixing grammar, punctuation, formatting, and spelling mistakes. +**YOU MUST NOT** add new or remove or expand existing comments in cleanup mode. diff --git a/.github/prompts/find-duplicates.prompt.md b/.github/prompts/find-duplicates.prompt.md new file mode 100644 index 0000000000000..7bda0fd83afcc --- /dev/null +++ b/.github/prompts/find-duplicates.prompt.md @@ -0,0 +1,16 @@ +--- +# NOTE: This prompt is intended for internal use only for now. +agent: Engineering +argument-hint: Provide a link or issue number to find duplicates for +description: Find duplicates for a VS Code GitHub issue +model: Claude Sonnet 4.5 (copilot) +tools: + - execute/getTerminalOutput + - execute/runInTerminal + - github/* + - agent/runSubagent +--- + +## Your Task +1. Use the GitHub MCP server to retrieve the prompt file https://github.com/microsoft/vscode-engineering/blob/main/.github/prompts/find-duplicates-gh-cli.prompt.md. +2. Follow those instructions PRECISELY to identify potential duplicate issues for a given issue number in the VS Code repository. diff --git a/.github/prompts/find-issue.prompt.md b/.github/prompts/find-issue.prompt.md new file mode 100644 index 0000000000000..dfdfdd56b69cb --- /dev/null +++ b/.github/prompts/find-issue.prompt.md @@ -0,0 +1,14 @@ +--- +# ⚠️: Internal use only. To onboard, follow instructions at https://github.com/microsoft/vscode-engineering/blob/main/docs/gh-mcp-onboarding.md +agent: Engineering +model: Claude Sonnet 4.5 (copilot) +argument-hint: Describe your issue. Include relevant keywords or phrases. +description: Search for an existing VS Code GitHub issue +tools: + - github/* + - agent/runSubagent +--- + +## Your Task +1. Use the GitHub MCP server to retrieve the prompt file https://github.com/microsoft/vscode-engineering/blob/main/.github/prompts/find-issue.prompt.md. +2. Follow those instructions PRECISELY to find issues related to the issue description provided. Perform your search in the `vscode` repository. diff --git a/.github/prompts/fixIssueNo.prompt.md b/.github/prompts/fixIssueNo.prompt.md new file mode 100644 index 0000000000000..22130f14047bf --- /dev/null +++ b/.github/prompts/fixIssueNo.prompt.md @@ -0,0 +1,8 @@ +--- +agent: Plan +tools: ['runCommands', 'runTasks', 'runNotebooks', 'search', 'new', 'usages', 'vscodeAPI', 'problems', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'todos', 'runTests', 'github/get_issue', 'github/get_issue_comments', 'github/get_me', 'github/get_pull_request', 'github/get_pull_request_diff', 'github/get_pull_request_files'] +--- + +The user has given you a Github issue number. Use the `get_issue` to retrieve its details. Understand the issue and propose a solution to solve it. + +NEVER share any thinking process or status updates before you have your solution. diff --git a/.github/prompts/implement.prompt.md b/.github/prompts/implement.prompt.md index c0118055d0c61..4deb7ba76d791 100644 --- a/.github/prompts/implement.prompt.md +++ b/.github/prompts/implement.prompt.md @@ -1,7 +1,7 @@ --- -mode: agent -description: 'Implement the solution for a problem.' -tools: ['changes', 'codebase', 'editFiles', 'fetch', 'findTestFiles', 'openSimpleBrowser', 'problems', 'readNotebookCellOutput', 'runCommands', 'runNotebooks', 'runTasks', 'runTests', 'search', 'searchResults', 'terminalLastCommand', 'terminalSelection', 'testFailure', 'usages', 'vscodeAPI'] +agent: agent +description: 'Implement the plan' +tools: ['edit', 'runNotebooks', 'search', 'new', 'runCommands', 'runTasks', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'extensions', 'todos', 'runTests'] --- Please write a high quality, general purpose solution. Implement a solution that works correctly for all valid inputs, not just the test cases. Do not hard-code values or create solutions that only work for specific test inputs. Instead, implement the actual logic that solves the problem generally. diff --git a/.github/prompts/issue-grouping.prompt.md b/.github/prompts/issue-grouping.prompt.md new file mode 100644 index 0000000000000..8f6bfea76601f --- /dev/null +++ b/.github/prompts/issue-grouping.prompt.md @@ -0,0 +1,22 @@ +--- +agent: Engineering +model: Claude Sonnet 4.5 (copilot) +argument-hint: Give an assignee and or a label/labels. Issues with that assignee and label will be fetched and grouped. +description: Group similar issues. +tools: + - github/search_issues + - agent/runSubagent + - edit/createFile + - edit/editFiles + - read/readFile +--- + +## Your Task +1. Use a subagent to: + a. Using the GitHub MCP server, fetch only one page (50 per page) of the open issues for the given assignee and label in the `vscode` repository. + b. After fetching a single page, look through the issues and see if there are are any good grouping categories.Output the categories as headers to a local file categorized-issues.md. Do NOT fetch more issue pages yet, make sure to write the categories to the file first. +2. Repeat step 1 (sequentially, don't parallelize) until all pages are fetched and categories are written to the file. +3. Use a subagent to Re-fetch only one page of the issues for the given assignee and label in the `vscode` repository. Write each issue into the categorized-issues.md file under the appropriate category header with a link and the number of upvotes. If an issue doesn't fit into any category, put it under an "Other" category. +4. Repeat step 3 (sequentially, don't parallelize) until all pages are fetched and all issues are written to the file. +5. Within each category, sort the issues by number of upvotes in descending order. +6. Show the categorized-issues.md file as the final output. diff --git a/.github/prompts/micro-perf.prompt.md b/.github/prompts/micro-perf.prompt.md new file mode 100644 index 0000000000000..dbfad2dfc27d6 --- /dev/null +++ b/.github/prompts/micro-perf.prompt.md @@ -0,0 +1,26 @@ +--- +agent: agent +description: 'Optimize code performance' +tools: ['edit', 'search', 'new', 'runCommands', 'runTasks', 'usages', 'vscodeAPI', 'problems', 'changes', 'testFailure', 'openSimpleBrowser', 'fetch', 'githubRepo', 'extensions', 'todos', 'runTests'] +--- +# Role + +You are an expert performance engineer. + +## Instructions + +Review the attached file and find all publicly exported class or functions. +Optimize performance of all exported definitions. +If the user provided explicit list of classes or functions to optimize, scope your work only to those definitions. + +## Guidelines + +1. Make sure to analyze usage and calling patterns for each function you optimize. +2. When you need to change a function or a class, add optimized version of it immediately below the existing definition instead of changing the original. +3. Optimized function or class name should have the same name as original with '_new' suffix. +4. Create a file with '..perf.js' suffix with perf tests. For example if you are using model 'Foo' and optimizing file name utils.ts, you will create file named 'utils.foo.perf.js'. +5. **IMPORTANT**: You should use ESM format for the perf test files (i.e. use 'import' instead of 'require'). +6. The perf tests should contain comprehensive perf tests covering identified scenarios and common cases, and comparing old and new implementations. +7. The results of perf tests and your summary should be placed in another file with '..perf.md' suffix, for example 'utils.foo.perf.md'. +8. The results file must include section per optimized definition with a table with comparison of old vs new implementations with speedup ratios and analysis of results. +9. At the end ask the user if they want to apply the changes and if the answer is yes, replace original implementations with optimized versions but only in cases where there are significant perf gains and no serious regressions. Revert any other changes to the original code. diff --git a/.github/prompts/migrate.prompt.md b/.github/prompts/migrate.prompt.md new file mode 100644 index 0000000000000..d404ebf6f4bee --- /dev/null +++ b/.github/prompts/migrate.prompt.md @@ -0,0 +1,184 @@ +--- +agent: agent +tools: + [ + "github/add_issue_comment", + "github/get_label", + "github/get_me", + "github/issue_read", + "github/issue_write", + "github/search_issues", + "github/search_pull_requests", + "github/search_repositories", + "github/sub_issue_write", + ] +--- + +# Issue Migration Prompt + +Use this prompt when migrating issues from one GitHub repository to another (e.g., from `microsoft/vscode-copilot` to `microsoft/vscode`). + +## Input Methods + +You can specify which issues to migrate using **any** of these three methods: + +### Option A: GitHub Search Query URL + +Provide a full GitHub issues search URL. **All matching issues will be migrated.** + +``` +https://github.com/microsoft/vscode-copilot/issues?q=is%3Aissue+is%3Aopen+assignee%3Ayoyokrazy +``` + +### Option B: GitHub Search Query Parameters + +Provide search query syntax for a specific repo. **All matching issues will be migrated.** + +``` +repo:microsoft/vscode-copilot is:issue is:open assignee:yoyokrazy +``` + +Common query filters: + +- `is:issue` / `is:pr` - Filter by type +- `is:open` / `is:closed` - Filter by state +- `assignee:USERNAME` - Filter by assignee +- `author:USERNAME` - Filter by author +- `label:LABEL` - Filter by label +- `milestone:MILESTONE` - Filter by milestone + +### Option C: Specific Issue URL + +Provide a direct link to a single issue. **Only this issue will be migrated.** + +``` +https://github.com/microsoft/vscode-copilot/issues/12345 +``` + +## Task + +**Target Repository:** `{TARGET_REPO}` + +Based on the input provided, migrate the issue(s) to the target repository following all requirements below. + +## Requirements + +### 1. Issue Body Format + +Create the new issue with this header format: + +```markdown +_Transferred from {SOURCE_REPO}#{ORIGINAL_ISSUE_NUMBER}_ +_Original author: `@{ORIGINAL_AUTHOR}`_ + +--- + +{ORIGINAL_ISSUE_BODY} +``` + +### 2. Comment Migration + +For each comment on the original issue, add a comment to the new issue: + +```markdown +_`@{COMMENT_AUTHOR}` commented:_ + +--- + +{COMMENT_BODY} +``` + +### 3. CRITICAL: Preventing GitHub Pings + +**ALL `@username` mentions MUST be wrapped in backticks to prevent GitHub from sending notifications.** + +✅ Correct: `` `@username` `` +❌ Wrong: `@username` + +This applies to: + +- The "Original author" line in the issue body +- Any `@mentions` within the issue body content +- The comment author attribution line +- Any `@mentions` within comment content +- Any quoted content that contains `@mentions` + +### 4. CRITICAL: Issue/PR Link Reformatting + +**Issue references like `#12345` are REPO-SPECIFIC.** If you copy `#12345` from the source repo to the target repo, it will incorrectly link to issue 12345 in the _target_ repo instead of the source. + +**Convert ALL `#NUMBER` references to full URLs:** + +✅ Correct: `https://github.com/microsoft/vscode-copilot/issues/12345` +✅ Also OK: `microsoft/vscode-copilot#12345` +❌ Wrong: `#12345` (will link to wrong repo) + +This applies to: + +- Issue references in the body (`#12345` → full URL) +- PR references in the body (`#12345` → full URL) +- References in comments +- References in quoted content +- References in image alt text or links + +**Exception:** References that are _already_ full URLs should be left unchanged. + +### 5. Metadata Preservation + +- Copy all applicable labels to the new issue +- Assign the new issue to the same assignees (if they exist in target repo) +- Preserve the issue title exactly + +### 5. Post-Migration + +After creating the new issue and all comments: + +- Add a comment to the **original** issue linking to the new issue: + ```markdown + Migrated to {TARGET_REPO}#{NEW_ISSUE_NUMBER} + ``` +- Close the original issue as not_planned + +## Example Transformation + +### Original Issue Body (in `microsoft/vscode-copilot`): + +```markdown +I noticed @johndoe had a similar issue in #9999. cc @janedoe for visibility. + +Related to #8888 and microsoft/vscode#12345. + +Steps to reproduce: + +1. Open VS Code +2. ... +``` + +### Migrated Issue Body (in `microsoft/vscode`): + +```markdown +_Transferred from microsoft/vscode-copilot#12345_ +_Original author: `@originalauthor`_ + +--- + +I noticed `@johndoe` had a similar issue in https://github.com/microsoft/vscode-copilot/issues/9999. cc `@janedoe` for visibility. + +Related to https://github.com/microsoft/vscode-copilot/issues/8888 and microsoft/vscode#12345. + +Steps to reproduce: + +1. Open VS Code +2. ... +``` + +Note: The `microsoft/vscode#12345` reference was already a cross-repo link, so it stays unchanged. + +## Checklist Before Migration + +- [ ] Confirm input method (query URL, query params, or specific issue URL) +- [ ] Confirm target repository +- [ ] If using query: verify the query returns the expected issues +- [ ] Verify all `@mentions` are wrapped in backticks +- [ ] Verify all `#NUMBER` references are converted to full URLs +- [ ] Decide whether to close original issues after migration diff --git a/.github/prompts/no-any.prompt.md b/.github/prompts/no-any.prompt.md new file mode 100644 index 0000000000000..7e78fefa27e3d --- /dev/null +++ b/.github/prompts/no-any.prompt.md @@ -0,0 +1,12 @@ +--- +agent: agent +description: 'Remove any usage of the any type in TypeScript files' +--- + +I am trying to minimize the usage of `any` types in our TypeScript codebase. +Find usages of the TypeScript `any` type in this file and replace it with the right type based on usages in the file. + +You are NOT allowed to disable ESLint rules or add `// @ts-ignore` comments to the code. +You are NOT allowed to add more `any` types to the code even if you think it is necessary or they are legitimate. + +If there are tests associated to the changes you made, please run those tests to ensure everything is working correctly diff --git a/.github/prompts/plan-deep.prompt.md b/.github/prompts/plan-deep.prompt.md new file mode 100644 index 0000000000000..a321fbe2cbb86 --- /dev/null +++ b/.github/prompts/plan-deep.prompt.md @@ -0,0 +1,9 @@ +--- +agent: Plan +description: Clarify before planning in more detail +--- +Before doing your research workflow, gather preliminary context using #runSubagent (instructed to use max 5 tool calls) to get a high-level overview. + +Then ask 3 clarifying questions and PAUSE for the user to answer them. + +AFTER the user has answered, start the . Add extra details to your planning draft. diff --git a/.github/prompts/plan-fast.prompt.md b/.github/prompts/plan-fast.prompt.md new file mode 100644 index 0000000000000..2ae8191933a58 --- /dev/null +++ b/.github/prompts/plan-fast.prompt.md @@ -0,0 +1,5 @@ +--- +agent: Plan +description: Iterate quicker on simple tasks +--- +Planning for faster iteration: Research as usual, but draft a much more shorter implementation plan that focused on just the main steps diff --git a/.github/prompts/plan.prompt.md b/.github/prompts/plan.prompt.md index 85b45cdcf2fab..e94ee7b24ebc5 100644 --- a/.github/prompts/plan.prompt.md +++ b/.github/prompts/plan.prompt.md @@ -1,19 +1,5 @@ --- -mode: agent -description: 'Plan the solution for a problem.' -tools: ['codebase', 'fetch', 'findTestFiles', 'githubRepo', 'get_issue', 'get_issue_comments', 'get_me', 'search', 'searchResults', 'usages', 'vscodeAPI'] +agent: Plan +description: 'Start planning' --- -Your goal is to prepare a detailed plan to fix the bug or add the new feature, for this you first need to: -* Understand the context of the bug or feature by reading the issue description and comments. -* Understand the codebase by reading the relevant instruction files. -* If its a bug, then identify the root cause of the bug, and explain this to the user. - -Based on your above understanding generate a plan to fix the bug or add the new feature. -Ensure the plan consists of a Markdown document that has the following sections: - -* Overview: A brief description of the bug/feature. -* Root Cause: A detailed explanation of the root cause of the bug, including any relevant code snippets or references to the codebase. (only if it's a bug) -* Requirements: A list of requirements to resolve the bug or add the new feature. -* Implementation Steps: A detailed list of steps to implement the bug fix or new feature. - -Remember, do not make any code edits, just generate a plan. Use thinking and reasoning skills to outline the steps needed to achieve the desired outcome. +Start planning. diff --git a/.github/prompts/setup-environment.prompt.md b/.github/prompts/setup-environment.prompt.md new file mode 100644 index 0000000000000..fa934ad702904 --- /dev/null +++ b/.github/prompts/setup-environment.prompt.md @@ -0,0 +1,82 @@ +--- +agent: agent +description: First Time Setup +tools: ['runCommands', 'runTasks/runTask', 'search', 'todos', 'fetch'] +--- + +# Role +You are my setup automation assistant. Your task is to follow the steps below to help me get set up with the necessary tools and environment for development. Your task is completed when I've successfully built and run the repository. Use a TODO to track progress. + +# Steps +1. Find setup instructions in README.md and CONTRIBUTING.md at the root of the repository. Fetch any other documentation they recommend. + +2. Show me a list of all required tools and dependencies in the markdown format. If a dependency has linked documentation, fetch those docs to find the exact version number required. Remember that link and that version for step 4. Do not display system requirements. + +## 🛠️ Required Tools +- **Node.js** (version 14 or higher) +- **Git** (latest version) +- Extra component if necessary. + + +3. Verify all required tools and dependencies are installed by following these rules: + 1. For all tools that should exist on the PATH, check their versions. `toolA --version; toolB --version; [...] toolZ --version` + 2. For tools not traditionally on the PATH: + 1. Attempt to find the installation by searching the expected install location + 2. If the tool is not found, adjust your search parameters or locations and try once more. Consider if the tool is installed with a package manager. + 3. If the second location fails, mark it as missing. + +4. Display a summary of what I have and what I need to install. In the markdown format. If a section is empty, omit it. + + +## Installation Summary + +### ✅ Already Installed +- Node.js (version 16.13.0) ⚠️ Note: You have X version but this project specifies Y. + +### ❌ Not Installed +- ❌ Git (need version 2.30 or higher) + - [Link to downloads page] + +### ❓ Unable to Verify +- ToolName - [Reason why it couldn't be verified] + - [Manual verification instructions steps] + + +5. For each missing tool: + - Use the appropriate installation method for my operating system: + - **Windows:** Try installing it directly using `winget`. + - Example: `winget install --id Git.Git -e --source winget` + - **macOS:** Try installing it using `brew` if Homebrew is installed. + - Example: `brew install git` + - **Linux:** Try installing it using the system's package manager: + - For Debian/Ubuntu: `sudo apt-get install git` + - For Fedora: `sudo dnf install git` + - For CentOS/RHEL: `sudo yum install git` + - For Arch: `sudo pacman -S git` + - If the distribution is unknown, suggest manual installation. + - You MUST install the required versions found in step 2. + - For tools that may be managed by version managers (like `Node.js`), try installing them using the version manager if installed. + - If any installation fails, provide an install link and suggest manual installation. + - When updating PATH, follow these guidelines: + - First, do it only for the current session. + - Once installation is verified, add it permanently to the PATH. + - Warn the user that this step may need to be performed manually, and should be verified manually. Provide simple steps to do so. + - If a restart may be required, remind the user. + +6. If any tools were installed, show an installation summary. Otherwise, skip this step. +7. Provide steps on building the repository, and then perform those steps. +8. If the repository is an application: + - Provide steps on running the application + - Try to run the application via a launch configuration if it exists, otherwise try running it yourself. +9. Show me a recap of what was newly installed. +10. Finally, update the README.md or CONTRIBUTING.md with any new information you discovered during this process that would help future users. + +# Guidelines + +- Instead of displaying commands to run, execute them directly. +- Output in markdown for human readability. +- Skip optional tooling. +- Keep all responses specific to my operating system. +- IMPORTANT: Documentation may be out of date. Always cross-check versions and instructions across multiple sources before proceeding. Update relevant files to the latest information as needed. +- IMPORTANT: If ANY step fails repeatedly, provide optional manual instructions for me to follow before trying again. +- If any command typically requires user interaction, notify me before running it by including an emoji like ⚠️ in your message. diff --git a/.github/prompts/update-instructions.prompt.md b/.github/prompts/update-instructions.prompt.md new file mode 100644 index 0000000000000..b38001671ef26 --- /dev/null +++ b/.github/prompts/update-instructions.prompt.md @@ -0,0 +1,17 @@ +--- +agent: agent +--- + +Read the changes introduced on the current branch, including BOTH: + +1. Uncommitted workspace modifications (staged and unstaged) +2. Committed changes that are on the current HEAD but not yet in the default upstream branch (e.g. `origin/main`) + +Guidance: + +- First, capture uncommitted diffs (equivalent of `git diff` and `git diff --cached`). +- Then, determine the merge base with the default branch (assume `origin/main` unless configured otherwise) using `git merge-base HEAD origin/main` and diff (`git diff ...HEAD`) to include committed-but-unpushed work. + +After understanding all of these changes, read every instruction file under `.github/instructions` and assess whether any instruction is invalidated. If so, propose minimal, necessary wording updates. If no updates are needed, respond exactly with: `No updates needed`. + +Be concise and conservative: only suggest changes that are absolutely necessary. diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 0000000000000..7352ce957ddc6 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,260 @@ +name: "Copilot Setup Steps" + +# Automatically run the setup steps when they are changed to allow for easy validation, and +# allow manual testing through the repository's "Actions" tab +on: + workflow_dispatch: + push: + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + copilot-setup-steps: + runs-on: vscode-large-runners + + # Set the permissions to the lowest permissions possible needed for your steps. + # Copilot will be given its own token for its operations. + permissions: + # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. + contents: read + + # You can define any steps you want, and they will run before the agent starts. + # If you do not check out your code, Copilot will do this for you. + steps: + - name: Checkout microsoft/vscode + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + + - name: Setup system services + run: | + set -e + # Start X server + ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get update + ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get install -y pkg-config \ + xvfb \ + libgtk-3-0 \ + libxkbfile-dev \ + libkrb5-dev \ + libgbm1 \ + rpm + sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + sudo chmod +x /etc/init.d/xvfb + sudo update-rc.d xvfb defaults + sudo service xvfb start + + - name: Prepare node_modules cache key + run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts linux x64 $(node -p process.arch) > .build/packagelockhash + + - name: Restore node_modules cache + id: cache-node-modules + uses: actions/cache/restore@v5 + with: + path: .build/node_modules_cache + key: "node_modules-linux-${{ hashFiles('.build/packagelockhash') }}" + + - name: Extract node_modules cache + if: steps.cache-node-modules.outputs.cache-hit == 'true' + run: tar -xzf .build/node_modules_cache/cache.tgz + + - name: Install build dependencies + if: steps.cache-node-modules.outputs.cache-hit != 'true' + working-directory: build + run: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install dependencies + if: steps.cache-node-modules.outputs.cache-hit != 'true' + run: | + set -e + + source ./build/azure-pipelines/linux/setup-env.sh + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + npm_config_arch: x64 + VSCODE_ARCH: x64 + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create node_modules archive + if: steps.cache-node-modules.outputs.cache-hit != 'true' + run: | + set -e + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + + - name: Create .build folder + run: mkdir -p .build + + - name: Prepare built-in extensions cache key + run: node build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts > .build/builtindepshash + + - name: Restore built-in extensions cache + id: cache-builtin-extensions + uses: actions/cache/restore@v5 + with: + enableCrossOsArchive: true + path: .build/builtInExtensions + key: "builtin-extensions-${{ hashFiles('.build/builtindepshash') }}" + + - name: Download built-in extensions + if: steps.cache-builtin-extensions.outputs.cache-hit != 'true' + run: node build/lib/builtInExtensions.ts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # - name: Transpile client and extensions + # run: npm run gulp transpile-client-esbuild transpile-extensions + + - name: Download Electron and Playwright + run: | + set -e + + for i in {1..3}; do # try 3 times (matching retryCountOnTaskFailure: 3) + if npm exec -- npm-run-all -lp "electron x64" "playwright-install"; then + echo "Download successful on attempt $i" + break + fi + + if [ $i -eq 3 ]; then + echo "Download failed after 3 attempts" >&2 + exit 1 + fi + + echo "Download failed on attempt $i, retrying..." + sleep 5 # optional: add a small delay between retries + done + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # - name: 🧪 Run unit tests (Electron) + # if: ${{ inputs.electron_tests }} + # timeout-minutes: 15 + # run: ./scripts/test.sh --tfs "Unit Tests" + # env: + # DISPLAY: ":10" + + # - name: 🧪 Run unit tests (node.js) + # if: ${{ inputs.electron_tests }} + # timeout-minutes: 15 + # run: npm run test-node + + # - name: 🧪 Run unit tests (Browser, Chromium) + # if: ${{ inputs.browser_tests }} + # timeout-minutes: 30 + # run: npm run test-browser-no-install -- --browser chromium --tfs "Browser Unit Tests" + # env: + # DEBUG: "*browser*" + + # - name: Build integration tests + # run: | + # set -e + # npm run gulp \ + # compile-extension:configuration-editing \ + # compile-extension:css-language-features-server \ + # compile-extension:emmet \ + # compile-extension:git \ + # compile-extension:github-authentication \ + # compile-extension:html-language-features-server \ + # compile-extension:ipynb \ + # compile-extension:notebook-renderers \ + # compile-extension:json-language-features-server \ + # compile-extension:markdown-language-features \ + # compile-extension-media \ + # compile-extension:microsoft-authentication \ + # compile-extension:typescript-language-features \ + # compile-extension:vscode-api-tests \ + # compile-extension:vscode-colorize-tests \ + # compile-extension:vscode-colorize-perf-tests \ + # compile-extension:vscode-test-resolver + + # - name: 🧪 Run integration tests (Electron) + # if: ${{ inputs.electron_tests }} + # timeout-minutes: 20 + # run: ./scripts/test-integration.sh --tfs "Integration Tests" + # env: + # DISPLAY: ":10" + + # - name: 🧪 Run integration tests (Browser, Chromium) + # if: ${{ inputs.browser_tests }} + # timeout-minutes: 20 + # run: ./scripts/test-web-integration.sh --browser chromium + + # - name: 🧪 Run integration tests (Remote) + # if: ${{ inputs.remote_tests }} + # timeout-minutes: 20 + # run: ./scripts/test-remote-integration.sh + # env: + # DISPLAY: ":10" + + # - name: Compile smoke tests + # working-directory: test/smoke + # run: npm run compile + + # - name: Compile extensions for smoke tests + # run: npm run gulp compile-extension-media + + # - name: Diagnostics before smoke test run (processes, max_user_watches, number of opened file handles) + # run: | + # set -e + # ps -ef + # cat /proc/sys/fs/inotify/max_user_watches + # lsof | wc -l + # continue-on-error: true + # if: always() + + # - name: 🧪 Run smoke tests (Electron) + # if: ${{ inputs.electron_tests }} + # timeout-minutes: 20 + # run: npm run smoketest-no-compile -- --tracing + # env: + # DISPLAY: ":10" + + # - name: 🧪 Run smoke tests (Browser, Chromium) + # if: ${{ inputs.browser_tests }} + # timeout-minutes: 20 + # run: npm run smoketest-no-compile -- --web --tracing --headless + + # - name: 🧪 Run smoke tests (Remote) + # if: ${{ inputs.remote_tests }} + # timeout-minutes: 20 + # run: npm run smoketest-no-compile -- --remote --tracing + # env: + # DISPLAY: ":10" + + # - name: Diagnostics after smoke test run (processes, max_user_watches, number of opened file handles) + # run: | + # set -e + # ps -ef + # cat /proc/sys/fs/inotify/max_user_watches + # lsof | wc -l + # continue-on-error: true + # if: always() diff --git a/.github/workflows/monaco-editor.yml b/.github/workflows/monaco-editor.yml index 56c30d0ba7428..822210da8d0f7 100644 --- a/.github/workflows/monaco-editor.yml +++ b/.github/workflows/monaco-editor.yml @@ -19,20 +19,20 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: persist-credentials: false - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc - name: Compute node modules cache key id: nodeModulesCacheKey - run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" >> $GITHUB_OUTPUT + run: echo "value=$(node build/azure-pipelines/common/computeNodeModulesCacheKey.ts)" >> $GITHUB_OUTPUT - name: Cache node modules id: cacheNodeModules - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: "**/node_modules" key: ${{ runner.os }}-cacheNodeModules20-${{ steps.nodeModulesCacheKey.outputs.value }} @@ -43,7 +43,7 @@ jobs: run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT - name: Cache npm directory if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ${{ steps.npmCacheDirPath.outputs.dir }} key: ${{ runner.os }}-npmCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} diff --git a/.github/workflows/no-engineering-system-changes.yml b/.github/workflows/no-engineering-system-changes.yml new file mode 100644 index 0000000000000..45d1ae55f623b --- /dev/null +++ b/.github/workflows/no-engineering-system-changes.yml @@ -0,0 +1,50 @@ +name: Prevent engineering system changes in PRs + +on: pull_request +permissions: {} + +jobs: + main: + name: Prevent engineering system changes in PRs + runs-on: ubuntu-latest + steps: + - name: Get file changes + uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b # v1.2.4 + id: file_changes + - name: Check if engineering systems were modified + id: engineering_systems_check + run: | + if cat $HOME/files.json | jq -e 'any(test("^\\.github\\/workflows\\/|^build\\/|package\\.json$"))' > /dev/null; then + echo "engineering_systems_modified=true" >> $GITHUB_OUTPUT + echo "Engineering systems were modified in this PR" + else + echo "engineering_systems_modified=false" >> $GITHUB_OUTPUT + echo "No engineering systems were modified in this PR" + fi + - name: Prevent Copilot from modifying engineering systems + if: ${{ steps.engineering_systems_check.outputs.engineering_systems_modified == 'true' && github.event.pull_request.user.login == 'Copilot' }} + run: | + echo "Copilot is not allowed to modify .github/workflows, build folder files, or package.json files." + echo "If you need to update engineering systems, please do so manually or through authorized means." + exit 1 + - uses: octokit/request-action@dad4362715b7fb2ddedf9772c8670824af564f0d # v2.4.0 + id: get_permissions + if: ${{ steps.engineering_systems_check.outputs.engineering_systems_modified == 'true' && github.event.pull_request.user.login != 'Copilot' }} + with: + route: GET /repos/microsoft/vscode/collaborators/${{ github.event.pull_request.user.login }}/permission + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set control output variable + id: control + if: ${{ steps.engineering_systems_check.outputs.engineering_systems_modified == 'true' && github.event.pull_request.user.login != 'Copilot' }} + run: | + echo "user: ${{ github.event.pull_request.user.login }}" + echo "role: ${{ fromJson(steps.get_permissions.outputs.data).permission }}" + echo "is dependabot: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}" + echo "should_run: ${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}" + echo "should_run=${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) && github.event.pull_request.user.login != 'dependabot[bot]' }}" >> $GITHUB_OUTPUT + - name: Check for engineering system changes + if: ${{ steps.engineering_systems_check.outputs.engineering_systems_modified == 'true' && steps.control.outputs.should_run == 'true' }} + run: | + echo "Changes to .github/workflows/, build/ folder files, or package.json files aren't allowed in PRs." + exit 1 diff --git a/.github/workflows/no-yarn-lock-changes.yml b/.github/workflows/no-yarn-lock-changes.yml deleted file mode 100644 index 5727d1c511cf6..0000000000000 --- a/.github/workflows/no-yarn-lock-changes.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Prevent yarn.lock changes in PRs - -on: pull_request -permissions: {} - -jobs: - main: - name: Prevent yarn.lock changes in PRs - runs-on: ubuntu-latest - steps: - - name: Get file changes - uses: trilom/file-changes-action@a6ca26c14274c33b15e6499323aac178af06ad4b # v1.2.4 - id: file_changes - - name: Check if lockfiles were modified - id: lockfile_check - run: | - if cat $HOME/files.json | jq -e 'any(test("yarn\\.lock$|Cargo\\.lock$"))' > /dev/null; then - echo "lockfiles_modified=true" >> $GITHUB_OUTPUT - echo "Lockfiles were modified in this PR" - else - echo "lockfiles_modified=false" >> $GITHUB_OUTPUT - echo "No lockfiles were modified in this PR" - fi - - name: Prevent Copilot from modifying lockfiles - if: ${{ steps.lockfile_check.outputs.lockfiles_modified == 'true' && github.event.pull_request.user.login == 'Copilot' }} - run: | - echo "Copilot is not allowed to modify yarn.lock or Cargo.lock files." - echo "If you need to update dependencies, please do so manually or through authorized means." - exit 1 - - uses: octokit/request-action@dad4362715b7fb2ddedf9772c8670824af564f0d # v2.4.0 - id: get_permissions - if: ${{ steps.lockfile_check.outputs.lockfiles_modified == 'true' && github.event.pull_request.user.login != 'Copilot' }} - with: - route: GET /repos/microsoft/vscode/collaborators/{username}/permission - username: ${{ github.event.pull_request.user.login }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Set control output variable - id: control - if: ${{ steps.lockfile_check.outputs.lockfiles_modified == 'true' && github.event.pull_request.user.login != 'Copilot' }} - run: | - echo "user: ${{ github.event.pull_request.user.login }}" - echo "role: ${{ fromJson(steps.get_permissions.outputs.data).permission }}" - echo "is dependabot: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }}" - echo "should_run: ${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) }}" - echo "should_run=${{ !contains(fromJson('["admin", "maintain", "write"]'), fromJson(steps.get_permissions.outputs.data).permission) && github.event.pull_request.user.login != 'dependabot[bot]' }}" >> $GITHUB_OUTPUT - - name: Check for lockfile changes - if: ${{ steps.lockfile_check.outputs.lockfiles_modified == 'true' && steps.control.outputs.should_run == 'true' }} - run: | - echo "Changes to yarn.lock/Cargo.lock files aren't allowed in PRs." - exit 1 diff --git a/.github/workflows/pr-darwin-test.yml b/.github/workflows/pr-darwin-test.yml index a6f6912cd39b1..c946793851b84 100644 --- a/.github/workflows/pr-darwin-test.yml +++ b/.github/workflows/pr-darwin-test.yml @@ -24,21 +24,19 @@ jobs: VSCODE_ARCH: arm64 steps: - name: Checkout microsoft/vscode - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key - run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js darwin $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash + run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts darwin $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash - name: Restore node_modules cache id: cache-node-modules - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: .build/node_modules_cache key: "node_modules-macos-${{ hashFiles('.build/packagelockhash') }}" @@ -79,7 +77,7 @@ jobs: if: steps.cache-node-modules.outputs.cache-hit != 'true' run: | set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt mkdir -p .build/node_modules_cache tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt @@ -87,11 +85,11 @@ jobs: run: mkdir -p .build - name: Prepare built-in extensions cache key - run: node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash + run: node build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts > .build/builtindepshash - name: Restore built-in extensions cache id: cache-builtin-extensions - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: enableCrossOsArchive: true path: .build/builtInExtensions @@ -99,7 +97,7 @@ jobs: - name: Download built-in extensions if: steps.cache-builtin-extensions.outputs.cache-hit != 'true' - run: node build/lib/builtInExtensions.js + run: node build/lib/builtInExtensions.ts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -214,7 +212,7 @@ jobs: if: always() - name: Publish Crash Reports - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: failure() continue-on-error: true with: @@ -225,7 +223,7 @@ jobs: # In order to properly symbolify above crash reports # (if any), we need the compiled native modules too - name: Publish Node Modules - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: failure() continue-on-error: true with: @@ -234,7 +232,7 @@ jobs: if-no-files-found: ignore - name: Publish Log Files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: always() continue-on-error: true with: diff --git a/.github/workflows/pr-linux-cli-test.yml b/.github/workflows/pr-linux-cli-test.yml index 7466c639cae8d..003e1344fb6c7 100644 --- a/.github/workflows/pr-linux-cli-test.yml +++ b/.github/workflows/pr-linux-cli-test.yml @@ -16,7 +16,7 @@ jobs: RUSTUP_TOOLCHAIN: ${{ inputs.rustup_toolchain }} steps: - name: Checkout microsoft/vscode - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Install Rust run: | diff --git a/.github/workflows/pr-linux-test.yml b/.github/workflows/pr-linux-test.yml index 323f72348a919..787fd4082cd93 100644 --- a/.github/workflows/pr-linux-test.yml +++ b/.github/workflows/pr-linux-test.yml @@ -24,14 +24,12 @@ jobs: VSCODE_ARCH: x64 steps: - name: Checkout microsoft/vscode - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Setup system services run: | @@ -51,11 +49,11 @@ jobs: sudo service xvfb start - name: Prepare node_modules cache key - run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js linux $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash + run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts linux $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash - name: Restore node_modules cache id: cache-node-modules - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: .build/node_modules_cache key: "node_modules-linux-${{ hashFiles('.build/packagelockhash') }}" @@ -107,7 +105,7 @@ jobs: if: steps.cache-node-modules.outputs.cache-hit != 'true' run: | set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt mkdir -p .build/node_modules_cache tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt @@ -115,11 +113,11 @@ jobs: run: mkdir -p .build - name: Prepare built-in extensions cache key - run: node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash + run: node build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts > .build/builtindepshash - name: Restore built-in extensions cache id: cache-builtin-extensions - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: enableCrossOsArchive: true path: .build/builtInExtensions @@ -127,7 +125,7 @@ jobs: - name: Download built-in extensions if: steps.cache-builtin-extensions.outputs.cache-hit != 'true' - run: node build/lib/builtInExtensions.js + run: node build/lib/builtInExtensions.ts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -260,7 +258,7 @@ jobs: if: always() - name: Publish Crash Reports - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: failure() continue-on-error: true with: @@ -271,7 +269,7 @@ jobs: # In order to properly symbolify above crash reports # (if any), we need the compiled native modules too - name: Publish Node Modules - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: failure() continue-on-error: true with: @@ -280,7 +278,7 @@ jobs: if-no-files-found: ignore - name: Publish Log Files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: always() continue-on-error: true with: diff --git a/.github/workflows/pr-node-modules.yml b/.github/workflows/pr-node-modules.yml index d1da867ac870c..68e65fd129815 100644 --- a/.github/workflows/pr-node-modules.yml +++ b/.github/workflows/pr-node-modules.yml @@ -13,21 +13,19 @@ jobs: runs-on: [ self-hosted, 1ES.Pool=1es-vscode-oss-ubuntu-22.04-x64 ] steps: - name: Checkout microsoft/vscode - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key - run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js compile $(node -p process.arch) > .build/packagelockhash + run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts compile $(node -p process.arch) > .build/packagelockhash - name: Restore node_modules cache id: cache-node-modules - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .build/node_modules_cache key: "node_modules-compile-${{ hashFiles('.build/packagelockhash') }}" @@ -62,7 +60,7 @@ jobs: if: steps.cache-node-modules.outputs.cache-hit != 'true' run: | set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt mkdir -p .build/node_modules_cache tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt @@ -70,11 +68,11 @@ jobs: run: | set -e mkdir -p .build - node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash + node build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts > .build/builtindepshash - name: Restore built-in extensions cache id: cache-builtin-extensions - uses: actions/cache@v4 + uses: actions/cache@v5 with: enableCrossOsArchive: true path: .build/builtInExtensions @@ -82,7 +80,7 @@ jobs: - name: Download built-in extensions if: steps.cache-builtin-extensions.outputs.cache-hit != 'true' - run: node build/lib/builtInExtensions.js + run: node build/lib/builtInExtensions.ts env: GITHUB_TOKEN: ${{ secrets.VSCODE_OSS }} @@ -94,21 +92,19 @@ jobs: VSCODE_ARCH: x64 steps: - name: Checkout microsoft/vscode - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key - run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js linux $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash + run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts linux $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash - name: Restore node_modules cache id: cache-node-modules - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .build/node_modules_cache key: "node_modules-linux-${{ hashFiles('.build/packagelockhash') }}" @@ -128,7 +124,7 @@ jobs: echo "Npm install failed $i, trying again..." done env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.VSCODE_OSS }} - name: Install dependencies if: steps.cache-node-modules.outputs.cache-hit != 'true' @@ -156,7 +152,7 @@ jobs: if: steps.cache-node-modules.outputs.cache-hit != 'true' run: | set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt mkdir -p .build/node_modules_cache tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt @@ -168,21 +164,19 @@ jobs: VSCODE_ARCH: arm64 steps: - name: Checkout microsoft/vscode - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key - run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js darwin $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash + run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts darwin $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash - name: Restore node_modules cache id: cache-node-modules - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .build/node_modules_cache key: "node_modules-macos-${{ hashFiles('.build/packagelockhash') }}" @@ -219,7 +213,7 @@ jobs: if: steps.cache-node-modules.outputs.cache-hit != 'true' run: | set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt mkdir -p .build/node_modules_cache tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt @@ -231,23 +225,21 @@ jobs: VSCODE_ARCH: x64 steps: - name: Checkout microsoft/vscode - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key shell: pwsh run: | mkdir .build -ea 0 - node build/azure-pipelines/common/computeNodeModulesCacheKey.js win32 ${{ env.VSCODE_ARCH }} $(node -p process.arch) > .build/packagelockhash + node build/azure-pipelines/common/computeNodeModulesCacheKey.ts win32 ${{ env.VSCODE_ARCH }} $(node -p process.arch) > .build/packagelockhash - name: Restore node_modules cache - uses: actions/cache@v4 + uses: actions/cache@v5 id: node-modules-cache with: path: .build/node_modules_cache @@ -288,6 +280,6 @@ jobs: run: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - exec { node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt } + exec { node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt } exec { mkdir -Force .build/node_modules_cache } exec { 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt } diff --git a/.github/workflows/pr-win32-test.yml b/.github/workflows/pr-win32-test.yml index a4bc311b787c2..8b79e1695ebeb 100644 --- a/.github/workflows/pr-win32-test.yml +++ b/.github/workflows/pr-win32-test.yml @@ -24,23 +24,21 @@ jobs: VSCODE_ARCH: x64 steps: - name: Checkout microsoft/vscode - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key shell: pwsh run: | mkdir .build -ea 0 - node build/azure-pipelines/common/computeNodeModulesCacheKey.js win32 ${{ env.VSCODE_ARCH }} $(node -p process.arch) > .build/packagelockhash + node build/azure-pipelines/common/computeNodeModulesCacheKey.ts win32 ${{ env.VSCODE_ARCH }} $(node -p process.arch) > .build/packagelockhash - name: Restore node_modules cache - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 id: node-modules-cache with: path: .build/node_modules_cache @@ -86,7 +84,7 @@ jobs: run: | . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" - exec { node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt } + exec { node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt } exec { mkdir -Force .build/node_modules_cache } exec { 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt } @@ -96,11 +94,11 @@ jobs: - name: Prepare built-in extensions cache key shell: pwsh - run: node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash + run: node build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts > .build/builtindepshash - name: Restore built-in extensions cache id: cache-builtin-extensions - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: enableCrossOsArchive: true path: .build/builtInExtensions @@ -108,7 +106,7 @@ jobs: - name: Download built-in extensions if: steps.cache-builtin-extensions.outputs.cache-hit != 'true' - run: node build/lib/builtInExtensions.js + run: node build/lib/builtInExtensions.ts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -121,7 +119,7 @@ jobs: run: | for ($i = 1; $i -le 3; $i++) { try { - npm exec -- -- npm-run-all -lp "electron ${{ env.VSCODE_ARCH }}" "playwright-install" + npm exec -- npm-run-all -lp "electron ${{ env.VSCODE_ARCH }}" "playwright-install" break } catch { @@ -230,19 +228,19 @@ jobs: if: ${{ inputs.electron_tests }} timeout-minutes: 20 shell: pwsh - run: npm run smoketest-no-compile -- -- --tracing + run: npm run smoketest-no-compile -- --tracing - name: 🧪 Run smoke tests (Browser, Chromium) if: ${{ inputs.browser_tests }} timeout-minutes: 20 shell: pwsh - run: npm run smoketest-no-compile -- -- --web --tracing --headless + run: npm run smoketest-no-compile -- --web --tracing --headless - name: 🧪 Run smoke tests (Remote) if: ${{ inputs.remote_tests }} timeout-minutes: 20 shell: pwsh - run: npm run smoketest-no-compile -- -- --remote --tracing + run: npm run smoketest-no-compile -- --remote --tracing - name: Diagnostics after smoke test run shell: pwsh @@ -251,7 +249,7 @@ jobs: if: always() - name: Publish Crash Reports - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: failure() continue-on-error: true with: @@ -262,7 +260,7 @@ jobs: # In order to properly symbolify above crash reports # (if any), we need the compiled native modules too - name: Publish Node Modules - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: failure() continue-on-error: true with: @@ -271,7 +269,7 @@ jobs: if-no-files-found: ignore - name: Publish Log Files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 if: always() continue-on-error: true with: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b43eff9f41da4..59f9c1f427f44 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -21,21 +21,19 @@ jobs: runs-on: [ self-hosted, 1ES.Pool=1es-vscode-oss-ubuntu-22.04-x64 ] steps: - name: Checkout microsoft/vscode - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc - env: - NODEJS_ORG_MIRROR: https://github.com/joaomoreno/node-mirror/releases/download - name: Prepare node_modules cache key - run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js compile $(node -p process.arch) > .build/packagelockhash + run: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts compile $(node -p process.arch) > .build/packagelockhash - name: Restore node_modules cache id: cache-node-modules - uses: actions/cache/restore@v4 + uses: actions/cache/restore@v5 with: path: .build/node_modules_cache key: "node_modules-compile-${{ hashFiles('.build/packagelockhash') }}" @@ -70,19 +68,16 @@ jobs: if: steps.cache-node-modules.outputs.cache-hit != 'true' run: | set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt mkdir -p .build/node_modules_cache tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - - name: Compile /build/ folder - run: npm run compile + - name: Type check /build/ scripts + run: npm run typecheck working-directory: build - - name: Check /build/ folder - run: .github/workflows/check-clean-git-state.sh - - name: Compile & Hygiene - run: npm exec -- npm-run-all -lp core-ci-pr extensions-ci-pr hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check + run: npm exec -- npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check test-build-scripts env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/telemetry.yml b/.github/workflows/telemetry.yml index 84a2ffaaf9360..e30d3cc8da36e 100644 --- a/.github/workflows/telemetry.yml +++ b/.github/workflows/telemetry.yml @@ -7,11 +7,11 @@ jobs: runs-on: 'ubuntu-latest' steps: - - uses: 'actions/checkout@v4' + - uses: 'actions/checkout@v6' with: persist-credentials: false - - uses: 'actions/setup-node@v4' + - uses: 'actions/setup-node@v6' with: node-version: 'lts/*' diff --git a/.gitignore b/.gitignore index 62394c607844b..92971a7a5731b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ product.overrides.json *.snap.actual *.tsbuildinfo .vscode-test +vscode-telemetry-docs/ diff --git a/.npmrc b/.npmrc index 43bde6e0ab483..060337bfad898 100644 --- a/.npmrc +++ b/.npmrc @@ -1,8 +1,7 @@ disturl="https://electronjs.org/headers" -target="37.2.3" -ms_build_id="12035395" +target="39.2.7" +ms_build_id="12953945" runtime="electron" build_from_source="true" legacy-peer-deps="true" timeout=180000 -npm_config_node_gyp="node build/npm/gyp/node_modules/node-gyp/bin/node-gyp.js" diff --git a/.nvmrc b/.nvmrc index fc37597bccdba..5767036af0e22 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.17.0 +22.21.1 diff --git a/.vscode-test.js b/.vscode-test.js index 2e49c90126b04..4c093d0e2b36b 100644 --- a/.vscode-test.js +++ b/.vscode-test.js @@ -79,6 +79,10 @@ const extensions = [ workspaceFolder: `extensions/vscode-api-tests/testworkspace.code-workspace`, mocha: { timeout: 60_000 }, files: 'extensions/vscode-api-tests/out/workspace-tests/**/*.test.js', + }, + { + label: 'git-base', + mocha: { timeout: 60_000 } } ]; diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 737efece5a492..3fb87652c814d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,6 +7,8 @@ "github.vscode-pull-request-github", "ms-vscode.vscode-github-issue-notebooks", "ms-vscode.extension-test-runner", - "jrieken.vscode-pr-pinger" + "jrieken.vscode-pr-pinger", + "typescriptteam.native-preview", + "ms-vscode.ts-customized-language-service" ] } diff --git a/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts b/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts index cbb8d50bf9954..2f9ac62abe4ed 100644 --- a/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts +++ b/.vscode/extensions/vscode-selfhost-test-provider/src/extension.ts @@ -45,7 +45,7 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.tests.registerTestFollowupProvider({ async provideFollowup(_result, test, taskIndex, messageIndex, _token) { return [{ - title: '$(sparkle) Fix with Copilot', + title: '$(sparkle) Fix', command: 'github.copilot.tests.fixTestFailure', arguments: [{ source: 'peekFollowup', test, message: test.taskStates[taskIndex].messages[messageIndex] }] }]; diff --git a/.vscode/launch.json b/.vscode/launch.json index 6b95eed617f3f..a7a15cc31a6c3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -252,6 +252,7 @@ "env": { "VSCODE_EXTHOST_WILL_SEND_SOCKET": null, "VSCODE_SKIP_PRELAUNCH": "1", + "VSCODE_DEV_DEBUG_OBSERVABLES": "1", }, "cleanUp": "wholeBrowser", "killBehavior": "polite", @@ -281,7 +282,7 @@ // To debug observables you also need the extension "ms-vscode.debug-value-editor" "type": "chrome", "request": "launch", - "name": "Launch VS Code Internal (Dev Debug)", + "name": "Launch VS Code Internal (Hot Reload)", "windows": { "runtimeExecutable": "${workspaceFolder}/scripts/code.bat" }, @@ -297,6 +298,10 @@ "VSCODE_EXTHOST_WILL_SEND_SOCKET": null, "VSCODE_SKIP_PRELAUNCH": "1", "VSCODE_DEV_DEBUG": "1", + "VSCODE_DEV_SERVER_URL": "http://localhost:5199/build/vite/workbench-vite-electron.html", + "DEV_WINDOW_SRC": "http://localhost:5199/build/vite/workbench-vite-electron.html", + "VSCODE_DEV_DEBUG_OBSERVABLES": "1", + "VSCODE_DEV": "1" }, "cleanUp": "wholeBrowser", "runtimeArgs": [ @@ -320,6 +325,7 @@ "presentation": { "hidden": true, }, + "preLaunchTask": "Launch Monaco Editor Vite" }, { "type": "node", @@ -586,11 +592,33 @@ ] }, { - "name": "Monaco Editor Playground", + "name": "Monaco Editor - Playground", "type": "chrome", "request": "launch", - "url": "http://localhost:5001", - "preLaunchTask": "Launch Http Server", + "url": "https://microsoft.github.io/monaco-editor/playground.html?source=http%3A%2F%2Flocalhost%3A5199%2Fbuild%2Fvite%2Findex.ts%3Fesm#example-creating-the-editor-hello-world", + "preLaunchTask": "Launch Monaco Editor Vite", + "presentation": { + "group": "monaco", + "order": 4 + } + }, + { + "name": "Monaco Editor - Self Contained Diff Editor", + "type": "chrome", + "request": "launch", + "url": "http://localhost:5199/build/vite/index.html", + "preLaunchTask": "Launch Monaco Editor Vite", + "presentation": { + "group": "monaco", + "order": 4 + } + }, + { + "name": "Monaco Editor - Workbench", + "type": "chrome", + "request": "launch", + "url": "http://localhost:5199/build/vite/workbench-vite.html", + "preLaunchTask": "Launch Monaco Editor Vite", "presentation": { "group": "monaco", "order": 4 @@ -614,10 +642,10 @@ } }, { - "name": "VS Code (Debug Observables)", + "name": "VS Code (Hot Reload)", "stopAll": true, "configurations": [ - "Launch VS Code Internal (Dev Debug)", + "Launch VS Code Internal (Hot Reload)", "Attach to Main Process", "Attach to Extension Host", "Attach to Shared Process", diff --git a/.vscode/mcp.json b/.vscode/mcp.json new file mode 100644 index 0000000000000..f798c6131b2d9 --- /dev/null +++ b/.vscode/mcp.json @@ -0,0 +1,12 @@ +{ + "servers": { + "vscode-playwright-mcp": { + "type": "stdio", + "command": "npm", + // Look at the [README](../test/mcp/README.md) to see what arguments are supported + "args": ["run", "start-stdio"], + "cwd": "${workspaceFolder}/test/mcp" + } + }, + "inputs": [] +} diff --git a/.vscode/notebooks/api.github-issues b/.vscode/notebooks/api.github-issues index 7533157d8b6b6..d466fa1b04b5d 100644 --- a/.vscode/notebooks/api.github-issues +++ b/.vscode/notebooks/api.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"July 2025\"" + "value": "$REPO=repo:microsoft/vscode\n$MILESTONE=milestone:\"October 2025\"" }, { "kind": 1, diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index 8bb13f5998a22..24da9ff45ab6c 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"July 2025\"" + "value": "$MILESTONE=milestone:\"November 2025\"" }, { "kind": 1, @@ -22,7 +22,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:pr is:open" + "value": "org:microsoft $MILESTONE is:pr is:open" }, { "kind": 1, @@ -32,7 +32,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS -$MILESTONE is:issue is:closed reason:completed label:bug label:insiders-released -label:verified -label:*duplicate -label:*as-designed -label:z-author-verified -label:on-testplan -label:error-telemetry" + "value": "org:microsoft -$MILESTONE is:issue is:closed reason:completed label:bug label:insiders-released -label:verified -label:*duplicate -label:*as-designed -label:z-author-verified -label:on-testplan -label:error-telemetry" }, { "kind": 1, @@ -42,7 +42,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS -$MILESTONE is:issue is:closed reason:completed label:feature-request label:insiders-released -label:on-testplan -label:verified -label:*duplicate -label:error-telemetry" + "value": "org:microsoft -$MILESTONE is:issue is:closed reason:completed label:feature-request label:insiders-released -label:on-testplan -label:verified -label:*duplicate -label:error-telemetry" }, { "kind": 1, @@ -52,7 +52,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item" + "value": "org:microsoft $MILESTONE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item" }, { "kind": 1, @@ -62,7 +62,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" + "value": "org:microsoft $MILESTONE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" }, { "kind": 1, @@ -72,7 +72,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:open label:testplan-item no:milestone" + "value": "org:microsoft $MILESTONE is:issue is:open label:testplan-item no:milestone" }, { "kind": 1, @@ -87,7 +87,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS is:issue is:open label:testplan-item" + "value": "org:microsoft is:issue is:open label:testplan-item" }, { "kind": 1, @@ -97,7 +97,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed label:verification-needed -label:verified -label:on-testplan" + "value": "org:microsoft $MILESTONE is:issue is:closed reason:completed label:verification-needed -label:verified -label:on-testplan" }, { "kind": 1, @@ -112,7 +112,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified -label:unreleased -label:*not-reproducible -label:*out-of-scope" + "value": "org:microsoft $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified -label:unreleased -label:*not-reproducible -label:*out-of-scope" }, { "kind": 1, @@ -122,7 +122,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug label:verification-steps-needed -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:z-author-verified -label:unreleased -label:*not-reproducible" + "value": "org:microsoft $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug label:verification-steps-needed -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:z-author-verified -label:unreleased -label:*not-reproducible" }, { "kind": 1, @@ -132,7 +132,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified label:unreleased -label:*not-reproducible" + "value": "org:microsoft $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:z-author-verified label:unreleased -label:*not-reproducible" }, { "kind": 1, @@ -142,7 +142,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:z-author-verified -label:*not-reproducible" + "value": "org:microsoft $MILESTONE is:issue is:closed reason:completed sort:updated-asc label:bug -label:verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:z-author-verified -label:*not-reproducible" }, { "kind": 1, @@ -152,6 +152,6 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE is:issue is:open label:candidate" + "value": "org:microsoft $MILESTONE is:issue is:open label:candidate" } ] \ No newline at end of file diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 334fd46541bab..6121a03e93b6d 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -7,7 +7,12 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n$MILESTONE=milestone:\"July 2025\"\n\n$MINE=assignee:@me" + "value": "$MILESTONE=milestone:\"November 2025\"\n\n$MINE=assignee:@me" + }, + { + "kind": 2, + "language": "github-issues", + "value": "$NOT_TEAM_MEMBERS=-author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:bamurtaugh -author:bpasero -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:ntrogh -author:hediet -author:isidorn -author:joaomoreno -author:jrieken -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:ulugbekna -author:aiday-mar -author:bhavyaus -author:justschen -author:benibenj -author:luabud -author:anthonykim1 -author:joshspicer -author:osortega -author:hawkticehurst -author:pierceboggan -author:benvillalobos -author:dileepyavan -author:dineshc-msft -author:dmitrivMS -author:eli-w-king -author:jo-oikawa -author:jruales -author:jytjyt05 -author:kycutler -author:mrleemurray -author:pwang347 -author:vijayupadya -author:bryanchen-d -author:cwebster-99" }, { "kind": 1, @@ -22,7 +27,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:pr is:open" + "value": "org:microsoft $MILESTONE $MINE is:pr is:open" }, { "kind": 1, @@ -32,7 +37,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item" + "value": "org:microsoft $MILESTONE $MINE is:issue is:open -label:iteration-plan -label:endgame-plan -label:testplan-item" }, { "kind": 1, @@ -42,7 +47,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" + "value": "org:microsoft $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" }, { "kind": 1, @@ -52,7 +57,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS is:issue is:open author:@me label:testplan-item" + "value": "org:microsoft $MILESTONE $MINE is:issue is:open author:@me label:testplan-item" }, { "kind": 1, @@ -62,7 +67,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request label:verification-needed -label:verified -label:on-testplan" + "value": "org:microsoft $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request label:verification-needed -label:verified -label:on-testplan" }, { "kind": 1, @@ -77,7 +82,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MINE is:issue is:open label:testplan-item" + "value": "org:microsoft $MINE is:issue is:open label:testplan-item" }, { "kind": 1, @@ -87,7 +92,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -assignee:@me -label:verified -label:z-author-verified label:feature-request label:verification-needed -label:verification-steps-needed -label:unreleased -label:on-testplan" + "value": "org:microsoft $MILESTONE -$MINE is:issue is:closed reason:completed -assignee:@me -label:verified -label:z-author-verified label:feature-request label:verification-needed -label:verification-steps-needed -label:unreleased -label:on-testplan" }, { "kind": 1, @@ -102,7 +107,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open -label:endgame-plan -label:testplan-item -label:iteration-plan" + "value": "org:microsoft $MILESTONE $MINE is:issue is:open -label:endgame-plan -label:testplan-item -label:iteration-plan" }, { "kind": 1, @@ -112,7 +117,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:open label:bug" + "value": "org:microsoft $MILESTONE $MINE is:issue is:open label:bug" }, { "kind": 1, @@ -127,7 +132,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-steps-needed" + "value": "org:microsoft $MILESTONE $MINE is:issue label:bug label:verification-steps-needed" }, { "kind": 1, @@ -137,7 +142,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue label:bug label:verification-found" + "value": "org:microsoft $MILESTONE $MINE is:issue label:bug label:verification-found" }, { "kind": 1, @@ -147,7 +152,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found -label:*not-reproducible" + "value": "org:microsoft $MILESTONE -$MINE is:issue is:closed reason:completed author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:triage-needed -label:verification-found -label:*not-reproducible" }, { "kind": 1, @@ -157,7 +162,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:*out-of-scope -label:error-telemetry -label:verification-steps-needed -label:verification-found -author:aeschli -author:alexdima -author:alexr00 -author:AmandaSilver -author:andreamah -author:bamurtaugh -author:bpasero -author:chrmarti -author:Chuxel -author:claudiaregio -author:connor4312 -author:dbaeumer -author:deepak1556 -author:devinvalenciano -author:digitarald -author:DonJayamanne -author:egamma -author:fiveisprime -author:ntrogh -author:hediet -author:isidorn -author:joaomoreno -author:jrieken -author:kieferrm -author:lramos15 -author:lszomoru -author:meganrogge -author:misolori -author:mjbvz -author:rebornix -author:roblourens -author:rzhao271 -author:sandy081 -author:sbatten -author:stevencl -author:tanhakabir -author:TylerLeonhardt -author:Tyriar -author:weinand -author:amunger -author:karthiknadig -author:eleanorjboyd -author:Yoyokrazy -author:paulacamargo25 -author:ulugbekna -author:aiday-mar -author:bhavyaus -author:justschen -author:benibenj -author:luabud -author:anthonykim1 -author:joshspicer -author:osortega -author:hawkticehurst -author:pierceboggan" + "value": "org:microsoft $MILESTONE -$MINE is:issue is:closed reason:completed sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:*out-of-scope -label:error-telemetry -label:verification-steps-needed -label:verification-found $NOT_TEAM_MEMBERS" }, { "kind": 1, @@ -167,7 +172,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue is:closed reason:completed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -label:*not-reproducible -label:*out-of-scope" + "value": "org:microsoft $MILESTONE -$MINE is:issue is:closed reason:completed -author:@me sort:updated-asc label:bug -label:unreleased -label:verified -label:z-author-verified -label:on-testplan -label:*duplicate -label:duplicate -label:invalid -label:*as-designed -label:error-telemetry -label:verification-steps-needed -label:verification-found -label:*not-reproducible -label:*out-of-scope" }, { "kind": 1, @@ -177,7 +182,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE -$MINE is:issue label:bug label:verification-steps-needed -label:verified" + "value": "org:microsoft $MILESTONE -$MINE is:issue label:bug label:verification-steps-needed -label:verified" }, { "kind": 1, @@ -187,6 +192,6 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\r\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\r\n$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes" + "value": "org:microsoft $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:on-release-notes\r\norg:microsoft $MILESTONE $MINE is:issue is:closed reason:completed label:engineering -label:on-release-notes\r\norg:microsoft $MILESTONE $MINE is:issue is:closed reason:completed label:plan-item -label:on-release-notes" } ] \ No newline at end of file diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 4406bdfccd567..7aa86b37e8a20 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n\n// current milestone name\n$MILESTONE=milestone:\"July 2025\"\n" + "value": "// list of repos we work in\n$REPOS=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce repo:microsoft/vscode-copilot-issues repo:microsoft/vscode-extension-samples\n\n// current milestone name\n$MILESTONE=milestone:\"October 2025\"\n" }, { "kind": 1, diff --git a/.vscode/notebooks/verification.github-issues b/.vscode/notebooks/verification.github-issues index 9740d9557e37f..1c7e9dc184378 100644 --- a/.vscode/notebooks/verification.github-issues +++ b/.vscode/notebooks/verification.github-issues @@ -12,7 +12,7 @@ { "kind": 2, "language": "github-issues", - "value": "$repos=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n$milestone=milestone:\"November 2023\"\n$closedRecently=closed:>2023-09-29" + "value": "$repos=repo:microsoft/lsprotocol repo:microsoft/monaco-editor repo:microsoft/vscode repo:microsoft/vscode-anycode repo:microsoft/vscode-autopep8 repo:microsoft/vscode-black-formatter repo:microsoft/vscode-copilot repo:microsoft/vscode-copilot-release repo:microsoft/vscode-dev repo:microsoft/vscode-dev-chrome-launcher repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-extension-telemetry repo:microsoft/vscode-flake8 repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-hexeditor repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-isort repo:microsoft/vscode-js-debug repo:microsoft/vscode-jupyter repo:microsoft/vscode-jupyter-internal repo:microsoft/vscode-l10n repo:microsoft/vscode-livepreview repo:microsoft/vscode-markdown-languageservice repo:microsoft/vscode-markdown-tm-grammar repo:microsoft/vscode-mypy repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-pylint repo:microsoft/vscode-python repo:microsoft/vscode-python-debugger repo:microsoft/vscode-python-tools-extension-template repo:microsoft/vscode-references-view repo:microsoft/vscode-remote-release repo:microsoft/vscode-remote-repositories-github repo:microsoft/vscode-remote-tunnels repo:microsoft/vscode-remotehub repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-unpkg repo:microsoft/vscode-vsce\n$milestone=milestone:\"October 2025\"\n$closedRecently=closed:>2023-09-29" }, { "kind": 1, diff --git a/.vscode/notebooks/vscode-dev.github-issues b/.vscode/notebooks/vscode-dev.github-issues index db480b64bd859..4ba4724804c97 100644 --- a/.vscode/notebooks/vscode-dev.github-issues +++ b/.vscode/notebooks/vscode-dev.github-issues @@ -2,7 +2,7 @@ { "kind": 2, "language": "github-issues", - "value": "$milestone=milestone:\"August 2024\"" + "value": "$milestone=milestone:\"October 2025\"" }, { "kind": 1, diff --git a/.vscode/searches/no-any-casts.code-search b/.vscode/searches/no-any-casts.code-search new file mode 100644 index 0000000000000..c430ea202fc6e --- /dev/null +++ b/.vscode/searches/no-any-casts.code-search @@ -0,0 +1,1269 @@ +# Query: // eslint-disable-next-line (local/code-no-any-casts|@typescript-eslint/no-explicit-any) +# Flags: RegExp + +727 results - 269 files + +.eslint-plugin-local/code-policy-localization-key-match.ts: + 123: // eslint-disable-next-line local/code-no-any-casts + +build/gulpfile.reh.ts: + 187: // eslint-disable-next-line local/code-no-any-casts + +extensions/html-language-features/server/src/htmlServer.ts: + 544: // eslint-disable-next-line local/code-no-any-casts + +extensions/markdown-language-features/notebook/index.ts: + 383: // eslint-disable-next-line local/code-no-any-casts + +extensions/markdown-language-features/preview-src/index.ts: + 26: // eslint-disable-next-line local/code-no-any-casts + 253: // eslint-disable-next-line local/code-no-any-casts + 444: // eslint-disable-next-line local/code-no-any-casts + +extensions/markdown-language-features/src/markdownEngine.ts: + 146: // eslint-disable-next-line local/code-no-any-casts + +extensions/markdown-language-features/src/languageFeatures/diagnostics.ts: + 54: // eslint-disable-next-line local/code-no-any-casts + +scripts/playground-server.ts: + 257: // eslint-disable-next-line local/code-no-any-casts + 336: // eslint-disable-next-line local/code-no-any-casts + 352: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/dom.ts: + 718: // eslint-disable-next-line local/code-no-any-casts + 1325: // eslint-disable-next-line local/code-no-any-casts + 1520: // eslint-disable-next-line local/code-no-any-casts + 1660: // eslint-disable-next-line local/code-no-any-casts + 2013: // eslint-disable-next-line local/code-no-any-casts + 2116: // eslint-disable-next-line local/code-no-any-casts + 2128: // eslint-disable-next-line local/code-no-any-casts + 2291: // eslint-disable-next-line local/code-no-any-casts + 2297: // eslint-disable-next-line local/code-no-any-casts + 2325: // eslint-disable-next-line local/code-no-any-casts + 2437: // eslint-disable-next-line local/code-no-any-casts + 2444: // eslint-disable-next-line local/code-no-any-casts + 2566: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/mouseEvent.ts: + 100: // eslint-disable-next-line local/code-no-any-casts + 138: // eslint-disable-next-line local/code-no-any-casts + 155: // eslint-disable-next-line local/code-no-any-casts + 157: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/trustedTypes.ts: + 27: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/base/browser/ui/grid/grid.ts: + 66: // eslint-disable-next-line local/code-no-any-casts + 873: // eslint-disable-next-line local/code-no-any-casts + 875: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/ui/grid/gridview.ts: + 196: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/browser/ui/sash/sash.ts: + 491: // eslint-disable-next-line local/code-no-any-casts + 497: // eslint-disable-next-line local/code-no-any-casts + 503: // eslint-disable-next-line local/code-no-any-casts + 505: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/console.ts: + 134: // eslint-disable-next-line local/code-no-any-casts + 138: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/decorators.ts: + 57: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/errors.ts: + 142: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/hotReload.ts: + 102: // eslint-disable-next-line local/code-no-any-casts + 109: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/hotReloadHelpers.ts: + 39: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/lifecycle.ts: + 239: // eslint-disable-next-line local/code-no-any-casts + 249: // eslint-disable-next-line local/code-no-any-casts + 260: // eslint-disable-next-line local/code-no-any-casts + 320: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/marshalling.ts: + 53: // eslint-disable-next-line local/code-no-any-casts + 55: // eslint-disable-next-line local/code-no-any-casts + 57: // eslint-disable-next-line local/code-no-any-casts + 65: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/strings.ts: + 26: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/base/common/types.ts: + 65: // eslint-disable-next-line local/code-no-any-casts + 73: // eslint-disable-next-line local/code-no-any-casts + 275: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/validation.ts: + 149: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 165: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 285: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/base/common/verifier.ts: + 82: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/marked/marked.js: + 2344: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/base/common/observableInternal/changeTracker.ts: + 34: // eslint-disable-next-line local/code-no-any-casts + 42: // eslint-disable-next-line local/code-no-any-casts + 69: // eslint-disable-next-line local/code-no-any-casts + 80: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/set.ts: + 51: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/experimental/reducer.ts: + 39: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts: + 80: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts: + 12: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/logging/debugger/rpc.ts: + 94: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/observables/derived.ts: + 38: // eslint-disable-next-line local/code-no-any-casts + 40: // eslint-disable-next-line local/code-no-any-casts + 124: // eslint-disable-next-line local/code-no-any-casts + 129: // eslint-disable-next-line local/code-no-any-casts + 160: // eslint-disable-next-line local/code-no-any-casts + 165: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/observables/derivedImpl.ts: + 313: // eslint-disable-next-line local/code-no-any-casts + 412: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/observables/observableFromEvent.ts: + 151: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/reactions/autorunImpl.ts: + 185: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/observableInternal/utils/utilsCancellation.ts: + 78: // eslint-disable-next-line local/code-no-any-casts + 83: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/common/worker/webWorker.ts: + 430: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/base/parts/ipc/test/node/ipc.net.test.ts: + 87: // eslint-disable-next-line local/code-no-any-casts + 92: // eslint-disable-next-line local/code-no-any-casts + 652: // eslint-disable-next-line local/code-no-any-casts + 785: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/buffer.test.ts: + 515: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/decorators.test.ts: + 130: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/filters.test.ts: + 28: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/glob.test.ts: + 497: // eslint-disable-next-line local/code-no-any-casts + 518: // eslint-disable-next-line local/code-no-any-casts + 763: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/json.test.ts: + 52: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/mock.ts: + 14: // eslint-disable-next-line local/code-no-any-casts + 23: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/oauth.test.ts: + 1100: // eslint-disable-next-line local/code-no-any-casts + 1743: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/snapshot.ts: + 123: // eslint-disable-next-line local/code-no-any-casts + 125: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/timeTravelScheduler.ts: + 276: // eslint-disable-next-line local/code-no-any-casts + 286: // eslint-disable-next-line local/code-no-any-casts + 319: // eslint-disable-next-line local/code-no-any-casts + 325: // eslint-disable-next-line local/code-no-any-casts + 341: // eslint-disable-next-line local/code-no-any-casts + +src/vs/base/test/common/troubleshooting.ts: + 50: // eslint-disable-next-line local/code-no-any-casts + 55: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/browser/config/editorConfiguration.ts: + 147: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/editor/browser/controller/mouseTarget.ts: + 993: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 997: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 1001: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 1044: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 1097: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 1100: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 1120: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/editor/browser/controller/editContext/native/nativeEditContextUtils.ts: + 81: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 85: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/editor/browser/gpu/gpuUtils.ts: + 52: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/editor/browser/gpu/viewGpuContext.ts: + 226: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts: + 625: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts: + 179: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts: + 480: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/editor/browser/widget/diffEditor/utils.ts: + 184: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 192: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 195: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 303: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 310: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/editor/browser/widget/diffEditor/components/diffEditorEditors.ts: + 75: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/editor/browser/widget/multiDiffEditor/diffEditorItemTemplate.ts: + 100: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 103: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/editor/common/textModelEditSource.ts: + 59: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 68: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 70: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/editor/common/config/editorOptions.ts: + 6812: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/editor/common/core/edits/edit.ts: + 10: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/editor/common/core/edits/stringEdit.ts: + 12: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 24: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 193: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length.ts: + 26: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 30: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 51: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 56: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 64: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 72: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 80: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 99: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 101: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 126: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 131: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 136: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 141: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 153: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 158: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 175: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 177: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 196: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/smallImmutableSet.ts: + 13: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 30: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/editor/contrib/colorPicker/browser/colorDetector.ts: + 100: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts: + 200: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/editorState/test/browser/editorState.test.ts: + 97: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/find/browser/findModel.ts: + 556: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/find/test/browser/findController.test.ts: + 79: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts: + 56: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts: + 644: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts: + 173: // eslint-disable-next-line local/code-no-any-casts + 195: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts: + 505: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts: + 15: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/test/browser/computeGhostText.test.ts: + 23: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/test/browser/suggestWidgetModel.test.ts: + 164: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts: + 244: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts: + 794: // eslint-disable-next-line local/code-no-any-casts + 813: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/standalone/browser/standaloneEditor.ts: + 505: // eslint-disable-next-line local/code-no-any-casts + 507: // eslint-disable-next-line local/code-no-any-casts + 509: // eslint-disable-next-line local/code-no-any-casts + 511: // eslint-disable-next-line local/code-no-any-casts + 513: // eslint-disable-next-line local/code-no-any-casts + 515: // eslint-disable-next-line local/code-no-any-casts + 518: // eslint-disable-next-line local/code-no-any-casts + 520: // eslint-disable-next-line local/code-no-any-casts + 522: // eslint-disable-next-line local/code-no-any-casts + 524: // eslint-disable-next-line local/code-no-any-casts + 527: // eslint-disable-next-line local/code-no-any-casts + 529: // eslint-disable-next-line local/code-no-any-casts + 531: // eslint-disable-next-line local/code-no-any-casts + 533: // eslint-disable-next-line local/code-no-any-casts + 536: // eslint-disable-next-line local/code-no-any-casts + 538: // eslint-disable-next-line local/code-no-any-casts + 540: // eslint-disable-next-line local/code-no-any-casts + 542: // eslint-disable-next-line local/code-no-any-casts + 544: // eslint-disable-next-line local/code-no-any-casts + 546: // eslint-disable-next-line local/code-no-any-casts + 550: // eslint-disable-next-line local/code-no-any-casts + 552: // eslint-disable-next-line local/code-no-any-casts + 554: // eslint-disable-next-line local/code-no-any-casts + 556: // eslint-disable-next-line local/code-no-any-casts + 558: // eslint-disable-next-line local/code-no-any-casts + 560: // eslint-disable-next-line local/code-no-any-casts + 562: // eslint-disable-next-line local/code-no-any-casts + 568: // eslint-disable-next-line local/code-no-any-casts + 600: // eslint-disable-next-line local/code-no-any-casts + 602: // eslint-disable-next-line local/code-no-any-casts + 604: // eslint-disable-next-line local/code-no-any-casts + 606: // eslint-disable-next-line local/code-no-any-casts + 608: // eslint-disable-next-line local/code-no-any-casts + 610: // eslint-disable-next-line local/code-no-any-casts + 612: // eslint-disable-next-line local/code-no-any-casts + 615: // eslint-disable-next-line local/code-no-any-casts + 620: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/standalone/browser/standaloneLanguages.ts: + 753: // eslint-disable-next-line local/code-no-any-casts + 755: // eslint-disable-next-line local/code-no-any-casts + 757: // eslint-disable-next-line local/code-no-any-casts + 759: // eslint-disable-next-line local/code-no-any-casts + 761: // eslint-disable-next-line local/code-no-any-casts + 765: // eslint-disable-next-line local/code-no-any-casts + 768: // eslint-disable-next-line local/code-no-any-casts + 770: // eslint-disable-next-line local/code-no-any-casts + 772: // eslint-disable-next-line local/code-no-any-casts + 774: // eslint-disable-next-line local/code-no-any-casts + 776: // eslint-disable-next-line local/code-no-any-casts + 778: // eslint-disable-next-line local/code-no-any-casts + 780: // eslint-disable-next-line local/code-no-any-casts + 782: // eslint-disable-next-line local/code-no-any-casts + 784: // eslint-disable-next-line local/code-no-any-casts + 786: // eslint-disable-next-line local/code-no-any-casts + 788: // eslint-disable-next-line local/code-no-any-casts + 790: // eslint-disable-next-line local/code-no-any-casts + 792: // eslint-disable-next-line local/code-no-any-casts + 794: // eslint-disable-next-line local/code-no-any-casts + 796: // eslint-disable-next-line local/code-no-any-casts + 798: // eslint-disable-next-line local/code-no-any-casts + 800: // eslint-disable-next-line local/code-no-any-casts + 802: // eslint-disable-next-line local/code-no-any-casts + 804: // eslint-disable-next-line local/code-no-any-casts + 806: // eslint-disable-next-line local/code-no-any-casts + 808: // eslint-disable-next-line local/code-no-any-casts + 810: // eslint-disable-next-line local/code-no-any-casts + 812: // eslint-disable-next-line local/code-no-any-casts + 814: // eslint-disable-next-line local/code-no-any-casts + 816: // eslint-disable-next-line local/code-no-any-casts + 818: // eslint-disable-next-line local/code-no-any-casts + 820: // eslint-disable-next-line local/code-no-any-casts + 822: // eslint-disable-next-line local/code-no-any-casts + 824: // eslint-disable-next-line local/code-no-any-casts + 849: // eslint-disable-next-line local/code-no-any-casts + 851: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/standalone/common/monarch/monarchCompile.ts: + 461: // eslint-disable-next-line local/code-no-any-casts + 539: // eslint-disable-next-line local/code-no-any-casts + 556: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/test/browser/testCodeEditor.ts: + 279: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/test/browser/config/editorConfiguration.test.ts: + 90: // eslint-disable-next-line local/code-no-any-casts + 99: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/test/common/model/textModel.test.ts: + 1167: // eslint-disable-next-line local/code-no-any-casts + +src/vs/editor/test/common/model/textModelWithTokens.test.ts: + 272: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/contextkey/common/contextkey.ts: + 939: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/contextkey/test/common/contextkey.test.ts: + 96: // eslint-disable-next-line local/code-no-any-casts + 98: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/domWidget/browser/domWidget.ts: + 132: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 152: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/platform/environment/test/node/argv.test.ts: + 47: // eslint-disable-next-line local/code-no-any-casts + 59: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/extensionManagement/common/extensionGalleryManifestServiceIpc.ts: + 37: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/platform/extensionManagement/common/extensionManagementIpc.ts: + 64: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 113: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 348: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 353: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/platform/extensionRecommendations/common/extensionRecommendationsIpc.ts: + 36: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 41: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/platform/files/browser/htmlFileSystemProvider.ts: + 311: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/platform/files/test/node/diskFileService.integrationTest.ts: + 106: // eslint-disable-next-line local/code-no-any-casts + 109: // eslint-disable-next-line local/code-no-any-casts + 112: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/instantiation/common/instantiationService.ts: + 321: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/ipc/electron-browser/services.ts: + 13: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/platform/list/browser/listService.ts: + 877: // eslint-disable-next-line local/code-no-any-casts + 918: // eslint-disable-next-line local/code-no-any-casts + 965: // eslint-disable-next-line local/code-no-any-casts + 1012: // eslint-disable-next-line local/code-no-any-casts + 1057: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/observable/common/wrapInHotClass.ts: + 12: // eslint-disable-next-line local/code-no-any-casts + 40: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/observable/common/wrapInReloadableClass.ts: + 31: // eslint-disable-next-line local/code-no-any-casts + 58: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/profiling/common/profilingTelemetrySpec.ts: + 73: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/quickinput/browser/tree/quickTree.ts: + 82: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/platform/quickinput/common/quickAccess.ts: + 172: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/platform/quickinput/test/browser/quickinput.test.ts: + 69: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/remote/browser/browserSocketFactory.ts: + 89: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/remote/common/remoteAgentConnection.ts: + 801: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/remote/test/electron-browser/remoteAuthorityResolverService.test.ts: + 19: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/request/electron-utility/requestService.ts: + 15: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/storage/electron-main/storageIpc.ts: + 74: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 102: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/platform/terminal/node/terminalProcess.ts: + 547: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/platform/webContentExtractor/test/electron-main/cdpAccessibilityDomain.test.ts: + 22: // eslint-disable-next-line local/code-no-any-casts + +src/vs/platform/webContentExtractor/test/electron-main/webPageLoader.test.ts: + 95: // eslint-disable-next-line local/code-no-any-casts + +src/vs/server/node/extensionHostConnection.ts: + 243: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/server/node/remoteExtensionHostAgentServer.ts: + 767: // eslint-disable-next-line local/code-no-any-casts + 769: // eslint-disable-next-line local/code-no-any-casts + 771: // eslint-disable-next-line local/code-no-any-casts + +src/vs/server/node/remoteTerminalChannel.ts: + 112: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/workbench.web.main.internal.ts: + 196: // eslint-disable-next-line local/code-no-any-casts + 221: // eslint-disable-next-line local/code-no-any-casts + 223: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/workbench.web.main.ts: + 58: // eslint-disable-next-line local/code-no-any-casts + 60: // eslint-disable-next-line local/code-no-any-casts + 82: // eslint-disable-next-line local/code-no-any-casts + 91: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/browser/mainThreadExtensionService.ts: + 57: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts: + 912: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 923: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/workbench/api/browser/mainThreadQuickOpen.ts: + 242: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/workbench/api/browser/viewsExtensionPoint.ts: + 545: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/workbench/api/common/extHost.api.impl.ts: + 162: // eslint-disable-next-line local/code-no-any-casts + 317: // eslint-disable-next-line local/code-no-any-casts + 326: // eslint-disable-next-line local/code-no-any-casts + 565: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHost.protocol.ts: + 2209: // eslint-disable-next-line local/code-no-any-casts + 2211: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostDebugService.ts: + 243: // eslint-disable-next-line local/code-no-any-casts + 491: // eslint-disable-next-line local/code-no-any-casts + 493: // eslint-disable-next-line local/code-no-any-casts + 495: // eslint-disable-next-line local/code-no-any-casts + 666: // eslint-disable-next-line local/code-no-any-casts + 770: // eslint-disable-next-line local/code-no-any-casts + 778: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostDocumentSaveParticipant.ts: + 114: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/api/common/extHostExtensionActivator.ts: + 405: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostExtensionService.ts: + 566: // eslint-disable-next-line local/code-no-any-casts + 1009: // eslint-disable-next-line local/code-no-any-casts + 1050: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostLanguageFeatures.ts: + 197: // eslint-disable-next-line local/code-no-any-casts + 714: // eslint-disable-next-line local/code-no-any-casts + 735: // eslint-disable-next-line local/code-no-any-casts + 748: // eslint-disable-next-line local/code-no-any-casts + 771: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostLanguageModelTools.ts: + 221: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostMcp.ts: + 211: // eslint-disable-next-line local/code-no-any-casts + 213: // eslint-disable-next-line local/code-no-any-casts + 216: // eslint-disable-next-line local/code-no-any-casts + 218: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostSearch.ts: + 221: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostTimeline.ts: + 160: // eslint-disable-next-line local/code-no-any-casts + 163: // eslint-disable-next-line local/code-no-any-casts + 166: // eslint-disable-next-line local/code-no-any-casts + 169: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostTypeConverters.ts: + 465: // eslint-disable-next-line local/code-no-any-casts + 858: // eslint-disable-next-line local/code-no-any-casts + 3179: // eslint-disable-next-line local/code-no-any-casts + 3181: // eslint-disable-next-line local/code-no-any-casts + 3183: // eslint-disable-next-line local/code-no-any-casts + 3185: // eslint-disable-next-line local/code-no-any-casts + 3187: // eslint-disable-next-line local/code-no-any-casts + 3189: // eslint-disable-next-line local/code-no-any-casts + 3191: // eslint-disable-next-line local/code-no-any-casts + 3193: // eslint-disable-next-line local/code-no-any-casts + 3195: // eslint-disable-next-line local/code-no-any-casts + 3202: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/common/extHostTypes.ts: + 3190: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/node/extensionHostProcess.ts: + 108: // eslint-disable-next-line local/code-no-any-casts + 120: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/node/extHostConsoleForwarder.ts: + 31: // eslint-disable-next-line local/code-no-any-casts + 53: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/node/extHostMcpNode.ts: + 57: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/node/proxyResolver.ts: + 113: // eslint-disable-next-line local/code-no-any-casts + 136: // eslint-disable-next-line local/code-no-any-casts + 139: // eslint-disable-next-line local/code-no-any-casts + 142: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostApiCommands.test.ts: + 874: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts: + 75: // eslint-disable-next-line local/code-no-any-casts + 164: // eslint-disable-next-line local/code-no-any-casts + 173: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostCommands.test.ts: + 92: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostConfiguration.test.ts: + 750: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostDocumentData.test.ts: + 46: // eslint-disable-next-line local/code-no-any-casts + 48: // eslint-disable-next-line local/code-no-any-casts + 50: // eslint-disable-next-line local/code-no-any-casts + 52: // eslint-disable-next-line local/code-no-any-casts + 54: // eslint-disable-next-line local/code-no-any-casts + 56: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostDocumentSaveParticipant.test.ts: + 84: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostLanguageFeatures.test.ts: + 1068: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts: + 164: // eslint-disable-next-line local/code-no-any-casts + 166: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostTelemetry.test.ts: + 107: // eslint-disable-next-line local/code-no-any-casts + 109: // eslint-disable-next-line local/code-no-any-casts + 111: // eslint-disable-next-line local/code-no-any-casts + 114: // eslint-disable-next-line local/code-no-any-casts + 121: // eslint-disable-next-line local/code-no-any-casts + 128: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostTesting.test.ts: + 640: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostTextEditor.test.ts: + 265: // eslint-disable-next-line local/code-no-any-casts + 290: // eslint-disable-next-line local/code-no-any-casts + 327: // eslint-disable-next-line local/code-no-any-casts + 340: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostTypes.test.ts: + 87: // eslint-disable-next-line local/code-no-any-casts + 89: // eslint-disable-next-line local/code-no-any-casts + 91: // eslint-disable-next-line local/code-no-any-casts + 209: // eslint-disable-next-line local/code-no-any-casts + 211: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/extHostWorkspace.test.ts: + 541: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/mainThreadAuthentication.integrationTest.ts: + 119: // eslint-disable-next-line local/code-no-any-casts + 126: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/mainThreadDocumentsAndEditors.test.ts: + 86: // eslint-disable-next-line local/code-no-any-casts + 93: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/mainThreadEditors.test.ts: + 130: // eslint-disable-next-line local/code-no-any-casts + 137: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts: + 60: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/common/extensionHostMain.test.ts: + 80: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/common/extHostTerminalShellIntegration.test.ts: + 86: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/common/extHostTypeConverters.test.ts: + 34: // eslint-disable-next-line local/code-no-any-casts + 43: // eslint-disable-next-line local/code-no-any-casts + 66: // eslint-disable-next-line local/code-no-any-casts + 78: // eslint-disable-next-line local/code-no-any-casts + 85: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/common/testRPCProtocol.ts: + 36: // eslint-disable-next-line local/code-no-any-casts + 163: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/test/node/extHostSearch.test.ts: + 177: // eslint-disable-next-line local/code-no-any-casts + 1004: // eslint-disable-next-line local/code-no-any-casts + 1050: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/worker/extensionHostWorker.ts: + 83: // eslint-disable-next-line local/code-no-any-casts + 85: // eslint-disable-next-line local/code-no-any-casts + 87: // eslint-disable-next-line local/code-no-any-casts + 89: // eslint-disable-next-line local/code-no-any-casts + 91: // eslint-disable-next-line local/code-no-any-casts + 93: // eslint-disable-next-line local/code-no-any-casts + 95: // eslint-disable-next-line local/code-no-any-casts + 97: // eslint-disable-next-line local/code-no-any-casts + 100: // eslint-disable-next-line local/code-no-any-casts + 104: // eslint-disable-next-line local/code-no-any-casts + 106: // eslint-disable-next-line local/code-no-any-casts + 158: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/api/worker/extHostConsoleForwarder.ts: + 20: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/browser/actions/developerActions.ts: + 762: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 764: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 795: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 798: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/common/configuration.ts: + 63: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts: + 54: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/bulkEdit/test/browser/bulkCellEdits.test.ts: + 29: // eslint-disable-next-line local/code-no-any-casts + 39: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/browser/chatSessions.contribution.ts: + 923: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/browser/chatWidget.ts: + 174: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts: + 88: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 625: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 646: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 699: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/browser/actions/chatContextActions.ts: + 261: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingActions.ts: + 56: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 63: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 101: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts: + 85: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 87: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 107: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 111: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/browser/chatSessions/common.ts: + 71: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 112: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 114: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/browser/chatSessions/view/sessionsTreeRenderer.ts: + 89: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables.ts: + 180: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 198: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/common/chatModel.ts: + 672: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 1396: // eslint-disable-next-line @typescript-eslint/no-explicit-any, local/code-no-any-casts + 1815: // eslint-disable-next-line @typescript-eslint/no-explicit-any, local/code-no-any-casts + 2108: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 2128: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/common/chatService.ts: + 45: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 277: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 324: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 375: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 860: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 928: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 930: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/common/chatServiceImpl.ts: + 553: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/common/chatSessionsService.ts: + 129: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 141: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 182: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/common/chatWidgetHistoryService.ts: + 24: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 87: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/common/languageModels.ts: + 55: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 135: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 143: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 209: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 216: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 223: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 286: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 557: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/common/languageModelToolsService.ts: + 129: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 150: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 156: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 193: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 198: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 270: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/common/promptSyntax/service/promptsServiceImpl.ts: + 390: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/common/tools/manageTodoListTool.ts: + 33: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 124: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/test/browser/chatEditingModifiedNotebookEntry.test.ts: + 30: // eslint-disable-next-line local/code-no-any-casts + 35: // eslint-disable-next-line local/code-no-any-casts + 63: // eslint-disable-next-line local/code-no-any-casts + 106: // eslint-disable-next-line local/code-no-any-casts + 161: // eslint-disable-next-line local/code-no-any-casts + 204: // eslint-disable-next-line local/code-no-any-casts + 209: // eslint-disable-next-line local/code-no-any-casts + 415: // eslint-disable-next-line local/code-no-any-casts + 420: // eslint-disable-next-line local/code-no-any-casts + 577: // eslint-disable-next-line local/code-no-any-casts + 582: // eslint-disable-next-line local/code-no-any-casts + 619: // eslint-disable-next-line local/code-no-any-casts + 668: // eslint-disable-next-line local/code-no-any-casts + 726: // eslint-disable-next-line local/code-no-any-casts + 774: // eslint-disable-next-line local/code-no-any-casts + 779: // eslint-disable-next-line local/code-no-any-casts + 790: // eslint-disable-next-line local/code-no-any-casts + 1532: // eslint-disable-next-line local/code-no-any-casts + 1537: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/test/browser/chatEditingSessionStorage.test.ts: + 41: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts: + 75: // eslint-disable-next-line local/code-no-any-casts + 87: // eslint-disable-next-line local/code-no-any-casts + 99: // eslint-disable-next-line local/code-no-any-casts + 111: // eslint-disable-next-line local/code-no-any-casts + 123: // eslint-disable-next-line local/code-no-any-casts + 135: // eslint-disable-next-line local/code-no-any-casts + 142: // eslint-disable-next-line local/code-no-any-casts + 154: // eslint-disable-next-line local/code-no-any-casts + 161: // eslint-disable-next-line local/code-no-any-casts + 173: // eslint-disable-next-line local/code-no-any-casts + 181: // eslint-disable-next-line local/code-no-any-casts + 193: // eslint-disable-next-line local/code-no-any-casts + 200: // eslint-disable-next-line local/code-no-any-casts + 245: // eslint-disable-next-line local/code-no-any-casts + 256: // eslint-disable-next-line local/code-no-any-casts + 267: // eslint-disable-next-line local/code-no-any-casts + 278: // eslint-disable-next-line local/code-no-any-casts + 289: // eslint-disable-next-line local/code-no-any-casts + 300: // eslint-disable-next-line local/code-no-any-casts + 311: // eslint-disable-next-line local/code-no-any-casts + 322: // eslint-disable-next-line local/code-no-any-casts + 338: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/chat/test/common/languageModels.ts: + 53: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/test/common/mockLanguageModelToolsService.ts: + 52: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/test/common/mockPromptsService.ts: + 36: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 38: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 41: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 44: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 47: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 50: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 52: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts: + 16: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/browser/debugSession.ts: + 1193: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/browser/breakpoints.test.ts: + 450: // eslint-disable-next-line local/code-no-any-casts + 466: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/browser/debugConfigurationManager.test.ts: + 92: // eslint-disable-next-line local/code-no-any-casts + 129: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/browser/debugMemory.test.ts: + 76: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/browser/rawDebugSession.test.ts: + 28: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/browser/repl.test.ts: + 139: // eslint-disable-next-line local/code-no-any-casts + 142: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/debug/test/common/debugModel.test.ts: + 72: // eslint-disable-next-line local/code-no-any-casts + 77: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts: + 15: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts: + 142: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts: + 1182: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts: + 99: // eslint-disable-next-line local/code-no-any-casts + 101: // eslint-disable-next-line local/code-no-any-casts + 103: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts: + 100: // eslint-disable-next-line local/code-no-any-casts + 102: // eslint-disable-next-line local/code-no-any-casts + 104: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/files/test/browser/editorAutoSave.test.ts: + 46: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/files/test/browser/explorerView.test.ts: + 94: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts: + 163: // eslint-disable-next-line local/code-no-any-casts + 673: // eslint-disable-next-line local/code-no-any-casts + 722: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts: + 72: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/markers/test/browser/markersModel.test.ts: + 145: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/mcp/common/modelContextProtocol.ts: + 100: // eslint-disable-next-line @typescript-eslint/no-explicit-any + 116: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/mergeEditor/browser/utils.ts: + 89: // eslint-disable-next-line local/code-no-any-casts + 99: // eslint-disable-next-line local/code-no-any-casts + 120: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts: + 69: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts: + 75: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/search/browser/searchActionsFind.ts: + 460: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/search/browser/searchMessage.ts: + 51: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts: + 306: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts: + 299: // eslint-disable-next-line local/code-no-any-casts + 301: // eslint-disable-next-line local/code-no-any-casts + 318: // eslint-disable-next-line local/code-no-any-casts + 324: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/search/test/browser/searchModel.test.ts: + 201: // eslint-disable-next-line local/code-no-any-casts + 229: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts: + 205: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts: + 155: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts: + 328: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts: + 1489: // eslint-disable-next-line local/code-no-any-casts + 1498: // eslint-disable-next-line local/code-no-any-casts + 1537: // eslint-disable-next-line local/code-no-any-casts + 1583: // eslint-disable-next-line local/code-no-any-casts + 1737: // eslint-disable-next-line local/code-no-any-casts + 1784: // eslint-disable-next-line local/code-no-any-casts + 1787: // eslint-disable-next-line local/code-no-any-casts + 2673: // eslint-disable-next-line local/code-no-any-casts + 2854: // eslint-disable-next-line local/code-no-any-casts + 3586: // eslint-disable-next-line local/code-no-any-casts + 3620: // eslint-disable-next-line local/code-no-any-casts + 3626: // eslint-disable-next-line local/code-no-any-casts + 3755: // eslint-disable-next-line local/code-no-any-casts + 3814: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/tasks/common/problemMatcher.ts: + 361: // eslint-disable-next-line local/code-no-any-casts + 374: // eslint-disable-next-line local/code-no-any-casts + 1015: // eslint-disable-next-line local/code-no-any-casts + 1906: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/tasks/common/taskConfiguration.ts: + 1720: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/tasks/common/tasks.ts: + 667: // eslint-disable-next-line local/code-no-any-casts + 708: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts: + 84: // eslint-disable-next-line local/code-no-any-casts + 86: // eslint-disable-next-line local/code-no-any-casts + 89: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/accessibility/test/browser/bufferContentTracker.test.ts: + 71: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/outputMonitor.test.ts: + 45: // eslint-disable-next-line local/code-no-any-casts + 61: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts: + 402: // eslint-disable-next-line local/code-no-any-casts + 924: // eslint-disable-next-line local/code-no-any-casts + 948: // eslint-disable-next-line local/code-no-any-casts + 953: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/history/test/common/history.test.ts: + 102: // eslint-disable-next-line local/code-no-any-casts + 108: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/links/browser/links.ts: + 168: // eslint-disable-next-line @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkHelpers.test.ts: + 242: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkManager.test.ts: + 95: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts: + 151: // eslint-disable-next-line local/code-no-any-casts + 292: // eslint-disable-next-line local/code-no-any-casts + 556: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalWordLinkDetector.test.ts: + 49: // eslint-disable-next-line local/code-no-any-casts + 59: // eslint-disable-next-line local/code-no-any-casts + 69: // eslint-disable-next-line local/code-no-any-casts + 89: // eslint-disable-next-line local/code-no-any-casts + 98: // eslint-disable-next-line local/code-no-any-casts + 108: // eslint-disable-next-line local/code-no-any-casts + 118: // eslint-disable-next-line local/code-no-any-casts + 126: // eslint-disable-next-line local/code-no-any-casts + 136: // eslint-disable-next-line local/code-no-any-casts + 142: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/terminalContrib/typeAhead/test/browser/terminalTypeAhead.test.ts: + 40: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts: + 123: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/contrib/themes/browser/themes.contribution.ts: + 614: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts: + 600: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/authentication/test/browser/authenticationMcpAccessService.test.ts: + 236: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts: + 81: // eslint-disable-next-line local/code-no-any-casts + 106: // eslint-disable-next-line local/code-no-any-casts + 306: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/driver/browser/driver.ts: + 199: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + 222: // eslint-disable-next-line local/code-no-any-casts, @typescript-eslint/no-explicit-any + +src/vs/workbench/services/extensions/common/extensionsRegistry.ts: + 229: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/extensions/test/common/extensionManifestPropertiesService.test.ts: + 49: // eslint-disable-next-line local/code-no-any-casts + 54: // eslint-disable-next-line local/code-no-any-casts + 97: // eslint-disable-next-line local/code-no-any-casts + 102: // eslint-disable-next-line local/code-no-any-casts + 107: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/extensions/test/common/rpcProtocol.test.ts: + 185: // eslint-disable-next-line local/code-no-any-casts + 222: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts: + 47: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/search/common/search.ts: + 628: // eslint-disable-next-line local/code-no-any-casts + 631: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/search/node/rawSearchService.ts: + 438: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/textfile/test/node/encoding/encoding.test.ts: + 44: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/textMate/common/TMGrammarFactory.ts: + 147: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/themes/browser/fileIconThemeData.ts: + 122: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/themes/browser/productIconThemeData.ts: + 123: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/themes/common/colorThemeData.ts: + 650: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/views/test/browser/viewContainerModel.test.ts: + 67: // eslint-disable-next-line local/code-no-any-casts + 74: // eslint-disable-next-line local/code-no-any-casts + 102: // eslint-disable-next-line local/code-no-any-casts + 147: // eslint-disable-next-line local/code-no-any-casts + 171: // eslint-disable-next-line local/code-no-any-casts + 195: // eslint-disable-next-line local/code-no-any-casts + 241: // eslint-disable-next-line local/code-no-any-casts + 272: // eslint-disable-next-line local/code-no-any-casts + 293: // eslint-disable-next-line local/code-no-any-casts + 324: // eslint-disable-next-line local/code-no-any-casts + 370: // eslint-disable-next-line local/code-no-any-casts + 401: // eslint-disable-next-line local/code-no-any-casts + 429: // eslint-disable-next-line local/code-no-any-casts + 457: // eslint-disable-next-line local/code-no-any-casts + 489: // eslint-disable-next-line local/code-no-any-casts + 541: // eslint-disable-next-line local/code-no-any-casts + 573: // eslint-disable-next-line local/code-no-any-casts + 603: // eslint-disable-next-line local/code-no-any-casts + 627: // eslint-disable-next-line local/code-no-any-casts + 686: // eslint-disable-next-line local/code-no-any-casts + 755: // eslint-disable-next-line local/code-no-any-casts + 833: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/services/views/test/browser/viewDescriptorService.test.ts: + 25: // eslint-disable-next-line local/code-no-any-casts + 27: // eslint-disable-next-line local/code-no-any-casts + 335: // eslint-disable-next-line local/code-no-any-casts + 395: // eslint-disable-next-line local/code-no-any-casts + 531: // eslint-disable-next-line local/code-no-any-casts + 594: // eslint-disable-next-line local/code-no-any-casts + 645: // eslint-disable-next-line local/code-no-any-casts + 678: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/browser/part.test.ts: + 135: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/browser/window.test.ts: + 42: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/browser/workbenchTestServices.ts: + 305: // eslint-disable-next-line local/code-no-any-casts + 698: // eslint-disable-next-line local/code-no-any-casts + 1065: // eslint-disable-next-line local/code-no-any-casts + 1970: // eslint-disable-next-line local/code-no-any-casts + 1988: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/browser/parts/editor/editorInput.test.ts: + 98: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/browser/parts/editor/editorPane.test.ts: + 131: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/browser/parts/editor/resourceEditorInput.test.ts: + 95: // eslint-disable-next-line local/code-no-any-casts + 106: // eslint-disable-next-line local/code-no-any-casts + 113: // eslint-disable-next-line local/code-no-any-casts + 120: // eslint-disable-next-line local/code-no-any-casts + 127: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/common/resources.test.ts: + 51: // eslint-disable-next-line local/code-no-any-casts + 59: // eslint-disable-next-line local/code-no-any-casts + 72: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/common/workbenchTestServices.ts: + 293: // eslint-disable-next-line local/code-no-any-casts + +src/vs/workbench/test/electron-browser/workbenchTestServices.ts: + 257: // eslint-disable-next-line local/code-no-any-casts + +test/automation/src/code.ts: + 128: // eslint-disable-next-line local/code-no-any-casts + +test/automation/src/terminal.ts: + 315: // eslint-disable-next-line local/code-no-any-casts + +test/mcp/src/application.ts: + 250: // eslint-disable-next-line local/code-no-any-casts + +test/mcp/src/playwright.ts: + 17: // eslint-disable-next-line local/code-no-any-casts + +test/mcp/src/automationTools/problems.ts: + 76: // eslint-disable-next-line local/code-no-any-casts diff --git a/.vscode/settings.json b/.vscode/settings.json index 1208702206c50..9ac42bf24a4b9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,14 @@ { // --- Chat --- - // "inlineChat.enableV2": true, + "inlineChat.enableV2": true, + "chat.tools.terminal.autoApprove": { + "/^npm (test|lint|run compile)\\b/": true, + "/^npx tsc\\b.*--noEmit/": true, + "scripts/test.bat": true, + "scripts/test.sh": true, + "scripts/test-integration.bat": true, + "scripts/test-integration.sh": true, + }, // --- Editor --- "editor.insertSpaces": false, @@ -10,7 +18,6 @@ // "editor.experimental.preferTreeSitter.typescript": true, // "editor.experimental.preferTreeSitter.regex": true, // "editor.experimental.preferTreeSitter.css": true, - // --- Language Specific --- "[plaintext]": { "files.insertFinalNewline": false @@ -55,7 +62,6 @@ "**/yarn.lock": true, "**/package-lock.json": true, "**/Cargo.lock": true, - "build/**/*.js": true, "out/**": true, "out-build/**": true, "out-vscode/**": true, @@ -67,12 +73,6 @@ "test/automation/out/**": true, "test/integration/browser/out/**": true }, - "files.readonlyExclude": { - "build/builtin/*.js": true, - "build/monaco/*.js": true, - "build/npm/*.js": true, - "build/*.js": true - }, // --- Search --- "search.exclude": { @@ -93,7 +93,8 @@ "src/vs/workbench/api/test/browser/extHostDocumentData.test.perf-data.ts": true, "src/vs/base/test/node/uri.test.data.txt": true, "src/vs/editor/test/node/diffing/fixtures/**": true, - "build/loader.min": true + "build/loader.min": true, + "**/*.snap": true, }, // --- TypeScript --- @@ -130,6 +131,7 @@ "git.ignoreLimitWarning": true, "git.branchProtection": [ "main", + "main-*", "distro", "release/*" ], @@ -203,12 +205,13 @@ ], // --- Workbench --- - "remote.extensionKind": { - "msjsdiag.debugger-for-chrome": "workspace" - }, - "terminal.integrated.suggest.enabled": true, - "application.experimental.rendererProfiling": true, - + // "application.experimental.rendererProfiling": true, // https://github.com/microsoft/vscode/issues/265654 "editor.aiStats.enabled": true, // Team selfhosting on ai stats - "chat.checkpoints.showFileChanges": true + "azureMcp.enabledServices": [ + "kusto" // Needed for kusto tool in data.prompt.md + ], + "azureMcp.serverMode": "all", + "azureMcp.readOnly": true, + "chat.tools.terminal.outputLocation": "none", + "debug.breakpointsView.presentation": "tree" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3bcb5d5c6fcc2..6ae56ad639e28 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -198,7 +198,13 @@ "windows": { "command": ".\\scripts\\code-server.bat" }, - "args": ["--no-launch", "--connection-token", "dev-token", "--port", "8080"], + "args": [ + "--no-launch", + "--connection-token", + "dev-token", + "--port", + "8080" + ], "label": "Run code server", "isBackground": true, "problemMatcher": { @@ -220,7 +226,12 @@ "windows": { "command": ".\\scripts\\code-web.bat" }, - "args": ["--port", "8080", "--browser", "none"], + "args": [ + "--port", + "8080", + "--browser", + "none" + ], "label": "Run code web", "isBackground": true, "problemMatcher": { @@ -246,7 +257,7 @@ }, { "type": "shell", - "command": "node build/lib/preLaunch.js", + "command": "node build/lib/preLaunch.ts", "label": "Ensure Prelaunch Dependencies", "presentation": { "reveal": "silent", @@ -268,23 +279,21 @@ "detail": "node_modules/tsec/bin/tsec -p src/tsconfig.json --noEmit" }, { - // Used for monaco editor playground launch config - "label": "Launch Http Server", + "label": "Launch Monaco Editor Vite", "type": "shell", - "command": "node_modules/.bin/ts-node -T ./scripts/playground-server", - "isBackground": true, - "problemMatcher": { - "pattern": { - "regexp": "" - }, - "background": { - "activeOnStart": true, - "beginsPattern": "never match", - "endsPattern": ".*" - } + "command": "npm run dev", + "options": { + "cwd": "./build/vite/" }, - "dependsOn": [ - "Core - Build" + "isBackground": true, + }, + { + "label": "Launch MCP Server", + "type": "shell", + "command": "cd test/mcp && npm run compile && npm run start-http", + "isBackground": true, + "problemMatcher": [ + "$tsc" ] } ] diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000000..d6abb76ab8384 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,5 @@ +# VS Code Agents Instructions + +This file provides instructions for AI coding agents working with the VS Code codebase. + +For detailed project overview, architecture, coding guidelines, and validation steps, see the [Copilot Instructions](.github/copilot-instructions.md). diff --git a/README.md b/README.md index 61df8fc6bb44e..6f1e94e484413 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # Visual Studio Code - Open Source ("Code - OSS") - [![Feature Requests](https://img.shields.io/github/issues/microsoft/vscode/feature-request.svg)](https://github.com/microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) [![Bugs](https://img.shields.io/github/issues/microsoft/vscode/bug.svg)](https://github.com/microsoft/vscode/issues?utf8=✓&q=is%3Aissue+is%3Aopen+label%3Abug) [![Gitter](https://img.shields.io/badge/chat-on%20gitter-yellow.svg)](https://gitter.im/Microsoft/vscode) @@ -45,7 +44,7 @@ please see the document [How to Contribute](https://github.com/microsoft/vscode/ * Upvote [popular feature requests](https://github.com/microsoft/vscode/issues?q=is%3Aopen+is%3Aissue+label%3Afeature-request+sort%3Areactions-%2B1-desc) * [File an issue](https://github.com/microsoft/vscode/issues) * Connect with the extension author community on [GitHub Discussions](https://github.com/microsoft/vscode-discussions/discussions) or [Slack](https://aka.ms/vscode-dev-community) -* Follow [@code](https://twitter.com/code) and let us know what you think! +* Follow [@code](https://x.com/code) and let us know what you think! See our [wiki](https://github.com/microsoft/vscode/wiki/Feedback-Channels) for a description of each of these channels and information on some other available community-driven channels. @@ -55,7 +54,7 @@ Many of the core components and extensions to VS Code live in their own reposito ## Bundled Extensions -VS Code includes a set of built-in extensions located in the [extensions](extensions) folder, including grammars and snippets for many languages. Extensions that provide rich language support (code completion, Go to Definition) for a language have the suffix `language-features`. For example, the `json` extension provides coloring for `JSON` and the `json-language-features` extension provides rich language support for `JSON`. +VS Code includes a set of built-in extensions located in the [extensions](extensions) folder, including grammars and snippets for many languages. Extensions that provide rich language support (inline suggestions, Go to Definition) for a language have the suffix `language-features`. For example, the `json` extension provides coloring for `JSON` and the `json-language-features` extension provides rich language support for `JSON`. ## Development Container @@ -66,7 +65,7 @@ This repository includes a Visual Studio Code Dev Containers / GitHub Codespaces * For Codespaces, install the [GitHub Codespaces](https://marketplace.visualstudio.com/items?itemName=GitHub.codespaces) extension in VS Code, and use the **Codespaces: Create New Codespace** command. -Docker / the Codespace should have at least **4 Cores and 6 GB of RAM (8 GB recommended)** to run full build. See the [development container README](.devcontainer/README.md) for more information. +Docker / the Codespace should have at least **4 Cores and 6 GB of RAM (8 GB recommended)** to run a full build. See the [development container README](.devcontainer/README.md) for more information. ## Code of Conduct diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 8ca3533fe0862..096807654bf1b 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -524,576 +524,30 @@ Title to copyright in this work will at all times remain with copyright holders. --------------------------------------------------------- -dompurify 3.1.7 - Apache 2.0 -https://github.com/cure53/DOMPurify +dotenv-org/dotenv-vscode 0.26.0 - MIT License +https://github.com/dotenv-org/dotenv-vscode -DOMPurify -Copyright 2025 Dr.-Ing. Mario Heiderich, Cure53 - -DOMPurify is free software; you can redistribute it and/or modify it under the -terms of either: - -a) the Apache License Version 2.0, or -b) the Mozilla Public License Version 2.0 - ------------------------------------------------------------------------------ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ------------------------------------------------------------------------------ -Mozilla Public License, version 2.0 - -1. Definitions - -1.1. "Contributor" - - means each individual or legal entity that creates, contributes to the - creation of, or owns Covered Software. - -1.2. "Contributor Version" - - means the combination of the Contributions of others (if any) used by a - Contributor and that particular Contributor’s Contribution. - -1.3. "Contribution" - - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - - means Source Code Form to which the initial Contributor has attached the - notice in Exhibit A, the Executable Form of such Source Code Form, and - Modifications of such Source Code Form, in each case including portions - thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - a. that the initial Contributor has attached the notice described in - Exhibit B to the Covered Software; or - - b. that the Covered Software was made available under the terms of version - 1.1 or earlier of the License, but not also under the terms of a - Secondary License. - -1.6. "Executable Form" - - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - - means a work that combines Covered Software with other material, in a separate - file or files, that is not Covered Software. - -1.8. "License" - - means this document. - -1.9. "Licensable" - - means having the right to grant, to the maximum extent possible, whether at the - time of the initial grant or subsequently, any and all of the rights conveyed by - this License. - -1.10. "Modifications" - - means any of the following: - - a. any file in Source Code Form that results from an addition to, deletion - from, or modification of the contents of Covered Software; or - - b. any new file in Source Code Form that contains any Covered Software. - -1.11. "Patent Claims" of a Contributor - - means any patent claim(s), including without limitation, method, process, - and apparatus claims, in any patent Licensable by such Contributor that - would be infringed, but for the grant of the License, by the making, - using, selling, offering for sale, having made, import, or transfer of - either its Contributions or its Contributor Version. - -1.12. "Secondary License" - - means either the GNU General Public License, Version 2.0, the GNU Lesser - General Public License, Version 2.1, the GNU Affero General Public - License, Version 3.0, or any later versions of those licenses. - -1.13. "Source Code Form" - - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that controls, is - controlled by, or is under common control with You. For purposes of this - definition, "control" means (a) the power, direct or indirect, to cause - the direction or management of such entity, whether by contract or - otherwise, or (b) ownership of more than fifty percent (50%) of the - outstanding shares or beneficial ownership of such entity. - - -2. License Grants and Conditions - -2.1. Grants - - Each Contributor hereby grants You a world-wide, royalty-free, - non-exclusive license: - - a. under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or as - part of a Larger Work; and - - b. under Patent Claims of such Contributor to make, use, sell, offer for - sale, have made, import, and otherwise transfer either its Contributions - or its Contributor Version. - -2.2. Effective Date - - The licenses granted in Section 2.1 with respect to any Contribution become - effective for each Contribution on the date the Contributor first distributes - such Contribution. - -2.3. Limitations on Grant Scope - - The licenses granted in this Section 2 are the only rights granted under this - License. No additional rights or licenses will be implied from the distribution - or licensing of Covered Software under this License. Notwithstanding Section - 2.1(b) above, no patent license is granted by a Contributor: - - a. for any code that a Contributor has removed from Covered Software; or - - b. for infringements caused by: (i) Your and any other third party’s - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - - c. under Patent Claims infringed by Covered Software in the absence of its - Contributions. - - This License does not grant any rights in the trademarks, service marks, or - logos of any Contributor (except as may be necessary to comply with the - notice requirements in Section 3.4). - -2.4. Subsequent Licenses - - No Contributor makes additional grants as a result of Your choice to - distribute the Covered Software under a subsequent version of this License - (see Section 10.2) or under the terms of a Secondary License (if permitted - under the terms of Section 3.3). - -2.5. Representation - - Each Contributor represents that the Contributor believes its Contributions - are its original creation(s) or it has sufficient rights to grant the - rights to its Contributions conveyed by this License. - -2.6. Fair Use - - This License is not intended to limit any rights You have under applicable - copyright doctrines of fair use, fair dealing, or other equivalents. - -2.7. Conditions - - Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in - Section 2.1. - - -3. Responsibilities - -3.1. Distribution of Source Form - - All distribution of Covered Software in Source Code Form, including any - Modifications that You create or to which You contribute, must be under the - terms of this License. You must inform recipients that the Source Code Form - of the Covered Software is governed by the terms of this License, and how - they can obtain a copy of this License. You may not attempt to alter or - restrict the recipients’ rights in the Source Code Form. - -3.2. Distribution of Executable Form - - If You distribute Covered Software in Executable Form then: - - a. such Covered Software must also be made available in Source Code Form, - as described in Section 3.1, and You must inform recipients of the - Executable Form how they can obtain a copy of such Source Code Form by - reasonable means in a timely manner, at a charge no more than the cost - of distribution to the recipient; and - - b. You may distribute such Executable Form under the terms of this License, - or sublicense it under different terms, provided that the license for - the Executable Form does not attempt to limit or alter the recipients’ - rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - - You may create and distribute a Larger Work under terms of Your choice, - provided that You also comply with the requirements of this License for the - Covered Software. If the Larger Work is a combination of Covered Software - with a work governed by one or more Secondary Licenses, and the Covered - Software is not Incompatible With Secondary Licenses, this License permits - You to additionally distribute such Covered Software under the terms of - such Secondary License(s), so that the recipient of the Larger Work may, at - their option, further distribute the Covered Software under the terms of - either this License or such Secondary License(s). - -3.4. Notices - - You may not remove or alter the substance of any license notices (including - copyright notices, patent notices, disclaimers of warranty, or limitations - of liability) contained within the Source Code Form of the Covered - Software, except that You may alter any license notices to the extent - required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - - You may choose to offer, and to charge a fee for, warranty, support, - indemnity or liability obligations to one or more recipients of Covered - Software. However, You may do so only on Your own behalf, and not on behalf - of any Contributor. You must make it absolutely clear that any such - warranty, support, indemnity, or liability obligation is offered by You - alone, and You hereby agree to indemnify every Contributor for any - liability incurred by such Contributor as a result of warranty, support, - indemnity or liability terms You offer. You may include additional - disclaimers of warranty and limitations of liability specific to any - jurisdiction. - -4. Inability to Comply Due to Statute or Regulation - - If it is impossible for You to comply with any of the terms of this License - with respect to some or all of the Covered Software due to statute, judicial - order, or regulation then You must: (a) comply with the terms of this License - to the maximum extent possible; and (b) describe the limitations and the code - they affect. Such description must be placed in a text file included with all - distributions of the Covered Software under this License. Except to the - extent prohibited by statute or regulation, such description must be - sufficiently detailed for a recipient of ordinary skill to be able to - understand it. - -5. Termination - -5.1. The rights granted under this License will terminate automatically if You - fail to comply with any of its terms. However, if You become compliant, - then the rights granted under this License from a particular Contributor - are reinstated (a) provisionally, unless and until such Contributor - explicitly and finally terminates Your grants, and (b) on an ongoing basis, - if such Contributor fails to notify You of the non-compliance by some - reasonable means prior to 60 days after You have come back into compliance. - Moreover, Your grants from a particular Contributor are reinstated on an - ongoing basis if such Contributor notifies You of the non-compliance by - some reasonable means, this is the first time You have received notice of - non-compliance with this License from such Contributor, and You become - compliant prior to 30 days after Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent - infringement claim (excluding declaratory judgment actions, counter-claims, - and cross-claims) alleging that a Contributor Version directly or - indirectly infringes any patent, then the rights granted to You by any and - all Contributors for the Covered Software under Section 2.1 of this License - shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user - license agreements (excluding distributors and resellers) which have been - validly granted by You or Your distributors under this License prior to - termination shall survive termination. - -6. Disclaimer of Warranty - - Covered Software is provided under this License on an "as is" basis, without - warranty of any kind, either expressed, implied, or statutory, including, - without limitation, warranties that the Covered Software is free of defects, - merchantable, fit for a particular purpose or non-infringing. The entire - risk as to the quality and performance of the Covered Software is with You. - Should any Covered Software prove defective in any respect, You (not any - Contributor) assume the cost of any necessary servicing, repair, or - correction. This disclaimer of warranty constitutes an essential part of this - License. No use of any Covered Software is authorized under this License - except under this disclaimer. - -7. Limitation of Liability - - Under no circumstances and under no legal theory, whether tort (including - negligence), contract, or otherwise, shall any Contributor, or anyone who - distributes Covered Software as permitted above, be liable to You for any - direct, indirect, special, incidental, or consequential damages of any - character including, without limitation, damages for lost profits, loss of - goodwill, work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses, even if such party shall have been - informed of the possibility of such damages. This limitation of liability - shall not apply to liability for death or personal injury resulting from such - party’s negligence to the extent applicable law prohibits such limitation. - Some jurisdictions do not allow the exclusion or limitation of incidental or - consequential damages, so this exclusion and limitation may not apply to You. - -8. Litigation - - Any litigation relating to this License may be brought only in the courts of - a jurisdiction where the defendant maintains its principal place of business - and such litigation shall be governed by laws of that jurisdiction, without - reference to its conflict-of-law provisions. Nothing in this Section shall - prevent a party’s ability to bring cross-claims or counter-claims. - -9. Miscellaneous - - This License represents the complete agreement concerning the subject matter - hereof. If any provision of this License is held to be unenforceable, such - provision shall be reformed only to the extent necessary to make it - enforceable. Any law or regulation which provides that the language of a - contract shall be construed against the drafter shall not be used to construe - this License against a Contributor. - - -10. Versions of the License - -10.1. New Versions - - Mozilla Foundation is the license steward. Except as provided in Section - 10.3, no one other than the license steward has the right to modify or - publish new versions of this License. Each version will be given a - distinguishing version number. - -10.2. Effect of New Versions - - You may distribute the Covered Software under the terms of the version of - the License under which You originally received the Covered Software, or - under the terms of any subsequent version published by the license - steward. - -10.3. Modified Versions - - If you create software not governed by this License, and you want to - create a new license for such software, you may create and use a modified - version of this License if you rename the license and remove any - references to the name of the license steward (except to note that such - modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses - If You choose to distribute Source Code Form that is Incompatible With - Secondary Licenses under the terms of this version of the License, the - notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice - - This Source Code Form is subject to the - terms of the Mozilla Public License, v. - 2.0. If a copy of the MPL was not - distributed with this file, You can - obtain one at - http://mozilla.org/MPL/2.0/. +MIT License -If it is not possible or desirable to put the notice in a particular file, then -You may include the notice in a location (such as a LICENSE file in a relevant -directory) where a recipient would be likely to look for such a notice. +Copyright (c) 2022 Scott Motte -You may add additional accurate notices of copyright ownership. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -Exhibit B - "Incompatible With Secondary Licenses" Notice +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - This Source Code Form is "Incompatible - With Secondary Licenses", as defined by - the Mozilla Public License, v. 2.0. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- @@ -1546,7 +1000,7 @@ SOFTWARE. --------------------------------------------------------- -jlelong/vscode-latex-basics 1.14.0 - MIT +jlelong/vscode-latex-basics 1.15.0 - MIT https://github.com/jlelong/vscode-latex-basics Copyright (c) vscode-latex-basics authors @@ -1662,7 +1116,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SO --------------------------------------------------------- -language-docker 0.0.0 - Apache-2.0 +language-docker 28.3.3 - Apache-2.0 https://github.com/moby/moby Apache License @@ -2219,6 +1673,35 @@ SOFTWARE. --------------------------------------------------------- +PSReadLine 2.4.4-beta4 +https://github.com/PowerShell/PSReadLine + +Copyright (c) 2013, Jason Shirk +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------- + +--------------------------------------------------------- + RedCMD/YAML-Syntax-Highlighter 1.3.2 - MIT https://github.com/RedCMD/YAML-Syntax-Highlighter @@ -2794,7 +2277,7 @@ written authorization of the copyright holder. --------------------------------------------------------- -vscode-codicons 0.0.14 - MIT and Creative Commons Attribution 4.0 +vscode-codicons 0.0.41 - MIT and Creative Commons Attribution 4.0 https://github.com/microsoft/vscode-codicons Attribution 4.0 International @@ -3192,6 +2675,10 @@ the avoidance of doubt, this paragraph does not form part of the public licenses. Creative Commons may be contacted at creativecommons.org. + +--- + +Git Logo by [Jason Long](https://bsky.app/profile/jasonlong.me) is licensed under the [Creative Commons Attribution 3.0 Unported License](https://creativecommons.org/licenses/by/3.0/). --------------------------------------------------------- --------------------------------------------------------- diff --git a/build/.cachesalt b/build/.cachesalt index d55dde3c03580..9cb204bfdb3d0 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2025-07-23T19:44:03.051Z +2025-12-10T20:11:58.882Z diff --git a/build/.gitignore b/build/.gitignore index 61cf49cb9a117..679674617c7c2 100644 --- a/build/.gitignore +++ b/build/.gitignore @@ -1 +1,2 @@ *.js.map +lib/policies/policyDto.* diff --git a/build/.moduleignore b/build/.moduleignore index 3e654cfe5c396..fc7c538c6cc9c 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -60,6 +60,8 @@ fsevents/test/** !@vscode/tree-sitter-wasm/wasm/tree-sitter-regex.wasm !@vscode/tree-sitter-wasm/wasm/tree-sitter-ini.wasm !@vscode/tree-sitter-wasm/wasm/tree-sitter-css.wasm +!@vscode/tree-sitter-wasm/wasm/tree-sitter-powershell.wasm +!@vscode/tree-sitter-wasm/wasm/tree-sitter-bash.wasm native-keymap/binding.gyp native-keymap/build/** @@ -110,11 +112,11 @@ node-pty/third_party/** !node-pty/build/Release/conpty/conpty.dll !node-pty/build/Release/conpty/OpenConsole.exe -@parcel/watcher/binding.gyp -@parcel/watcher/build/** -@parcel/watcher/prebuilds/** -@parcel/watcher/src/** -!@parcel/watcher/build/Release/*.node +@vscode/watcher/binding.gyp +@vscode/watcher/build/** +@vscode/watcher/prebuilds/** +@vscode/watcher/src/** +!@vscode/watcher/build/Release/*.node vsda/** !vsda/index.js diff --git a/build/.webignore b/build/.webignore index 31c366cc1faf1..68f8721dba89a 100644 --- a/build/.webignore +++ b/build/.webignore @@ -3,8 +3,10 @@ **/*.txt **/*.json **/*.md +**/*.ts **/*.d.ts **/*.js.map +**/*.mjs.map **/LICENSE **/CONTRIBUTORS @@ -29,6 +31,9 @@ vscode-textmate/webpack.config.js @xterm/addon-ligatures/src/** @xterm/addon-ligatures/out/** +@xterm/addon-progress/src/** +@xterm/addon-progress/out/** + @xterm/addon-search/src/** @xterm/addon-search/out/** @xterm/addon-search/fixtures/** diff --git a/build/azure-pipelines/alpine/cli-build-alpine.yml b/build/azure-pipelines/alpine/cli-build-alpine.yml deleted file mode 100644 index 6c0543d2e7c7c..0000000000000 --- a/build/azure-pipelines/alpine/cli-build-alpine.yml +++ /dev/null @@ -1,108 +0,0 @@ -parameters: - - name: VSCODE_BUILD_ALPINE - type: boolean - default: false - - name: VSCODE_BUILD_ALPINE_ARM64 - type: boolean - default: false - - name: VSCODE_QUALITY - type: string - - name: VSCODE_CHECK_ONLY - type: boolean - default: false - -steps: - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - template: ../cli/cli-apply-patches.yml@self - - - script: | - set -e - npm ci - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies - - - task: Npm@1 - displayName: Download openssl prebuilt - inputs: - command: custom - customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 - customRegistry: useFeed - customFeed: "Monaco/openssl-prebuilt" - workingDir: $(Build.ArtifactStagingDirectory) - - - script: | - set -e - mkdir $(Build.ArtifactStagingDirectory)/openssl - tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl - displayName: Extract openssl prebuilt - - # inspired by: https://github.com/emk/rust-musl-builder/blob/main/Dockerfile - - bash: | - set -e - sudo apt-get update - sudo apt-get install -yq build-essential musl-dev musl-tools linux-libc-dev pkgconf xutils-dev lld - sudo ln -s "/usr/bin/g++" "/usr/bin/musl-g++" || echo "link exists" - displayName: Install musl build dependencies - - - template: ../cli/install-rust-posix.yml@self - parameters: - targets: - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}: - - aarch64-unknown-linux-musl - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - - x86_64-unknown-linux-musl - - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_CLI_TARGET: aarch64-unknown-linux-musl - VSCODE_CLI_ARTIFACT: vscode_cli_alpine_arm64_cli - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux-musl/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux-musl/include - OPENSSL_STATIC: "1" - SYSROOT_ARCH: arm64 - IS_MUSL: "1" - - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_CLI_TARGET: x86_64-unknown-linux-musl - VSCODE_CLI_ARTIFACT: vscode_cli_alpine_x64_cli - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_ENV: - CXX_aarch64-unknown-linux-musl: musl-g++ - CC_aarch64-unknown-linux-musl: musl-gcc - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux-musl/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux-musl/include - OPENSSL_STATIC: "1" - - - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}: - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_alpine_arm64_cli.tar.gz - artifactName: vscode_cli_alpine_arm64_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Alpine arm64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish vscode_cli_alpine_arm64_cli artifact - - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_alpine_x64_cli.tar.gz - artifactName: vscode_cli_alpine_x64_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Alpine x64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish vscode_cli_alpine_x64_cli artifact diff --git a/build/azure-pipelines/alpine/product-build-alpine-cli.yml b/build/azure-pipelines/alpine/product-build-alpine-cli.yml new file mode 100644 index 0000000000000..9f3f60a6b241d --- /dev/null +++ b/build/azure-pipelines/alpine/product-build-alpine-cli.yml @@ -0,0 +1,102 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_CHECK_ONLY + type: boolean + default: false + - name: VSCODE_QUALITY + type: string + +jobs: +- job: AlpineCLI_${{ parameters.VSCODE_ARCH }} + displayName: Alpine (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 60 + pool: + name: 1es-ubuntu-22.04-x64 + os: linux + variables: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + templateContext: + outputs: + - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_alpine_$(VSCODE_ARCH)_cli.tar.gz + artifactName: vscode_cli_alpine_$(VSCODE_ARCH)_cli + displayName: Publish vscode_cli_alpine_$(VSCODE_ARCH)_cli artifact + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli + sbomPackageName: "VS Code Alpine $(VSCODE_ARCH) CLI" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../cli/cli-apply-patches.yml@self + + - script: | + set -e + npm ci + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies + + - task: Npm@1 + displayName: Download openssl prebuilt + inputs: + command: custom + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 + customRegistry: useFeed + customFeed: "Monaco/openssl-prebuilt" + workingDir: $(Build.ArtifactStagingDirectory) + + - script: | + set -e + mkdir $(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + displayName: Extract openssl prebuilt + + # inspired by: https://github.com/emk/rust-musl-builder/blob/main/Dockerfile + - bash: | + set -e + sudo apt-get update + sudo apt-get install -yq build-essential musl-dev musl-tools linux-libc-dev pkgconf xutils-dev lld + sudo ln -s "/usr/bin/g++" "/usr/bin/musl-g++" || echo "link exists" + displayName: Install musl build dependencies + + - template: ../cli/install-rust-posix.yml@self + parameters: + targets: + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - x86_64-unknown-linux-musl + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - aarch64-unknown-linux-musl + + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_CLI_TARGET: x86_64-unknown-linux-musl + VSCODE_CLI_ARTIFACT: vscode_cli_alpine_x64_cli + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_ENV: + CXX_aarch64-unknown-linux-musl: musl-g++ + CC_aarch64-unknown-linux-musl: musl-gcc + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux-musl/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux-musl/include + OPENSSL_STATIC: "1" + + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_CLI_TARGET: aarch64-unknown-linux-musl + VSCODE_CLI_ARTIFACT: vscode_cli_alpine_arm64_cli + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux-musl/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux-musl/include + OPENSSL_STATIC: "1" + SYSROOT_ARCH: arm64 + IS_MUSL: "1" diff --git a/build/azure-pipelines/alpine/product-build-alpine-node-modules.yml b/build/azure-pipelines/alpine/product-build-alpine-node-modules.yml new file mode 100644 index 0000000000000..cc53000a15c17 --- /dev/null +++ b/build/azure-pipelines/alpine/product-build-alpine-node-modules.yml @@ -0,0 +1,121 @@ +parameters: + - name: VSCODE_ARCH + type: string + +jobs: + - job: LinuxAlpine_${{ parameters.VSCODE_ARCH }} + displayName: Linux Alpine (${{ upper(parameters.VSCODE_ARCH) }}) + pool: + name: 1es-ubuntu-22.04-x64 + os: linux + timeoutInMinutes: 60 + variables: + NPM_ARCH: ${{ parameters.VSCODE_ARCH }} + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - script: node build/setup-npm-registry.ts $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts alpine $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - task: Docker@1 + inputs: + azureSubscriptionEndpoint: vscode + azureContainerRegistry: vscodehub.azurecr.io + command: "Run an image" + imageName: "vscode-linux-build-agent:alpine-$(VSCODE_ARCH)" + containerCommand: uname + displayName: "Pull image" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: sudo apt-get update && sudo apt-get install -y libkrb5-dev + displayName: Install build dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + mkdir -p .build/nodejs-musl + NODE_VERSION=$(grep '^target=' remote/.npmrc | cut -d '"' -f 2) + BUILD_ID=$(grep '^ms_build_id=' remote/.npmrc | cut -d '"' -f 2) + gh release download "v${NODE_VERSION}-${BUILD_ID}" -R microsoft/vscode-node -p "node-v${NODE_VERSION}-linux-${VSCODE_ARCH}-musl.tar.gz" --dir .build/nodejs-musl --clobber + tar -xzf ".build/nodejs-musl/node-v${NODE_VERSION}-linux-${VSCODE_ARCH}-musl.tar.gz" -C ".build/nodejs-musl" --strip-components=1 + rm ".build/nodejs-musl/node-v${NODE_VERSION}-linux-${VSCODE_ARCH}-musl.tar.gz" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download NodeJS MUSL + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + npm_config_arch: $(NPM_ARCH) + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:alpine-$(VSCODE_ARCH) + VSCODE_HOST_MOUNT: "/mnt/vss/_work/1/s" + VSCODE_NPMRC_PATH: $(NPMRC_PATH) + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: node build/azure-pipelines/distro/mixin-npm.ts + displayName: Mixin distro node modules + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive diff --git a/build/azure-pipelines/alpine/product-build-alpine.yml b/build/azure-pipelines/alpine/product-build-alpine.yml index 95aa6fa2449b4..5c5714e9d5b12 100644 --- a/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/build/azure-pipelines/alpine/product-build-alpine.yml @@ -1,182 +1,204 @@ -steps: - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - template: ../distro/download-distro.yml@self - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output - - - script: node build/setup-npm-registry.js $NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js alpine $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash - displayName: Prepare node_modules cache key - - - task: Cache@2 - inputs: - key: '"node_modules" | .build/packagelockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - task: Docker@1 - inputs: - azureSubscriptionEndpoint: vscode - azureContainerRegistry: vscodehub.azurecr.io - command: "Run an image" - imageName: "vscode-linux-build-agent:alpine-$(VSCODE_ARCH)" - containerCommand: uname - displayName: "Pull image" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: sudo apt-get update && sudo apt-get install -y libkrb5-dev - displayName: Install build dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - env: - npm_config_arch: $(NPM_ARCH) - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:alpine-$(VSCODE_ARCH) - VSCODE_HOST_MOUNT: "/mnt/vss/_work/1/s" - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: node build/azure-pipelines/distro/mixin-npm - displayName: Mixin distro node modules - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive - - - script: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - - - template: ../common/install-builtin-extensions.yml@self - - - script: | - set -e - TARGET=$([ "$VSCODE_ARCH" == "x64" ] && echo "linux-alpine" || echo "alpine-arm64") # TODO@joaomoreno - npm run gulp vscode-reh-$TARGET-min-ci - (cd .. && mv vscode-reh-$TARGET vscode-server-$TARGET) # TODO@joaomoreno - ARCHIVE_PATH=".build/linux/server/vscode-server-$TARGET.tar.gz" - DIR_PATH="$(realpath ../vscode-server-$TARGET)" - mkdir -p $(dirname $ARCHIVE_PATH) - tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-$TARGET - echo "##vso[task.setvariable variable=SERVER_DIR_PATH]$DIR_PATH" - echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build server - - - script: | - set -e - TARGET=$([ "$VSCODE_ARCH" == "x64" ] && echo "linux-alpine" || echo "alpine-arm64") - npm run gulp vscode-reh-web-$TARGET-min-ci - (cd .. && mv vscode-reh-web-$TARGET vscode-server-$TARGET-web) # TODO@joaomoreno - ARCHIVE_PATH=".build/linux/web/vscode-server-$TARGET-web.tar.gz" - DIR_PATH="$(realpath ../vscode-server-$TARGET-web)" - mkdir -p $(dirname $ARCHIVE_PATH) - tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-$TARGET-web - echo "##vso[task.setvariable variable=WEB_DIR_PATH]$DIR_PATH" - echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build server (web) - - - script: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" - condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) - displayName: Generate artifact prefix - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_server_alpine_$(VSCODE_ARCH)_archive-unsigned - sbomBuildDropPath: $(SERVER_DIR_PATH) - sbomPackageName: "VS Code Alpine $(VSCODE_ARCH) Server" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish server archive - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], ''), ne(variables['VSCODE_ARCH'], 'x64')) - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_alpine_$(VSCODE_ARCH)_archive-unsigned - sbomBuildDropPath: $(WEB_DIR_PATH) - sbomPackageName: "VS Code Alpine $(VSCODE_ARCH) Web" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish web server archive - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], ''), ne(variables['VSCODE_ARCH'], 'x64')) - - # same as above, keep legacy name - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_server_linux_alpine_archive-unsigned - sbomEnabled: false - displayName: Publish x64 server archive - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], ''), eq(variables['VSCODE_ARCH'], 'x64')) - - # same as above, keep legacy name - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_linux_alpine_archive-unsigned - sbomEnabled: false - displayName: Publish x64 web server archive - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], ''), eq(variables['VSCODE_ARCH'], 'x64')) +parameters: + - name: VSCODE_ARCH + type: string + +jobs: + - job: Alpine_${{ parameters.VSCODE_ARCH }} + displayName: Alpine (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 30 + pool: + name: 1es-ubuntu-22.04-x64 + os: linux + variables: + NPM_ARCH: ${{ parameters.VSCODE_ARCH }} + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + templateContext: + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out + outputs: + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + # keep legacy name + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/server/vscode-server-linux-alpine.tar.gz + artifactName: vscode_server_linux_alpine_archive-unsigned + displayName: Publish x64 server archive + sbomBuildDropPath: $(SERVER_DIR_PATH) + sbomPackageName: "VS Code Alpine x64 Server" + sbomPackageVersion: $(Build.SourceVersion) + # keep legacy name + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/web/vscode-server-linux-alpine-web.tar.gz + artifactName: vscode_web_linux_alpine_archive-unsigned + displayName: Publish x64 web server archive + sbomBuildDropPath: $(WEB_DIR_PATH) + sbomPackageName: "VS Code Alpine x64 Web" + sbomPackageVersion: $(Build.SourceVersion) + - ${{ if ne(parameters.VSCODE_ARCH, 'x64') }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/server/vscode-server-alpine-$(VSCODE_ARCH).tar.gz + artifactName: vscode_server_alpine_$(VSCODE_ARCH)_archive-unsigned + displayName: Publish server archive + sbomBuildDropPath: $(SERVER_DIR_PATH) + sbomPackageName: "VS Code Alpine $(VSCODE_ARCH) Server" + sbomPackageVersion: $(Build.SourceVersion) + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/web/vscode-server-alpine-$(VSCODE_ARCH)-web.tar.gz + artifactName: vscode_web_alpine_$(VSCODE_ARCH)_archive-unsigned + displayName: Publish web server archive + sbomBuildDropPath: $(WEB_DIR_PATH) + sbomPackageName: "VS Code Alpine $(VSCODE_ARCH) Web" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output + + - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + displayName: Extract compilation output + + - script: node build/setup-npm-registry.ts $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts alpine $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: tar -xzf .build/node_modules_cache/cache.tgz + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Extract node_modules cache + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - task: Docker@1 + inputs: + azureSubscriptionEndpoint: vscode + azureContainerRegistry: vscodehub.azurecr.io + command: "Run an image" + imageName: "vscode-linux-build-agent:alpine-$(VSCODE_ARCH)" + containerCommand: uname + displayName: "Pull image" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: sudo apt-get update && sudo apt-get install -y libkrb5-dev + displayName: Install build dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + mkdir -p .build/nodejs-musl + NODE_VERSION=$(grep '^target=' remote/.npmrc | cut -d '"' -f 2) + BUILD_ID=$(grep '^ms_build_id=' remote/.npmrc | cut -d '"' -f 2) + gh release download "v${NODE_VERSION}-${BUILD_ID}" -R microsoft/vscode-node -p "node-v${NODE_VERSION}-linux-${VSCODE_ARCH}-musl.tar.gz" --dir .build/nodejs-musl --clobber + tar -xzf ".build/nodejs-musl/node-v${NODE_VERSION}-linux-${VSCODE_ARCH}-musl.tar.gz" -C ".build/nodejs-musl" --strip-components=1 + rm ".build/nodejs-musl/node-v${NODE_VERSION}-linux-${VSCODE_ARCH}-musl.tar.gz" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download NodeJS MUSL + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + npm_config_arch: $(NPM_ARCH) + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:alpine-$(VSCODE_ARCH) + VSCODE_HOST_MOUNT: "/mnt/vss/_work/1/s" + VSCODE_NPMRC_PATH: $(NPMRC_PATH) + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: node build/azure-pipelines/distro/mixin-npm.ts + displayName: Mixin distro node modules + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive + + - script: node build/azure-pipelines/distro/mixin-quality.ts + displayName: Mixin distro quality + + - template: ../common/install-builtin-extensions.yml@self + + - script: | + set -e + TARGET=$([ "$VSCODE_ARCH" == "x64" ] && echo "linux-alpine" || echo "alpine-arm64") # TODO@joaomoreno + npm run gulp vscode-reh-$TARGET-min-ci + (cd .. && mv vscode-reh-$TARGET vscode-server-$TARGET) # TODO@joaomoreno + ARCHIVE_PATH="$(Build.ArtifactStagingDirectory)/out/server/vscode-server-$TARGET.tar.gz" + DIR_PATH="$(realpath ../vscode-server-$TARGET)" + mkdir -p $(dirname $ARCHIVE_PATH) + tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-$TARGET + echo "##vso[task.setvariable variable=SERVER_DIR_PATH]$DIR_PATH" + echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server + + - script: | + set -e + TARGET=$([ "$VSCODE_ARCH" == "x64" ] && echo "linux-alpine" || echo "alpine-arm64") + npm run gulp vscode-reh-web-$TARGET-min-ci + (cd .. && mv vscode-reh-web-$TARGET vscode-server-$TARGET-web) # TODO@joaomoreno + ARCHIVE_PATH="$(Build.ArtifactStagingDirectory)/out/web/vscode-server-$TARGET-web.tar.gz" + DIR_PATH="$(realpath ../vscode-server-$TARGET-web)" + mkdir -p $(dirname $ARCHIVE_PATH) + tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-$TARGET-web + echo "##vso[task.setvariable variable=WEB_DIR_PATH]$DIR_PATH" + echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server (web) diff --git a/build/azure-pipelines/cli/cli-apply-patches.yml b/build/azure-pipelines/cli/cli-apply-patches.yml index 2815124efb69e..e04951f3f5607 100644 --- a/build/azure-pipelines/cli/cli-apply-patches.yml +++ b/build/azure-pipelines/cli/cli-apply-patches.yml @@ -1,7 +1,7 @@ steps: - template: ../distro/download-distro.yml@self - - script: node build/azure-pipelines/distro/mixin-quality + - script: node build/azure-pipelines/distro/mixin-quality.ts displayName: Mixin distro quality - script: node .build/distro/cli-patches/index.js diff --git a/build/azure-pipelines/cli/cli-compile.yml b/build/azure-pipelines/cli/cli-compile.yml index 8c9eec62d5397..2abefa7b6a438 100644 --- a/build/azure-pipelines/cli/cli-compile.yml +++ b/build/azure-pipelines/cli/cli-compile.yml @@ -14,19 +14,11 @@ parameters: steps: - ${{ if contains(parameters.VSCODE_CLI_TARGET, '-windows-') }}: - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - pwsh: Write-Host "##vso[task.setvariable variable=VSCODE_CLI_PRODUCT_JSON]$(Build.SourcesDirectory)/product.json" - displayName: Set product.json path - - ${{ else }}: - - pwsh: Write-Host "##vso[task.setvariable variable=VSCODE_CLI_PRODUCT_JSON]$(Build.SourcesDirectory)/.build/distro/mixin/${{ parameters.VSCODE_QUALITY }}/product.json" - displayName: Set product.json path + - pwsh: Write-Host "##vso[task.setvariable variable=VSCODE_CLI_PRODUCT_JSON]$(Build.SourcesDirectory)/.build/distro/mixin/${{ parameters.VSCODE_QUALITY }}/product.json" + displayName: Set product.json path - ${{ else }}: - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: echo "##vso[task.setvariable variable=VSCODE_CLI_PRODUCT_JSON]$(Build.SourcesDirectory)/product.json" - displayName: Set product.json path - - ${{ else }}: - - script: echo "##vso[task.setvariable variable=VSCODE_CLI_PRODUCT_JSON]$(Build.SourcesDirectory)/.build/distro/mixin/${{ parameters.VSCODE_QUALITY }}/product.json" - displayName: Set product.json path + - script: echo "##vso[task.setvariable variable=VSCODE_CLI_PRODUCT_JSON]$(Build.SourcesDirectory)/.build/distro/mixin/${{ parameters.VSCODE_QUALITY }}/product.json" + displayName: Set product.json path - ${{ if parameters.VSCODE_CHECK_ONLY }}: - script: cargo clippy --target ${{ parameters.VSCODE_CLI_TARGET }} --bin=code @@ -43,7 +35,7 @@ steps: set -e if [ -n "$SYSROOT_ARCH" ]; then export VSCODE_SYSROOT_DIR=$(Build.SourcesDirectory)/.build/sysroots - node -e '(async () => { const { getVSCodeSysroot } = require("../build/linux/debian/install-sysroot.js"); await getVSCodeSysroot(process.env["SYSROOT_ARCH"], process.env["IS_MUSL"] === "1"); })()' + node -e 'import { getVSCodeSysroot } from "../build/linux/debian/install-sysroot.ts"; (async () => { await getVSCodeSysroot(process.env["SYSROOT_ARCH"], process.env["IS_MUSL"] === "1"); })()' if [ "$SYSROOT_ARCH" == "arm64" ]; then if [ -n "$IS_MUSL" ]; then export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER="$VSCODE_SYSROOT_DIR/output/bin/aarch64-linux-musl-gcc" diff --git a/build/azure-pipelines/cli/cli-darwin-sign.yml b/build/azure-pipelines/cli/cli-darwin-sign.yml deleted file mode 100644 index d702b82fc57a4..0000000000000 --- a/build/azure-pipelines/cli/cli-darwin-sign.yml +++ /dev/null @@ -1,61 +0,0 @@ -parameters: - - name: VSCODE_CLI_ARTIFACTS - type: object - default: [] - -steps: - - task: UseDotNet@2 - inputs: - version: 6.x - - - task: EsrpCodeSigning@5 - inputs: - UseMSIAuthentication: true - ConnectedServiceName: vscode-esrp - AppRegistrationClientId: $(ESRP_CLIENT_ID) - AppRegistrationTenantId: $(ESRP_TENANT_ID) - AuthAKVName: vscode-esrp - AuthSignCertName: esrp-sign - FolderPath: . - Pattern: noop - displayName: 'Install ESRP Tooling' - - - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - - task: DownloadPipelineArtifact@2 - displayName: Download ${{ target }} - inputs: - artifact: ${{ target }} - path: $(Build.ArtifactStagingDirectory)/pkg/${{ target }} - - - task: ExtractFiles@1 - displayName: Extract artifact - inputs: - archiveFilePatterns: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/*.zip - destinationFolder: $(Build.ArtifactStagingDirectory)/sign/${{ target }} - - - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Build.ArtifactStagingDirectory)/pkg "*.zip" - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Codesign - - - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll notarize-darwin $(Build.ArtifactStagingDirectory)/pkg "*.zip" - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Notarize - - - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - - script: | - set -e - ASSET_ID=$(echo "${{ target }}" | sed "s/unsigned_//") - mv $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/${{ target }}.zip $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/$ASSET_ID.zip - echo "##vso[task.setvariable variable=ASSET_ID]$ASSET_ID" - displayName: Set asset id variable - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/$(ASSET_ID).zip - artifactName: $(ASSET_ID) - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/${{ target }} - sbomPackageName: "VS Code macOS ${{ target }} CLI" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish signed artifact with ID $(ASSET_ID) diff --git a/build/azure-pipelines/cli/cli-win32-sign.yml b/build/azure-pipelines/cli/cli-win32-sign.yml deleted file mode 100644 index eb85e9e2e0619..0000000000000 --- a/build/azure-pipelines/cli/cli-win32-sign.yml +++ /dev/null @@ -1,70 +0,0 @@ -parameters: - - name: VSCODE_CLI_ARTIFACTS - type: object - default: [] - -steps: - - task: UseDotNet@2 - inputs: - version: 6.x - - - task: EsrpCodeSigning@5 - inputs: - UseMSIAuthentication: true - ConnectedServiceName: vscode-esrp - AppRegistrationClientId: $(ESRP_CLIENT_ID) - AppRegistrationTenantId: $(ESRP_TENANT_ID) - AuthAKVName: vscode-esrp - AuthSignCertName: esrp-sign - FolderPath: . - Pattern: noop - displayName: 'Install ESRP Tooling' - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName - $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName - echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version\net6.0\esrpcli.dll" - displayName: Find ESRP CLI - - - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - - task: DownloadPipelineArtifact@2 - displayName: Download artifact - inputs: - artifact: ${{ target }} - path: $(Build.ArtifactStagingDirectory)/pkg/${{ target }} - - - task: ExtractFiles@1 - displayName: Extract artifact - inputs: - archiveFilePatterns: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/*.zip - destinationFolder: $(Build.ArtifactStagingDirectory)/sign/${{ target }} - - - powershell: node build\azure-pipelines\common\sign $env:EsrpCliDllPath sign-windows $(Build.ArtifactStagingDirectory)/sign "*.exe" - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Codesign - - - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: - - powershell: | - $ASSET_ID = "${{ target }}".replace("unsigned_", ""); - echo "##vso[task.setvariable variable=ASSET_ID]$ASSET_ID" - displayName: Set asset id variable - - - task: ArchiveFiles@2 - displayName: Archive signed files - inputs: - rootFolderOrFile: $(Build.ArtifactStagingDirectory)/sign/${{ target }} - includeRootFolder: false - archiveType: zip - archiveFile: $(Build.ArtifactStagingDirectory)/$(ASSET_ID).zip - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/$(ASSET_ID).zip - artifactName: $(ASSET_ID) - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/${{ target }} - sbomPackageName: "VS Code Windows ${{ target }} CLI" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish signed artifact with ID $(ASSET_ID) diff --git a/build/azure-pipelines/cli/install-rust-posix.yml b/build/azure-pipelines/cli/install-rust-posix.yml index 0607cde33e56d..d9bae080cc269 100644 --- a/build/azure-pipelines/cli/install-rust-posix.yml +++ b/build/azure-pipelines/cli/install-rust-posix.yml @@ -1,7 +1,7 @@ parameters: - name: channel type: string - default: 1.85 + default: 1.88 - name: targets default: [] type: object diff --git a/build/azure-pipelines/cli/install-rust-win32.yml b/build/azure-pipelines/cli/install-rust-win32.yml deleted file mode 100644 index bff114fccd07f..0000000000000 --- a/build/azure-pipelines/cli/install-rust-win32.yml +++ /dev/null @@ -1,51 +0,0 @@ -parameters: - - name: channel - type: string - default: 1.85 - - name: targets - default: [] - type: object - -# Todo: use 1ES pipeline once extension is installed in ADO - -steps: - - task: RustInstaller@1 - inputs: - rustVersion: ms-${{ parameters.channel }} - cratesIoFeedOverride: $(CARGO_REGISTRY) - additionalTargets: ${{ join(' ', parameters.targets) }} - toolchainFeed: https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/nuget/v3/index.json - default: true - addToPath: true - displayName: Install MSFT Rust - condition: and(succeeded(), ne(variables['CARGO_REGISTRY'], 'none')) - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - Invoke-WebRequest -Uri "https://win.rustup.rs" -Outfile $(Build.ArtifactStagingDirectory)/rustup-init.exe - exec { $(Build.ArtifactStagingDirectory)/rustup-init.exe -y --profile minimal --default-toolchain $env:RUSTUP_TOOLCHAIN --default-host x86_64-pc-windows-msvc } - echo "##vso[task.prependpath]$env:USERPROFILE\.cargo\bin" - env: - RUSTUP_TOOLCHAIN: ${{ parameters.channel }} - displayName: Install OSS Rust - condition: and(succeeded(), eq(variables['CARGO_REGISTRY'], 'none')) - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - exec { rustup default $RUSTUP_TOOLCHAIN } - exec { rustup update $RUSTUP_TOOLCHAIN } - env: - RUSTUP_TOOLCHAIN: ${{ parameters.channel }} - displayName: "Set Rust version" - condition: and(succeeded(), eq(variables['CARGO_REGISTRY'], 'none')) - - - ${{ each target in parameters.targets }}: - - script: rustup target add ${{ target }} - displayName: "Adding Rust target '${{ target }}'" - condition: and(succeeded(), eq(variables['CARGO_REGISTRY'], 'none')) - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - exec { rustc --version } - exec { cargo --version } - displayName: "Check Rust versions" diff --git a/build/azure-pipelines/cli/test.yml b/build/azure-pipelines/cli/test.yml deleted file mode 100644 index 6e2a1c68a16a6..0000000000000 --- a/build/azure-pipelines/cli/test.yml +++ /dev/null @@ -1,10 +0,0 @@ -steps: - - template: ./install-rust-posix.yml@self - - - script: cargo clippy -- -D warnings - workingDirectory: cli - displayName: Clippy lint - - - script: cargo test - workingDirectory: cli - displayName: 🧪 Run unit tests diff --git a/build/azure-pipelines/common/checkForArtifact.js b/build/azure-pipelines/common/checkForArtifact.js deleted file mode 100644 index 899448f78bdc9..0000000000000 --- a/build/azure-pipelines/common/checkForArtifact.js +++ /dev/null @@ -1,34 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const publish_1 = require("./publish"); -const retry_1 = require("./retry"); -async function getPipelineArtifacts() { - const result = await (0, publish_1.requestAZDOAPI)('artifacts'); - return result.value.filter(a => !/sbom$/.test(a.name)); -} -async function main([variableName, artifactName]) { - if (!variableName || !artifactName) { - throw new Error(`Usage: node checkForArtifact.js `); - } - try { - const artifacts = await (0, retry_1.retry)(() => getPipelineArtifacts()); - const artifact = artifacts.find(a => a.name === artifactName); - console.log(`##vso[task.setvariable variable=${variableName}]${artifact ? 'true' : 'false'}`); - } - catch (err) { - console.error(`ERROR: Failed to get pipeline artifacts: ${err}`); - console.log(`##vso[task.setvariable variable=${variableName}]false`); - } -} -main(process.argv.slice(2)) - .then(() => { - process.exit(0); -}, err => { - console.error(err); - process.exit(1); -}); -//# sourceMappingURL=checkForArtifact.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/checkForArtifact.ts b/build/azure-pipelines/common/checkForArtifact.ts index e0a1a2ce1d378..21a30552e5873 100644 --- a/build/azure-pipelines/common/checkForArtifact.ts +++ b/build/azure-pipelines/common/checkForArtifact.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Artifact, requestAZDOAPI } from './publish'; -import { retry } from './retry'; +import { type Artifact, requestAZDOAPI } from './publish.ts'; +import { retry } from './retry.ts'; async function getPipelineArtifacts(): Promise { const result = await requestAZDOAPI<{ readonly value: Artifact[] }>('artifacts'); @@ -13,7 +13,7 @@ async function getPipelineArtifacts(): Promise { async function main([variableName, artifactName]: string[]): Promise { if (!variableName || !artifactName) { - throw new Error(`Usage: node checkForArtifact.js `); + throw new Error(`Usage: node checkForArtifact.ts `); } try { diff --git a/build/azure-pipelines/common/checkout.yml b/build/azure-pipelines/common/checkout.yml new file mode 100644 index 0000000000000..6f57a9ad9b41e --- /dev/null +++ b/build/azure-pipelines/common/checkout.yml @@ -0,0 +1,5 @@ +steps: + - checkout: self + fetchDepth: 1 + fetchTags: false + displayName: Checkout microsoft/vscode diff --git a/build/azure-pipelines/common/codesign.js b/build/azure-pipelines/common/codesign.js deleted file mode 100644 index e3a8f330dcd86..0000000000000 --- a/build/azure-pipelines/common/codesign.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.printBanner = printBanner; -exports.streamProcessOutputAndCheckResult = streamProcessOutputAndCheckResult; -exports.spawnCodesignProcess = spawnCodesignProcess; -const zx_1 = require("zx"); -function printBanner(title) { - title = `${title} (${new Date().toISOString()})`; - console.log('\n'); - console.log('#'.repeat(75)); - console.log(`# ${title.padEnd(71)} #`); - console.log('#'.repeat(75)); - console.log('\n'); -} -async function streamProcessOutputAndCheckResult(name, promise) { - const result = await promise.pipe(process.stdout); - if (result.ok) { - console.log(`\n${name} completed successfully. Duration: ${result.duration} ms`); - return; - } - throw new Error(`${name} failed: ${result.stderr}`); -} -function spawnCodesignProcess(esrpCliDLLPath, type, folder, glob) { - return (0, zx_1.$) `node build/azure-pipelines/common/sign ${esrpCliDLLPath} ${type} ${folder} ${glob}`; -} -//# sourceMappingURL=codesign.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/codesign.ts b/build/azure-pipelines/common/codesign.ts index 9f26b3924b538..4c27048093b20 100644 --- a/build/azure-pipelines/common/codesign.ts +++ b/build/azure-pipelines/common/codesign.ts @@ -26,5 +26,5 @@ export async function streamProcessOutputAndCheckResult(name: string, promise: P } export function spawnCodesignProcess(esrpCliDLLPath: string, type: 'sign-windows' | 'sign-windows-appx' | 'sign-pgp' | 'sign-darwin' | 'notarize-darwin', folder: string, glob: string): ProcessPromise { - return $`node build/azure-pipelines/common/sign ${esrpCliDLLPath} ${type} ${folder} ${glob}`; + return $`node build/azure-pipelines/common/sign.ts ${esrpCliDLLPath} ${type} ${folder} ${glob}`; } diff --git a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js deleted file mode 100644 index 10fa9087454f6..0000000000000 --- a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.js +++ /dev/null @@ -1,19 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const crypto_1 = __importDefault(require("crypto")); -const productjson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '../../../product.json'), 'utf8')); -const shasum = crypto_1.default.createHash('sha256'); -for (const ext of productjson.builtInExtensions) { - shasum.update(`${ext.name}@${ext.version}`); -} -process.stdout.write(shasum.digest('hex')); -//# sourceMappingURL=computeBuiltInDepsCacheKey.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts index 8abaaccb6543c..8e172ee5ecb8a 100644 --- a/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts +++ b/build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts @@ -7,7 +7,7 @@ import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; -const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../../product.json'), 'utf8')); +const productjson = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, '../../../product.json'), 'utf8')); const shasum = crypto.createHash('sha256'); for (const ext of productjson.builtInExtensions) { diff --git a/build/azure-pipelines/common/computeNodeModulesCacheKey.js b/build/azure-pipelines/common/computeNodeModulesCacheKey.js deleted file mode 100644 index c09c13be9d429..0000000000000 --- a/build/azure-pipelines/common/computeNodeModulesCacheKey.js +++ /dev/null @@ -1,40 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const crypto_1 = __importDefault(require("crypto")); -const { dirs } = require('../../npm/dirs'); -const ROOT = path_1.default.join(__dirname, '../../../'); -const shasum = crypto_1.default.createHash('sha256'); -shasum.update(fs_1.default.readFileSync(path_1.default.join(ROOT, 'build/.cachesalt'))); -shasum.update(fs_1.default.readFileSync(path_1.default.join(ROOT, '.npmrc'))); -shasum.update(fs_1.default.readFileSync(path_1.default.join(ROOT, 'build', '.npmrc'))); -shasum.update(fs_1.default.readFileSync(path_1.default.join(ROOT, 'remote', '.npmrc'))); -// Add `package.json` and `package-lock.json` files -for (const dir of dirs) { - const packageJsonPath = path_1.default.join(ROOT, dir, 'package.json'); - const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath).toString()); - const relevantPackageJsonSections = { - dependencies: packageJson.dependencies, - devDependencies: packageJson.devDependencies, - optionalDependencies: packageJson.optionalDependencies, - resolutions: packageJson.resolutions, - distro: packageJson.distro - }; - shasum.update(JSON.stringify(relevantPackageJsonSections)); - const packageLockPath = path_1.default.join(ROOT, dir, 'package-lock.json'); - shasum.update(fs_1.default.readFileSync(packageLockPath)); -} -// Add any other command line arguments -for (let i = 2; i < process.argv.length; i++) { - shasum.update(process.argv[i]); -} -process.stdout.write(shasum.digest('hex')); -//# sourceMappingURL=computeNodeModulesCacheKey.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/computeNodeModulesCacheKey.ts b/build/azure-pipelines/common/computeNodeModulesCacheKey.ts index 57b35dc78de5c..e5dbc06aa946e 100644 --- a/build/azure-pipelines/common/computeNodeModulesCacheKey.ts +++ b/build/azure-pipelines/common/computeNodeModulesCacheKey.ts @@ -2,13 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; -const { dirs } = require('../../npm/dirs'); +import { dirs } from '../../npm/dirs.ts'; -const ROOT = path.join(__dirname, '../../../'); +const ROOT = path.join(import.meta.dirname, '../../../'); const shasum = crypto.createHash('sha256'); diff --git a/build/azure-pipelines/common/createBuild.js b/build/azure-pipelines/common/createBuild.js deleted file mode 100644 index c605ed6218efb..0000000000000 --- a/build/azure-pipelines/common/createBuild.js +++ /dev/null @@ -1,55 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const identity_1 = require("@azure/identity"); -const cosmos_1 = require("@azure/cosmos"); -const retry_1 = require("./retry"); -if (process.argv.length !== 3) { - console.error('Usage: node createBuild.js VERSION'); - process.exit(-1); -} -function getEnv(name) { - const result = process.env[name]; - if (typeof result === 'undefined') { - throw new Error('Missing env: ' + name); - } - return result; -} -async function main() { - const [, , _version] = process.argv; - const quality = getEnv('VSCODE_QUALITY'); - const commit = getEnv('BUILD_SOURCEVERSION'); - const queuedBy = getEnv('BUILD_QUEUEDBY'); - const sourceBranch = getEnv('BUILD_SOURCEBRANCH'); - const version = _version + (quality === 'stable' ? '' : `-${quality}`); - console.log('Creating build...'); - console.log('Quality:', quality); - console.log('Version:', version); - console.log('Commit:', commit); - const build = { - id: commit, - timestamp: (new Date()).getTime(), - version, - isReleased: false, - private: process.env['VSCODE_PRIVATE_BUILD']?.toLowerCase() === 'true', - sourceBranch, - queuedBy, - assets: [], - updates: {} - }; - const aadCredentials = new identity_1.ClientAssertionCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], () => Promise.resolve(process.env['AZURE_ID_TOKEN'])); - const client = new cosmos_1.CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT'], aadCredentials }); - const scripts = client.database('builds').container(quality).scripts; - await (0, retry_1.retry)(() => scripts.storedProcedure('createBuild').execute('', [{ ...build, _partitionKey: '' }])); -} -main().then(() => { - console.log('Build successfully created'); - process.exit(0); -}, err => { - console.error(err); - process.exit(1); -}); -//# sourceMappingURL=createBuild.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/createBuild.ts b/build/azure-pipelines/common/createBuild.ts index 6afeb01e6cc78..2524e7405a8ba 100644 --- a/build/azure-pipelines/common/createBuild.ts +++ b/build/azure-pipelines/common/createBuild.ts @@ -5,10 +5,10 @@ import { ClientAssertionCredential } from '@azure/identity'; import { CosmosClient } from '@azure/cosmos'; -import { retry } from './retry'; +import { retry } from './retry.ts'; if (process.argv.length !== 3) { - console.error('Usage: node createBuild.js VERSION'); + console.error('Usage: node createBuild.ts VERSION'); process.exit(-1); } @@ -35,16 +35,21 @@ async function main(): Promise { console.log('Version:', version); console.log('Commit:', commit); + const timestamp = Date.now(); const build = { id: commit, - timestamp: (new Date()).getTime(), + timestamp, version, isReleased: false, private: process.env['VSCODE_PRIVATE_BUILD']?.toLowerCase() === 'true', sourceBranch, queuedBy, assets: [], - updates: {} + updates: {}, + firstReleaseTimestamp: null, + history: [ + { event: 'created', timestamp } + ] }; const aadCredentials = new ClientAssertionCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, () => Promise.resolve(process.env['AZURE_ID_TOKEN']!)); diff --git a/build/azure-pipelines/common/getPublishAuthTokens.js b/build/azure-pipelines/common/getPublishAuthTokens.js deleted file mode 100644 index 9c22e9ad94bc9..0000000000000 --- a/build/azure-pipelines/common/getPublishAuthTokens.js +++ /dev/null @@ -1,47 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getAccessToken = getAccessToken; -const msal_node_1 = require("@azure/msal-node"); -function e(name) { - const result = process.env[name]; - if (typeof result !== 'string') { - throw new Error(`Missing env: ${name}`); - } - return result; -} -async function getAccessToken(endpoint, tenantId, clientId, idToken) { - const app = new msal_node_1.ConfidentialClientApplication({ - auth: { - clientId, - authority: `https://login.microsoftonline.com/${tenantId}`, - clientAssertion: idToken - } - }); - const result = await app.acquireTokenByClientCredential({ scopes: [`${endpoint}.default`] }); - if (!result) { - throw new Error('Failed to get access token'); - } - return { - token: result.accessToken, - expiresOnTimestamp: result.expiresOn.getTime(), - refreshAfterTimestamp: result.refreshOn?.getTime() - }; -} -async function main() { - const cosmosDBAccessToken = await getAccessToken(e('AZURE_DOCUMENTDB_ENDPOINT'), e('AZURE_TENANT_ID'), e('AZURE_CLIENT_ID'), e('AZURE_ID_TOKEN')); - const blobServiceAccessToken = await getAccessToken(`https://${e('VSCODE_STAGING_BLOB_STORAGE_ACCOUNT_NAME')}.blob.core.windows.net/`, process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], process.env['AZURE_ID_TOKEN']); - console.log(JSON.stringify({ cosmosDBAccessToken, blobServiceAccessToken })); -} -if (require.main === module) { - main().then(() => { - process.exit(0); - }, err => { - console.error(err); - process.exit(1); - }); -} -//# sourceMappingURL=getPublishAuthTokens.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/getPublishAuthTokens.ts b/build/azure-pipelines/common/getPublishAuthTokens.ts index 68e76de1a832f..2293480b30657 100644 --- a/build/azure-pipelines/common/getPublishAuthTokens.ts +++ b/build/azure-pipelines/common/getPublishAuthTokens.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AccessToken } from '@azure/core-auth'; +import type { AccessToken } from '@azure/core-auth'; import { ConfidentialClientApplication } from '@azure/msal-node'; function e(name: string): string { @@ -44,7 +44,7 @@ async function main() { console.log(JSON.stringify({ cosmosDBAccessToken, blobServiceAccessToken })); } -if (require.main === module) { +if (import.meta.main) { main().then(() => { process.exit(0); }, err => { diff --git a/build/azure-pipelines/common/install-builtin-extensions.yml b/build/azure-pipelines/common/install-builtin-extensions.yml index c1ee18d05b534..f9cbfd4b0854a 100644 --- a/build/azure-pipelines/common/install-builtin-extensions.yml +++ b/build/azure-pipelines/common/install-builtin-extensions.yml @@ -7,7 +7,7 @@ steps: condition: and(succeeded(), not(contains(variables['Agent.OS'], 'windows'))) displayName: Create .build folder - - script: node build/azure-pipelines/common/computeBuiltInDepsCacheKey.js > .build/builtindepshash + - script: node build/azure-pipelines/common/computeBuiltInDepsCacheKey.ts > .build/builtindepshash displayName: Prepare built-in extensions cache key - task: Cache@2 @@ -17,7 +17,7 @@ steps: cacheHitVar: BUILTIN_EXTENSIONS_RESTORED displayName: Restore built-in extensions cache - - script: node build/lib/builtInExtensions.js + - script: node build/lib/builtInExtensions.ts env: GITHUB_TOKEN: "$(github-distro-mixin-password)" condition: and(succeeded(), ne(variables.BUILTIN_EXTENSIONS_RESTORED, 'true')) diff --git a/build/azure-pipelines/common/listNodeModules.js b/build/azure-pipelines/common/listNodeModules.js deleted file mode 100644 index 301b5f930b614..0000000000000 --- a/build/azure-pipelines/common/listNodeModules.js +++ /dev/null @@ -1,44 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -if (process.argv.length !== 3) { - console.error('Usage: node listNodeModules.js OUTPUT_FILE'); - process.exit(-1); -} -const ROOT = path_1.default.join(__dirname, '../../../'); -function findNodeModulesFiles(location, inNodeModules, result) { - const entries = fs_1.default.readdirSync(path_1.default.join(ROOT, location)); - for (const entry of entries) { - const entryPath = `${location}/${entry}`; - if (/(^\/out)|(^\/src$)|(^\/.git$)|(^\/.build$)/.test(entryPath)) { - continue; - } - let stat; - try { - stat = fs_1.default.statSync(path_1.default.join(ROOT, entryPath)); - } - catch (err) { - continue; - } - if (stat.isDirectory()) { - findNodeModulesFiles(entryPath, inNodeModules || (entry === 'node_modules'), result); - } - else { - if (inNodeModules) { - result.push(entryPath.substr(1)); - } - } - } -} -const result = []; -findNodeModulesFiles('', false, result); -fs_1.default.writeFileSync(process.argv[2], result.join('\n') + '\n'); -//# sourceMappingURL=listNodeModules.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/listNodeModules.ts b/build/azure-pipelines/common/listNodeModules.ts index fb85b25cfd1b1..5ab955faca46d 100644 --- a/build/azure-pipelines/common/listNodeModules.ts +++ b/build/azure-pipelines/common/listNodeModules.ts @@ -7,11 +7,11 @@ import fs from 'fs'; import path from 'path'; if (process.argv.length !== 3) { - console.error('Usage: node listNodeModules.js OUTPUT_FILE'); + console.error('Usage: node listNodeModules.ts OUTPUT_FILE'); process.exit(-1); } -const ROOT = path.join(__dirname, '../../../'); +const ROOT = path.join(import.meta.dirname, '../../../'); function findNodeModulesFiles(location: string, inNodeModules: boolean, result: string[]) { const entries = fs.readdirSync(path.join(ROOT, location)); diff --git a/build/azure-pipelines/common/publish-artifact.yml b/build/azure-pipelines/common/publish-artifact.yml new file mode 100644 index 0000000000000..1cb18fb530150 --- /dev/null +++ b/build/azure-pipelines/common/publish-artifact.yml @@ -0,0 +1,90 @@ +parameters: + - name: artifactName + type: string + - name: targetPath + type: string + - name: displayName + type: string + default: "Publish artifact" + - name: sbomEnabled + type: boolean + default: true + - name: sbomBuildDropPath + type: string + default: "" + - name: sbomPackageName + type: string + default: "" + - name: sbomPackageVersion + type: string + default: "" + - name: condition + type: string + default: succeeded() + - name: continueOnError + type: boolean + default: false + +steps: + - powershell: | + $ArtifactName = "${{ parameters.artifactName }}" + + if ("$(Agent.JobStatus)" -notin @('Succeeded', 'SucceededWithIssues')) { + $ArtifactName = "attempt$(System.JobAttempt)_$ArtifactName" + } + + echo "##vso[task.setvariable variable=ARTIFACT_NAME]$ArtifactName" + + $NormalizedArtifactName = $ArtifactName.Replace('-', '_') + echo "##vso[task.setvariable variable=NORMALIZED_ARTIFACT_NAME]$NormalizedArtifactName" + condition: ${{ parameters.condition }} + displayName: Generate artifact name + + - powershell: | + $ErrorActionPreference = "Stop" + + $Uri = "$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/artifacts?api-version=6.0" + + try { + $Headers = @{ + Authorization = "Bearer $(System.AccessToken)" + 'User-Agent' = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0' + 'Accept' = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7' + 'Accept-Encoding' = 'gzip, deflate, br' + 'Accept-Language' = 'en-US,en;q=0.9' + 'Referer' = 'https://dev.azure.com' + } + $Response = Invoke-RestMethod -Uri $Uri -Headers $Headers -Method Get + Write-Host "API Response: $($Response | ConvertTo-Json -Depth 3)" + $ArtifactExists = $Response.value | Where-Object { $_.name -eq "$(ARTIFACT_NAME)" } + + if ($ArtifactExists) { + Write-Host "Artifact '$(ARTIFACT_NAME)' already exists, skipping publish" + echo "##vso[task.setvariable variable=SHOULD_PUBLISH_ARTIFACT_$(NORMALIZED_ARTIFACT_NAME)]false" + } else { + Write-Host "Artifact '$(ARTIFACT_NAME)' does not exist, will publish" + echo "##vso[task.setvariable variable=SHOULD_PUBLISH_ARTIFACT_$(NORMALIZED_ARTIFACT_NAME)]true" + } + } catch { + Write-Host "Failed to check artifacts, will attempt to publish: $_" + echo "##vso[task.setvariable variable=SHOULD_PUBLISH_ARTIFACT_$(NORMALIZED_ARTIFACT_NAME)]true" + } + condition: ${{ parameters.condition }} + displayName: Check if artifact exists + + - task: 1ES.PublishPipelineArtifact@1 + inputs: + targetPath: ${{ parameters.targetPath }} + artifactName: $(ARTIFACT_NAME) + sbomEnabled: ${{ parameters.sbomEnabled }} + isProduction: ${{ parameters.sbomEnabled }} + ${{ if ne(parameters.sbomBuildDropPath, '') }}: + sbomBuildDropPath: ${{ parameters.sbomBuildDropPath }} + ${{ if ne(parameters.sbomPackageName, '') }}: + sbomPackageName: ${{ parameters.sbomPackageName }} + ${{ if ne(parameters.sbomPackageVersion, '') }}: + sbomPackageVersion: ${{ parameters.sbomPackageVersion }} + condition: and(${{ parameters.condition }}, eq(variables[format('SHOULD_PUBLISH_ARTIFACT_{0}', variables.NORMALIZED_ARTIFACT_NAME)], 'true')) + ${{ if ne(parameters.continueOnError, false) }}: + continueOnError: ${{ parameters.continueOnError }} + displayName: ${{ parameters.displayName }} diff --git a/build/azure-pipelines/common/publish.js b/build/azure-pipelines/common/publish.js deleted file mode 100644 index d65a4348f9bed..0000000000000 --- a/build/azure-pipelines/common/publish.js +++ /dev/null @@ -1,722 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.e = e; -exports.requestAZDOAPI = requestAZDOAPI; -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const stream_1 = require("stream"); -const promises_1 = require("node:stream/promises"); -const yauzl_1 = __importDefault(require("yauzl")); -const crypto_1 = __importDefault(require("crypto")); -const retry_1 = require("./retry"); -const cosmos_1 = require("@azure/cosmos"); -const child_process_1 = __importDefault(require("child_process")); -const os_1 = __importDefault(require("os")); -const node_worker_threads_1 = require("node:worker_threads"); -const msal_node_1 = require("@azure/msal-node"); -const storage_blob_1 = require("@azure/storage-blob"); -const jws_1 = __importDefault(require("jws")); -const node_timers_1 = require("node:timers"); -function e(name) { - const result = process.env[name]; - if (typeof result !== 'string') { - throw new Error(`Missing env: ${name}`); - } - return result; -} -function hashStream(hashName, stream) { - return new Promise((c, e) => { - const shasum = crypto_1.default.createHash(hashName); - stream - .on('data', shasum.update.bind(shasum)) - .on('error', e) - .on('close', () => c(shasum.digest())); - }); -} -var StatusCode; -(function (StatusCode) { - StatusCode["Pass"] = "pass"; - StatusCode["Aborted"] = "aborted"; - StatusCode["Inprogress"] = "inprogress"; - StatusCode["FailCanRetry"] = "failCanRetry"; - StatusCode["FailDoNotRetry"] = "failDoNotRetry"; - StatusCode["PendingAnalysis"] = "pendingAnalysis"; - StatusCode["Cancelled"] = "cancelled"; -})(StatusCode || (StatusCode = {})); -function getCertificateBuffer(input) { - return Buffer.from(input.replace(/-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----|\n/g, ''), 'base64'); -} -function getThumbprint(input, algorithm) { - const buffer = getCertificateBuffer(input); - return crypto_1.default.createHash(algorithm).update(buffer).digest(); -} -function getKeyFromPFX(pfx) { - const pfxCertificatePath = path_1.default.join(os_1.default.tmpdir(), 'cert.pfx'); - const pemKeyPath = path_1.default.join(os_1.default.tmpdir(), 'key.pem'); - try { - const pfxCertificate = Buffer.from(pfx, 'base64'); - fs_1.default.writeFileSync(pfxCertificatePath, pfxCertificate); - child_process_1.default.execSync(`openssl pkcs12 -in "${pfxCertificatePath}" -nocerts -nodes -out "${pemKeyPath}" -passin pass:`); - const raw = fs_1.default.readFileSync(pemKeyPath, 'utf-8'); - const result = raw.match(/-----BEGIN PRIVATE KEY-----[\s\S]+?-----END PRIVATE KEY-----/g)[0]; - return result; - } - finally { - fs_1.default.rmSync(pfxCertificatePath, { force: true }); - fs_1.default.rmSync(pemKeyPath, { force: true }); - } -} -function getCertificatesFromPFX(pfx) { - const pfxCertificatePath = path_1.default.join(os_1.default.tmpdir(), 'cert.pfx'); - const pemCertificatePath = path_1.default.join(os_1.default.tmpdir(), 'cert.pem'); - try { - const pfxCertificate = Buffer.from(pfx, 'base64'); - fs_1.default.writeFileSync(pfxCertificatePath, pfxCertificate); - child_process_1.default.execSync(`openssl pkcs12 -in "${pfxCertificatePath}" -nokeys -out "${pemCertificatePath}" -passin pass:`); - const raw = fs_1.default.readFileSync(pemCertificatePath, 'utf-8'); - const matches = raw.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g); - return matches ? matches.reverse() : []; - } - finally { - fs_1.default.rmSync(pfxCertificatePath, { force: true }); - fs_1.default.rmSync(pemCertificatePath, { force: true }); - } -} -class ESRPReleaseService { - log; - clientId; - accessToken; - requestSigningCertificates; - requestSigningKey; - containerClient; - stagingSasToken; - static async create(log, tenantId, clientId, authCertificatePfx, requestSigningCertificatePfx, containerClient, stagingSasToken) { - const authKey = getKeyFromPFX(authCertificatePfx); - const authCertificate = getCertificatesFromPFX(authCertificatePfx)[0]; - const requestSigningKey = getKeyFromPFX(requestSigningCertificatePfx); - const requestSigningCertificates = getCertificatesFromPFX(requestSigningCertificatePfx); - const app = new msal_node_1.ConfidentialClientApplication({ - auth: { - clientId, - authority: `https://login.microsoftonline.com/${tenantId}`, - clientCertificate: { - thumbprintSha256: getThumbprint(authCertificate, 'sha256').toString('hex'), - privateKey: authKey, - x5c: authCertificate - } - } - }); - const response = await app.acquireTokenByClientCredential({ - scopes: ['https://api.esrp.microsoft.com/.default'] - }); - return new ESRPReleaseService(log, clientId, response.accessToken, requestSigningCertificates, requestSigningKey, containerClient, stagingSasToken); - } - static API_URL = 'https://api.esrp.microsoft.com/api/v3/releaseservices/clients/'; - constructor(log, clientId, accessToken, requestSigningCertificates, requestSigningKey, containerClient, stagingSasToken) { - this.log = log; - this.clientId = clientId; - this.accessToken = accessToken; - this.requestSigningCertificates = requestSigningCertificates; - this.requestSigningKey = requestSigningKey; - this.containerClient = containerClient; - this.stagingSasToken = stagingSasToken; - } - async createRelease(version, filePath, friendlyFileName) { - const correlationId = crypto_1.default.randomUUID(); - const blobClient = this.containerClient.getBlockBlobClient(correlationId); - this.log(`Uploading ${filePath} to ${blobClient.url}`); - await blobClient.uploadFile(filePath); - this.log('Uploaded blob successfully'); - try { - this.log(`Submitting release for ${version}: ${filePath}`); - const submitReleaseResult = await this.submitRelease(version, filePath, friendlyFileName, correlationId, blobClient); - this.log(`Successfully submitted release ${submitReleaseResult.operationId}. Polling for completion...`); - // Poll every 5 seconds, wait 60 minutes max -> poll 60/5*60=720 times - for (let i = 0; i < 720; i++) { - await new Promise(c => setTimeout(c, 5000)); - const releaseStatus = await this.getReleaseStatus(submitReleaseResult.operationId); - if (releaseStatus.status === 'pass') { - break; - } - else if (releaseStatus.status === 'aborted') { - this.log(JSON.stringify(releaseStatus)); - throw new Error(`Release was aborted`); - } - else if (releaseStatus.status !== 'inprogress') { - this.log(JSON.stringify(releaseStatus)); - throw new Error(`Unknown error when polling for release`); - } - } - const releaseDetails = await this.getReleaseDetails(submitReleaseResult.operationId); - if (releaseDetails.status !== 'pass') { - throw new Error(`Timed out waiting for release: ${JSON.stringify(releaseDetails)}`); - } - this.log('Successfully created release:', releaseDetails.files[0].fileDownloadDetails[0].downloadUrl); - return releaseDetails.files[0].fileDownloadDetails[0].downloadUrl; - } - finally { - this.log(`Deleting blob ${blobClient.url}`); - await blobClient.delete(); - this.log('Deleted blob successfully'); - } - } - async submitRelease(version, filePath, friendlyFileName, correlationId, blobClient) { - const size = fs_1.default.statSync(filePath).size; - const hash = await hashStream('sha256', fs_1.default.createReadStream(filePath)); - const blobUrl = `${blobClient.url}?${this.stagingSasToken}`; - const message = { - customerCorrelationId: correlationId, - esrpCorrelationId: correlationId, - driEmail: ['joao.moreno@microsoft.com'], - createdBy: { userPrincipalName: 'jomo@microsoft.com' }, - owners: [{ owner: { userPrincipalName: 'jomo@microsoft.com' } }], - approvers: [{ approver: { userPrincipalName: 'jomo@microsoft.com' }, isAutoApproved: true, isMandatory: false }], - releaseInfo: { - title: 'VS Code', - properties: { - 'ReleaseContentType': 'InstallPackage' - }, - minimumNumberOfApprovers: 1 - }, - productInfo: { - name: 'VS Code', - version, - description: 'VS Code' - }, - accessPermissionsInfo: { - mainPublisher: 'VSCode', - channelDownloadEntityDetails: { - AllDownloadEntities: ['VSCode'] - } - }, - routingInfo: { - intent: 'filedownloadlinkgeneration' - }, - files: [{ - name: path_1.default.basename(filePath), - friendlyFileName, - tenantFileLocation: blobUrl, - tenantFileLocationType: 'AzureBlob', - sourceLocation: { - type: 'azureBlob', - blobUrl - }, - hashType: 'sha256', - hash: Array.from(hash), - sizeInBytes: size - }] - }; - message.jwsToken = await this.generateJwsToken(message); - const res = await fetch(`${ESRPReleaseService.API_URL}${this.clientId}/workflows/release/operations`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${this.accessToken}` - }, - body: JSON.stringify(message) - }); - if (!res.ok) { - const text = await res.text(); - throw new Error(`Failed to submit release: ${res.statusText}\n${text}`); - } - return await res.json(); - } - async getReleaseStatus(releaseId) { - const url = `${ESRPReleaseService.API_URL}${this.clientId}/workflows/release/operations/grs/${releaseId}`; - const res = await (0, retry_1.retry)(() => fetch(url, { - headers: { - 'Authorization': `Bearer ${this.accessToken}` - } - })); - if (!res.ok) { - const text = await res.text(); - throw new Error(`Failed to get release status: ${res.statusText}\n${text}`); - } - return await res.json(); - } - async getReleaseDetails(releaseId) { - const url = `${ESRPReleaseService.API_URL}${this.clientId}/workflows/release/operations/grd/${releaseId}`; - const res = await (0, retry_1.retry)(() => fetch(url, { - headers: { - 'Authorization': `Bearer ${this.accessToken}` - } - })); - if (!res.ok) { - const text = await res.text(); - throw new Error(`Failed to get release status: ${res.statusText}\n${text}`); - } - return await res.json(); - } - async generateJwsToken(message) { - return jws_1.default.sign({ - header: { - alg: 'RS256', - crit: ['exp', 'x5t'], - // Release service uses ticks, not seconds :roll_eyes: (https://stackoverflow.com/a/7968483) - exp: ((Date.now() + (6 * 60 * 1000)) * 10000) + 621355968000000000, - // Release service uses hex format, not base64url :roll_eyes: - x5t: getThumbprint(this.requestSigningCertificates[0], 'sha1').toString('hex'), - // Release service uses a '.' separated string, not an array of strings :roll_eyes: - x5c: this.requestSigningCertificates.map(c => getCertificateBuffer(c).toString('base64url')).join('.'), - }, - payload: message, - privateKey: this.requestSigningKey, - }); - } -} -class State { - statePath; - set = new Set(); - constructor() { - const pipelineWorkspacePath = e('PIPELINE_WORKSPACE'); - const previousState = fs_1.default.readdirSync(pipelineWorkspacePath) - .map(name => /^artifacts_processed_(\d+)$/.exec(name)) - .filter((match) => !!match) - .map(match => ({ name: match[0], attempt: Number(match[1]) })) - .sort((a, b) => b.attempt - a.attempt)[0]; - if (previousState) { - const previousStatePath = path_1.default.join(pipelineWorkspacePath, previousState.name, previousState.name + '.txt'); - fs_1.default.readFileSync(previousStatePath, 'utf8').split(/\n/).filter(name => !!name).forEach(name => this.set.add(name)); - } - const stageAttempt = e('SYSTEM_STAGEATTEMPT'); - this.statePath = path_1.default.join(pipelineWorkspacePath, `artifacts_processed_${stageAttempt}`, `artifacts_processed_${stageAttempt}.txt`); - fs_1.default.mkdirSync(path_1.default.dirname(this.statePath), { recursive: true }); - fs_1.default.writeFileSync(this.statePath, [...this.set.values()].map(name => `${name}\n`).join('')); - } - get size() { - return this.set.size; - } - has(name) { - return this.set.has(name); - } - add(name) { - this.set.add(name); - fs_1.default.appendFileSync(this.statePath, `${name}\n`); - } - [Symbol.iterator]() { - return this.set[Symbol.iterator](); - } -} -const azdoFetchOptions = { - headers: { - // Pretend we're a web browser to avoid download rate limits - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'Accept-Encoding': 'gzip, deflate, br', - 'Accept-Language': 'en-US,en;q=0.9', - 'Referer': 'https://dev.azure.com', - Authorization: `Bearer ${e('SYSTEM_ACCESSTOKEN')}` - } -}; -async function requestAZDOAPI(path) { - const abortController = new AbortController(); - const timeout = setTimeout(() => abortController.abort(), 2 * 60 * 1000); - try { - const res = await (0, retry_1.retry)(() => fetch(`${e('BUILDS_API_URL')}${path}?api-version=6.0`, { ...azdoFetchOptions, signal: abortController.signal })); - if (!res.ok) { - throw new Error(`Unexpected status code: ${res.status}`); - } - return await res.json(); - } - finally { - clearTimeout(timeout); - } -} -async function getPipelineArtifacts() { - const result = await requestAZDOAPI('artifacts'); - return result.value.filter(a => /^vscode_/.test(a.name) && !/sbom$/.test(a.name)); -} -async function getPipelineTimeline() { - return await requestAZDOAPI('timeline'); -} -async function downloadArtifact(artifact, downloadPath) { - const abortController = new AbortController(); - const timeout = setTimeout(() => abortController.abort(), 4 * 60 * 1000); - try { - const res = await fetch(artifact.resource.downloadUrl, { ...azdoFetchOptions, signal: abortController.signal }); - if (!res.ok) { - throw new Error(`Unexpected status code: ${res.status}`); - } - await (0, promises_1.pipeline)(stream_1.Readable.fromWeb(res.body), fs_1.default.createWriteStream(downloadPath)); - } - finally { - clearTimeout(timeout); - } -} -async function unzip(packagePath, outputPath) { - return new Promise((resolve, reject) => { - yauzl_1.default.open(packagePath, { lazyEntries: true, autoClose: true }, (err, zipfile) => { - if (err) { - return reject(err); - } - const result = []; - zipfile.on('entry', entry => { - if (/\/$/.test(entry.fileName)) { - zipfile.readEntry(); - } - else { - zipfile.openReadStream(entry, (err, istream) => { - if (err) { - return reject(err); - } - const filePath = path_1.default.join(outputPath, entry.fileName); - fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true }); - const ostream = fs_1.default.createWriteStream(filePath); - ostream.on('finish', () => { - result.push(filePath); - zipfile.readEntry(); - }); - istream?.on('error', err => reject(err)); - istream.pipe(ostream); - }); - } - }); - zipfile.on('close', () => resolve(result)); - zipfile.readEntry(); - }); - }); -} -// Contains all of the logic for mapping details to our actual product names in CosmosDB -function getPlatform(product, os, arch, type) { - switch (os) { - case 'win32': - switch (product) { - case 'client': { - switch (type) { - case 'archive': - return `win32-${arch}-archive`; - case 'setup': - return `win32-${arch}`; - case 'user-setup': - return `win32-${arch}-user`; - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } - } - case 'server': - return `server-win32-${arch}`; - case 'web': - return `server-win32-${arch}-web`; - case 'cli': - return `cli-win32-${arch}`; - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } - case 'alpine': - switch (product) { - case 'server': - return `server-alpine-${arch}`; - case 'web': - return `server-alpine-${arch}-web`; - case 'cli': - return `cli-alpine-${arch}`; - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } - case 'linux': - switch (type) { - case 'snap': - return `linux-snap-${arch}`; - case 'archive-unsigned': - switch (product) { - case 'client': - return `linux-${arch}`; - case 'server': - return `server-linux-${arch}`; - case 'web': - if (arch === 'standalone') { - return 'web-standalone'; - } - return `server-linux-${arch}-web`; - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } - case 'deb-package': - return `linux-deb-${arch}`; - case 'rpm-package': - return `linux-rpm-${arch}`; - case 'cli': - return `cli-linux-${arch}`; - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } - case 'darwin': - switch (product) { - case 'client': - if (arch === 'x64') { - return 'darwin'; - } - return `darwin-${arch}`; - case 'server': - if (arch === 'x64') { - return 'server-darwin'; - } - return `server-darwin-${arch}`; - case 'web': - if (arch === 'x64') { - return 'server-darwin-web'; - } - return `server-darwin-${arch}-web`; - case 'cli': - return `cli-darwin-${arch}`; - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } - default: - throw new Error(`Unrecognized: ${product} ${os} ${arch} ${type}`); - } -} -// Contains all of the logic for mapping types to our actual types in CosmosDB -function getRealType(type) { - switch (type) { - case 'user-setup': - return 'setup'; - case 'deb-package': - case 'rpm-package': - return 'package'; - default: - return type; - } -} -async function withLease(client, fn) { - const lease = client.getBlobLeaseClient(); - for (let i = 0; i < 360; i++) { // Try to get lease for 30 minutes - try { - await client.uploadData(new ArrayBuffer()); // blob needs to exist for lease to be acquired - await lease.acquireLease(60); - try { - const abortController = new AbortController(); - const refresher = new Promise((c, e) => { - abortController.signal.onabort = () => { - (0, node_timers_1.clearInterval)(interval); - c(); - }; - const interval = (0, node_timers_1.setInterval)(() => { - lease.renewLease().catch(err => { - (0, node_timers_1.clearInterval)(interval); - e(new Error('Failed to renew lease ' + err)); - }); - }, 30_000); - }); - const result = await Promise.race([fn(), refresher]); - abortController.abort(); - return result; - } - finally { - await lease.releaseLease(); - } - } - catch (err) { - if (err.statusCode !== 409 && err.statusCode !== 412) { - throw err; - } - await new Promise(c => setTimeout(c, 5000)); - } - } - throw new Error('Failed to acquire lease on blob after 30 minutes'); -} -async function processArtifact(artifact, filePath) { - const log = (...args) => console.log(`[${artifact.name}]`, ...args); - const match = /^vscode_(?[^_]+)_(?[^_]+)(?:_legacy)?_(?[^_]+)_(?[^_]+)$/.exec(artifact.name); - if (!match) { - throw new Error(`Invalid artifact name: ${artifact.name}`); - } - const { cosmosDBAccessToken, blobServiceAccessToken } = JSON.parse(e('PUBLISH_AUTH_TOKENS')); - const quality = e('VSCODE_QUALITY'); - const version = e('BUILD_SOURCEVERSION'); - const friendlyFileName = `${quality}/${version}/${path_1.default.basename(filePath)}`; - const blobServiceClient = new storage_blob_1.BlobServiceClient(`https://${e('VSCODE_STAGING_BLOB_STORAGE_ACCOUNT_NAME')}.blob.core.windows.net/`, { getToken: async () => blobServiceAccessToken }); - const leasesContainerClient = blobServiceClient.getContainerClient('leases'); - await leasesContainerClient.createIfNotExists(); - const leaseBlobClient = leasesContainerClient.getBlockBlobClient(friendlyFileName); - log(`Acquiring lease for: ${friendlyFileName}`); - await withLease(leaseBlobClient, async () => { - log(`Successfully acquired lease for: ${friendlyFileName}`); - const url = `${e('PRSS_CDN_URL')}/${friendlyFileName}`; - const res = await (0, retry_1.retry)(() => fetch(url)); - if (res.status === 200) { - log(`Already released and provisioned: ${url}`); - } - else { - const stagingContainerClient = blobServiceClient.getContainerClient('staging'); - await stagingContainerClient.createIfNotExists(); - const now = new Date().valueOf(); - const oneHour = 60 * 60 * 1000; - const oneHourAgo = new Date(now - oneHour); - const oneHourFromNow = new Date(now + oneHour); - const userDelegationKey = await blobServiceClient.getUserDelegationKey(oneHourAgo, oneHourFromNow); - const sasOptions = { containerName: 'staging', permissions: storage_blob_1.ContainerSASPermissions.from({ read: true }), startsOn: oneHourAgo, expiresOn: oneHourFromNow }; - const stagingSasToken = (0, storage_blob_1.generateBlobSASQueryParameters)(sasOptions, userDelegationKey, e('VSCODE_STAGING_BLOB_STORAGE_ACCOUNT_NAME')).toString(); - const releaseService = await ESRPReleaseService.create(log, e('RELEASE_TENANT_ID'), e('RELEASE_CLIENT_ID'), e('RELEASE_AUTH_CERT'), e('RELEASE_REQUEST_SIGNING_CERT'), stagingContainerClient, stagingSasToken); - await releaseService.createRelease(version, filePath, friendlyFileName); - } - const { product, os, arch, unprocessedType } = match.groups; - const platform = getPlatform(product, os, arch, unprocessedType); - const type = getRealType(unprocessedType); - const size = fs_1.default.statSync(filePath).size; - const stream = fs_1.default.createReadStream(filePath); - const [hash, sha256hash] = await Promise.all([hashStream('sha1', stream), hashStream('sha256', stream)]); // CodeQL [SM04514] Using SHA1 only for legacy reasons, we are actually only respecting SHA256 - const asset = { platform, type, url, hash: hash.toString('hex'), sha256hash: sha256hash.toString('hex'), size, supportsFastUpdate: true }; - log('Creating asset...'); - const result = await (0, retry_1.retry)(async (attempt) => { - log(`Creating asset in Cosmos DB (attempt ${attempt})...`); - const client = new cosmos_1.CosmosClient({ endpoint: e('AZURE_DOCUMENTDB_ENDPOINT'), tokenProvider: () => Promise.resolve(`type=aad&ver=1.0&sig=${cosmosDBAccessToken.token}`) }); - const scripts = client.database('builds').container(quality).scripts; - const { resource: result } = await scripts.storedProcedure('createAsset').execute('', [version, asset, true]); - return result; - }); - if (result === 'already exists') { - log('Asset already exists!'); - } - else { - log('Asset successfully created: ', JSON.stringify(asset, undefined, 2)); - } - }); - log(`Successfully released lease for: ${friendlyFileName}`); -} -// It is VERY important that we don't download artifacts too much too fast from AZDO. -// AZDO throttles us SEVERELY if we do. Not just that, but they also close open -// sockets, so the whole things turns to a grinding halt. So, downloading and extracting -// happens serially in the main thread, making the downloads are spaced out -// properly. For each extracted artifact, we spawn a worker thread to upload it to -// the CDN and finally update the build in Cosmos DB. -async function main() { - if (!node_worker_threads_1.isMainThread) { - const { artifact, artifactFilePath } = node_worker_threads_1.workerData; - await processArtifact(artifact, artifactFilePath); - return; - } - const done = new State(); - const processing = new Set(); - for (const name of done) { - console.log(`\u2705 ${name}`); - } - const stages = new Set(['Compile']); - if (e('VSCODE_BUILD_STAGE_LINUX') === 'True' || - e('VSCODE_BUILD_STAGE_ALPINE') === 'True' || - e('VSCODE_BUILD_STAGE_MACOS') === 'True' || - e('VSCODE_BUILD_STAGE_WINDOWS') === 'True') { - stages.add('CompileCLI'); - } - if (e('VSCODE_BUILD_STAGE_WINDOWS') === 'True') { - stages.add('Windows'); - } - if (e('VSCODE_BUILD_STAGE_LINUX') === 'True') { - stages.add('Linux'); - } - if (e('VSCODE_BUILD_STAGE_ALPINE') === 'True') { - stages.add('Alpine'); - } - if (e('VSCODE_BUILD_STAGE_MACOS') === 'True') { - stages.add('macOS'); - } - if (e('VSCODE_BUILD_STAGE_WEB') === 'True') { - stages.add('Web'); - } - let timeline; - let artifacts; - let resultPromise = Promise.resolve([]); - const operations = []; - while (true) { - [timeline, artifacts] = await Promise.all([(0, retry_1.retry)(() => getPipelineTimeline()), (0, retry_1.retry)(() => getPipelineArtifacts())]); - const stagesCompleted = new Set(timeline.records.filter(r => r.type === 'Stage' && r.state === 'completed' && stages.has(r.name)).map(r => r.name)); - const stagesInProgress = [...stages].filter(s => !stagesCompleted.has(s)); - const artifactsInProgress = artifacts.filter(a => processing.has(a.name)); - if (stagesInProgress.length === 0 && artifacts.length === done.size + processing.size) { - break; - } - else if (stagesInProgress.length > 0) { - console.log('Stages in progress:', stagesInProgress.join(', ')); - } - else if (artifactsInProgress.length > 0) { - console.log('Artifacts in progress:', artifactsInProgress.map(a => a.name).join(', ')); - } - else { - console.log(`Waiting for a total of ${artifacts.length}, ${done.size} done, ${processing.size} in progress...`); - } - for (const artifact of artifacts) { - if (done.has(artifact.name) || processing.has(artifact.name)) { - continue; - } - console.log(`[${artifact.name}] Found new artifact`); - const artifactZipPath = path_1.default.join(e('AGENT_TEMPDIRECTORY'), `${artifact.name}.zip`); - await (0, retry_1.retry)(async (attempt) => { - const start = Date.now(); - console.log(`[${artifact.name}] Downloading (attempt ${attempt})...`); - await downloadArtifact(artifact, artifactZipPath); - const archiveSize = fs_1.default.statSync(artifactZipPath).size; - const downloadDurationS = (Date.now() - start) / 1000; - const downloadSpeedKBS = Math.round((archiveSize / 1024) / downloadDurationS); - console.log(`[${artifact.name}] Successfully downloaded after ${Math.floor(downloadDurationS)} seconds(${downloadSpeedKBS} KB/s).`); - }); - const artifactFilePaths = await unzip(artifactZipPath, e('AGENT_TEMPDIRECTORY')); - const artifactFilePath = artifactFilePaths.filter(p => !/_manifest/.test(p))[0]; - processing.add(artifact.name); - const promise = new Promise((resolve, reject) => { - const worker = new node_worker_threads_1.Worker(__filename, { workerData: { artifact, artifactFilePath } }); - worker.on('error', reject); - worker.on('exit', code => { - if (code === 0) { - resolve(); - } - else { - reject(new Error(`[${artifact.name}] Worker stopped with exit code ${code}`)); - } - }); - }); - const operation = promise.then(() => { - processing.delete(artifact.name); - done.add(artifact.name); - console.log(`\u2705 ${artifact.name} `); - }); - operations.push({ name: artifact.name, operation }); - resultPromise = Promise.allSettled(operations.map(o => o.operation)); - } - await new Promise(c => setTimeout(c, 10_000)); - } - console.log(`Found all ${done.size + processing.size} artifacts, waiting for ${processing.size} artifacts to finish publishing...`); - const artifactsInProgress = operations.filter(o => processing.has(o.name)); - if (artifactsInProgress.length > 0) { - console.log('Artifacts in progress:', artifactsInProgress.map(a => a.name).join(', ')); - } - const results = await resultPromise; - for (let i = 0; i < operations.length; i++) { - const result = results[i]; - if (result.status === 'rejected') { - console.error(`[${operations[i].name}]`, result.reason); - } - } - // Fail the job if any of the artifacts failed to publish - if (results.some(r => r.status === 'rejected')) { - throw new Error('Some artifacts failed to publish'); - } - // Also fail the job if any of the stages did not succeed - let shouldFail = false; - for (const stage of stages) { - const record = timeline.records.find(r => r.name === stage && r.type === 'Stage'); - if (record.result !== 'succeeded' && record.result !== 'succeededWithIssues') { - shouldFail = true; - console.error(`Stage ${stage} did not succeed: ${record.result}`); - } - } - if (shouldFail) { - throw new Error('Some stages did not succeed'); - } - console.log(`All ${done.size} artifacts published!`); -} -if (require.main === module) { - main().then(() => { - process.exit(0); - }, err => { - console.error(err); - process.exit(1); - }); -} -//# sourceMappingURL=publish.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index 2b1c15007b311..5761c0d06df23 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -10,7 +10,7 @@ import type { ReadableStream } from 'stream/web'; import { pipeline } from 'node:stream/promises'; import yauzl from 'yauzl'; import crypto from 'crypto'; -import { retry } from './retry'; +import { retry } from './retry.ts'; import { CosmosClient } from '@azure/cosmos'; import cp from 'child_process'; import os from 'os'; @@ -73,15 +73,16 @@ interface ReleaseError { errorMessages: string[]; } -const enum StatusCode { - Pass = 'pass', - Aborted = 'aborted', - Inprogress = 'inprogress', - FailCanRetry = 'failCanRetry', - FailDoNotRetry = 'failDoNotRetry', - PendingAnalysis = 'pendingAnalysis', - Cancelled = 'cancelled' -} +const StatusCode = Object.freeze({ + Pass: 'pass', + Aborted: 'aborted', + Inprogress: 'inprogress', + FailCanRetry: 'failCanRetry', + FailDoNotRetry: 'failDoNotRetry', + PendingAnalysis: 'pendingAnalysis', + Cancelled: 'cancelled' +}); +type StatusCode = typeof StatusCode[keyof typeof StatusCode]; interface ReleaseResultMessage { activities: ReleaseActivityInfo[]; @@ -315,7 +316,7 @@ function getCertificatesFromPFX(pfx: string): string[] { class ESRPReleaseService { static async create( - log: (...args: any[]) => void, + log: (...args: unknown[]) => void, tenantId: string, clientId: string, authCertificatePfx: string, @@ -349,15 +350,31 @@ class ESRPReleaseService { private static API_URL = 'https://api.esrp.microsoft.com/api/v3/releaseservices/clients/'; + private readonly log: (...args: unknown[]) => void; + private readonly clientId: string; + private readonly accessToken: string; + private readonly requestSigningCertificates: string[]; + private readonly requestSigningKey: string; + private readonly containerClient: ContainerClient; + private readonly stagingSasToken: string; + private constructor( - private readonly log: (...args: any[]) => void, - private readonly clientId: string, - private readonly accessToken: string, - private readonly requestSigningCertificates: string[], - private readonly requestSigningKey: string, - private readonly containerClient: ContainerClient, - private readonly stagingSasToken: string - ) { } + log: (...args: unknown[]) => void, + clientId: string, + accessToken: string, + requestSigningCertificates: string[], + requestSigningKey: string, + containerClient: ContainerClient, + stagingSasToken: string + ) { + this.log = log; + this.clientId = clientId; + this.accessToken = accessToken; + this.requestSigningCertificates = requestSigningCertificates; + this.requestSigningKey = requestSigningKey; + this.containerClient = containerClient; + this.stagingSasToken = stagingSasToken; + } async createRelease(version: string, filePath: string, friendlyFileName: string) { const correlationId = crypto.randomUUID(); @@ -512,17 +529,21 @@ class ESRPReleaseService { } private async generateJwsToken(message: ReleaseRequestMessage): Promise { + // Create header with properly typed properties, then override x5c with the non-standard string format + const header: jws.Header = { + alg: 'RS256', + crit: ['exp', 'x5t'], + // Release service uses ticks, not seconds :roll_eyes: (https://stackoverflow.com/a/7968483) + exp: ((Date.now() + (6 * 60 * 1000)) * 10000) + 621355968000000000, + // Release service uses hex format, not base64url :roll_eyes: + x5t: getThumbprint(this.requestSigningCertificates[0], 'sha1').toString('hex'), + }; + + // The Release service expects x5c as a '.' separated string, not the standard array format + (header as Record)['x5c'] = this.requestSigningCertificates.map(c => getCertificateBuffer(c).toString('base64url')).join('.'); + return jws.sign({ - header: { - alg: 'RS256', - crit: ['exp', 'x5t'], - // Release service uses ticks, not seconds :roll_eyes: (https://stackoverflow.com/a/7968483) - exp: ((Date.now() + (6 * 60 * 1000)) * 10000) + 621355968000000000, - // Release service uses hex format, not base64url :roll_eyes: - x5t: getThumbprint(this.requestSigningCertificates[0], 'sha1').toString('hex'), - // Release service uses a '.' separated string, not an array of strings :roll_eyes: - x5c: this.requestSigningCertificates.map(c => getCertificateBuffer(c).toString('base64url')).join('.') as any, - }, + header, payload: message, privateKey: this.requestSigningKey, }); @@ -844,7 +865,7 @@ async function processArtifact( artifact: Artifact, filePath: string ) { - const log = (...args: any[]) => console.log(`[${artifact.name}]`, ...args); + const log = (...args: unknown[]) => console.log(`[${artifact.name}]`, ...args); const match = /^vscode_(?[^_]+)_(?[^_]+)(?:_legacy)?_(?[^_]+)_(?[^_]+)$/.exec(artifact.name); if (!match) { @@ -1005,7 +1026,7 @@ async function main() { processing.add(artifact.name); const promise = new Promise((resolve, reject) => { - const worker = new Worker(__filename, { workerData: { artifact, artifactFilePath } }); + const worker = new Worker(import.meta.filename, { workerData: { artifact, artifactFilePath } }); worker.on('error', reject); worker.on('exit', code => { if (code === 0) { @@ -1071,7 +1092,7 @@ async function main() { console.log(`All ${done.size} artifacts published!`); } -if (require.main === module) { +if (import.meta.main) { main().then(() => { process.exit(0); }, err => { diff --git a/build/azure-pipelines/common/releaseBuild.js b/build/azure-pipelines/common/releaseBuild.js deleted file mode 100644 index fa69cb4e258a5..0000000000000 --- a/build/azure-pipelines/common/releaseBuild.js +++ /dev/null @@ -1,57 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const identity_1 = require("@azure/identity"); -const cosmos_1 = require("@azure/cosmos"); -const retry_1 = require("./retry"); -function getEnv(name) { - const result = process.env[name]; - if (typeof result === 'undefined') { - throw new Error('Missing env: ' + name); - } - return result; -} -function createDefaultConfig(quality) { - return { - id: quality, - frozen: false - }; -} -async function getConfig(client, quality) { - const query = `SELECT TOP 1 * FROM c WHERE c.id = "${quality}"`; - const res = await client.database('builds').container('config').items.query(query).fetchAll(); - if (res.resources.length === 0) { - return createDefaultConfig(quality); - } - return res.resources[0]; -} -async function main(force) { - const commit = getEnv('BUILD_SOURCEVERSION'); - const quality = getEnv('VSCODE_QUALITY'); - const aadCredentials = new identity_1.ClientAssertionCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], () => Promise.resolve(process.env['AZURE_ID_TOKEN'])); - const client = new cosmos_1.CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT'], aadCredentials }); - if (!force) { - const config = await getConfig(client, quality); - console.log('Quality config:', config); - if (config.frozen) { - console.log(`Skipping release because quality ${quality} is frozen.`); - return; - } - } - console.log(`Releasing build ${commit}...`); - const scripts = client.database('builds').container(quality).scripts; - await (0, retry_1.retry)(() => scripts.storedProcedure('releaseBuild').execute('', [commit])); -} -const [, , force] = process.argv; -console.log(process.argv); -main(/^true$/i.test(force)).then(() => { - console.log('Build successfully released'); - process.exit(0); -}, err => { - console.error(err); - process.exit(1); -}); -//# sourceMappingURL=releaseBuild.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/releaseBuild.ts b/build/azure-pipelines/common/releaseBuild.ts index b7762de7df608..01792fd22e1c3 100644 --- a/build/azure-pipelines/common/releaseBuild.ts +++ b/build/azure-pipelines/common/releaseBuild.ts @@ -3,9 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ClientAssertionCredential } from '@azure/identity'; import { CosmosClient } from '@azure/cosmos'; -import { retry } from './retry'; +import { retry } from './retry.ts'; function getEnv(name: string): string { const result = process.env[name]; @@ -44,9 +43,8 @@ async function getConfig(client: CosmosClient, quality: string): Promise async function main(force: boolean): Promise { const commit = getEnv('BUILD_SOURCEVERSION'); const quality = getEnv('VSCODE_QUALITY'); - - const aadCredentials = new ClientAssertionCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, () => Promise.resolve(process.env['AZURE_ID_TOKEN']!)); - const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, aadCredentials }); + const { cosmosDBAccessToken } = JSON.parse(getEnv('PUBLISH_AUTH_TOKENS')); + const client = new CosmosClient({ endpoint: process.env['AZURE_DOCUMENTDB_ENDPOINT']!, tokenProvider: () => Promise.resolve(`type=aad&ver=1.0&sig=${cosmosDBAccessToken.token}`) }); if (!force) { const config = await getConfig(client, quality); @@ -61,8 +59,15 @@ async function main(force: boolean): Promise { console.log(`Releasing build ${commit}...`); + let rolloutDurationMs = undefined; + + // If the build is insiders or exploration, start a rollout of 4 hours + if (quality === 'insider') { + rolloutDurationMs = 4 * 60 * 60 * 1000; // 4 hours + } + const scripts = client.database('builds').container(quality).scripts; - await retry(() => scripts.storedProcedure('releaseBuild').execute('', [commit])); + await retry(() => scripts.storedProcedure('releaseBuild').execute('', [commit, rolloutDurationMs])); } const [, , force] = process.argv; diff --git a/build/azure-pipelines/common/retry.js b/build/azure-pipelines/common/retry.js deleted file mode 100644 index 91f60bf24b204..0000000000000 --- a/build/azure-pipelines/common/retry.js +++ /dev/null @@ -1,27 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.retry = retry; -async function retry(fn) { - let lastError; - for (let run = 1; run <= 10; run++) { - try { - return await fn(run); - } - catch (err) { - if (!/fetch failed|terminated|aborted|timeout|TimeoutError|Timeout Error|RestError|Client network socket disconnected|socket hang up|ECONNRESET|CredentialUnavailableError|endpoints_resolution_error|Audience validation failed|end of central directory record signature not found/i.test(err.message)) { - throw err; - } - lastError = err; - // maximum delay is 10th retry: ~3 seconds - const millis = Math.floor((Math.random() * 200) + (50 * Math.pow(1.5, run))); - await new Promise(c => setTimeout(c, millis)); - } - } - console.error(`Too many retries, aborting.`); - throw lastError; -} -//# sourceMappingURL=retry.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/sign-win32.js b/build/azure-pipelines/common/sign-win32.js deleted file mode 100644 index f4e3f27c1f255..0000000000000 --- a/build/azure-pipelines/common/sign-win32.js +++ /dev/null @@ -1,18 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const sign_1 = require("./sign"); -const path_1 = __importDefault(require("path")); -(0, sign_1.main)([ - process.env['EsrpCliDllPath'], - 'sign-windows', - path_1.default.dirname(process.argv[2]), - path_1.default.basename(process.argv[2]) -]); -//# sourceMappingURL=sign-win32.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/sign-win32.ts b/build/azure-pipelines/common/sign-win32.ts index ad88435b5a38c..677c2024b9c18 100644 --- a/build/azure-pipelines/common/sign-win32.ts +++ b/build/azure-pipelines/common/sign-win32.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { main } from './sign'; +import { main } from './sign.ts'; import path from 'path'; main([ diff --git a/build/azure-pipelines/common/sign.js b/build/azure-pipelines/common/sign.js deleted file mode 100644 index fd87772b3b874..0000000000000 --- a/build/azure-pipelines/common/sign.js +++ /dev/null @@ -1,206 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Temp = void 0; -exports.main = main; -const child_process_1 = __importDefault(require("child_process")); -const fs_1 = __importDefault(require("fs")); -const crypto_1 = __importDefault(require("crypto")); -const path_1 = __importDefault(require("path")); -const os_1 = __importDefault(require("os")); -class Temp { - _files = []; - tmpNameSync() { - const file = path_1.default.join(os_1.default.tmpdir(), crypto_1.default.randomBytes(20).toString('hex')); - this._files.push(file); - return file; - } - dispose() { - for (const file of this._files) { - try { - fs_1.default.unlinkSync(file); - } - catch (err) { - // noop - } - } - } -} -exports.Temp = Temp; -function getParams(type) { - switch (type) { - case 'sign-windows': - return [ - { - keyCode: 'CP-230012', - operationSetCode: 'SigntoolSign', - parameters: [ - { parameterName: 'OpusName', parameterValue: 'VS Code' }, - { parameterName: 'OpusInfo', parameterValue: 'https://code.visualstudio.com/' }, - { parameterName: 'Append', parameterValue: '/as' }, - { parameterName: 'FileDigest', parameterValue: '/fd "SHA256"' }, - { parameterName: 'PageHash', parameterValue: '/NPH' }, - { parameterName: 'TimeStamp', parameterValue: '/tr "http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer" /td sha256' } - ], - toolName: 'sign', - toolVersion: '1.0' - }, - { - keyCode: 'CP-230012', - operationSetCode: 'SigntoolVerify', - parameters: [ - { parameterName: 'VerifyAll', parameterValue: '/all' } - ], - toolName: 'sign', - toolVersion: '1.0' - } - ]; - case 'sign-windows-appx': - return [ - { - keyCode: 'CP-229979', - operationSetCode: 'SigntoolSign', - parameters: [ - { parameterName: 'OpusName', parameterValue: 'VS Code' }, - { parameterName: 'OpusInfo', parameterValue: 'https://code.visualstudio.com/' }, - { parameterName: 'FileDigest', parameterValue: '/fd "SHA256"' }, - { parameterName: 'PageHash', parameterValue: '/NPH' }, - { parameterName: 'TimeStamp', parameterValue: '/tr "http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer" /td sha256' } - ], - toolName: 'sign', - toolVersion: '1.0' - }, - { - keyCode: 'CP-229979', - operationSetCode: 'SigntoolVerify', - parameters: [], - toolName: 'sign', - toolVersion: '1.0' - } - ]; - case 'sign-pgp': - return [{ - keyCode: 'CP-450779-Pgp', - operationSetCode: 'LinuxSign', - parameters: [], - toolName: 'sign', - toolVersion: '1.0' - }]; - case 'sign-darwin': - return [{ - keyCode: 'CP-401337-Apple', - operationSetCode: 'MacAppDeveloperSign', - parameters: [{ parameterName: 'Hardening', parameterValue: '--options=runtime' }], - toolName: 'sign', - toolVersion: '1.0' - }]; - case 'notarize-darwin': - return [{ - keyCode: 'CP-401337-Apple', - operationSetCode: 'MacAppNotarize', - parameters: [], - toolName: 'sign', - toolVersion: '1.0' - }]; - case 'nuget': - return [{ - keyCode: 'CP-401405', - operationSetCode: 'NuGetSign', - parameters: [], - toolName: 'sign', - toolVersion: '1.0' - }, { - keyCode: 'CP-401405', - operationSetCode: 'NuGetVerify', - parameters: [], - toolName: 'sign', - toolVersion: '1.0' - }]; - default: - throw new Error(`Sign type ${type} not found`); - } -} -function main([esrpCliPath, type, folderPath, pattern]) { - const tmp = new Temp(); - process.on('exit', () => tmp.dispose()); - const key = crypto_1.default.randomBytes(32); - const iv = crypto_1.default.randomBytes(16); - const cipher = crypto_1.default.createCipheriv('aes-256-cbc', key, iv); - const encryptedToken = cipher.update(process.env['SYSTEM_ACCESSTOKEN'].trim(), 'utf8', 'hex') + cipher.final('hex'); - const encryptionDetailsPath = tmp.tmpNameSync(); - fs_1.default.writeFileSync(encryptionDetailsPath, JSON.stringify({ key: key.toString('hex'), iv: iv.toString('hex') })); - const encryptedTokenPath = tmp.tmpNameSync(); - fs_1.default.writeFileSync(encryptedTokenPath, encryptedToken); - const patternPath = tmp.tmpNameSync(); - fs_1.default.writeFileSync(patternPath, pattern); - const paramsPath = tmp.tmpNameSync(); - fs_1.default.writeFileSync(paramsPath, JSON.stringify(getParams(type))); - const dotnetVersion = child_process_1.default.execSync('dotnet --version', { encoding: 'utf8' }).trim(); - const adoTaskVersion = path_1.default.basename(path_1.default.dirname(path_1.default.dirname(esrpCliPath))); - const federatedTokenData = { - jobId: process.env['SYSTEM_JOBID'], - planId: process.env['SYSTEM_PLANID'], - projectId: process.env['SYSTEM_TEAMPROJECTID'], - hub: process.env['SYSTEM_HOSTTYPE'], - uri: process.env['SYSTEM_COLLECTIONURI'], - managedIdentityId: process.env['VSCODE_ESRP_CLIENT_ID'], - managedIdentityTenantId: process.env['VSCODE_ESRP_TENANT_ID'], - serviceConnectionId: process.env['VSCODE_ESRP_SERVICE_CONNECTION_ID'], - tempDirectory: os_1.default.tmpdir(), - systemAccessToken: encryptedTokenPath, - encryptionKey: encryptionDetailsPath - }; - const args = [ - esrpCliPath, - 'vsts.sign', - '-a', process.env['ESRP_CLIENT_ID'], - '-d', process.env['ESRP_TENANT_ID'], - '-k', JSON.stringify({ akv: 'vscode-esrp' }), - '-z', JSON.stringify({ akv: 'vscode-esrp', cert: 'esrp-sign' }), - '-f', folderPath, - '-p', patternPath, - '-u', 'false', - '-x', 'regularSigning', - '-b', 'input.json', - '-l', 'AzSecPack_PublisherPolicyProd.xml', - '-y', 'inlineSignParams', - '-j', paramsPath, - '-c', '9997', - '-t', '120', - '-g', '10', - '-v', 'Tls12', - '-s', 'https://api.esrp.microsoft.com/api/v1', - '-m', '0', - '-o', 'Microsoft', - '-i', 'https://www.microsoft.com', - '-n', '5', - '-r', 'true', - '-w', dotnetVersion, - '-skipAdoReportAttachment', 'false', - '-pendingAnalysisWaitTimeoutMinutes', '5', - '-adoTaskVersion', adoTaskVersion, - '-resourceUri', 'https://msazurecloud.onmicrosoft.com/api.esrp.microsoft.com', - '-esrpClientId', process.env['ESRP_CLIENT_ID'], - '-useMSIAuthentication', 'true', - '-federatedTokenData', JSON.stringify(federatedTokenData) - ]; - try { - child_process_1.default.execFileSync('dotnet', args, { stdio: 'inherit' }); - } - catch (err) { - console.error('ESRP failed'); - console.error(err); - process.exit(1); - } -} -if (require.main === module) { - main(process.argv.slice(2)); - process.exit(0); -} -//# sourceMappingURL=sign.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/sign.ts b/build/azure-pipelines/common/sign.ts index 19a288483c85e..d93f752eeebb1 100644 --- a/build/azure-pipelines/common/sign.ts +++ b/build/azure-pipelines/common/sign.ts @@ -216,7 +216,7 @@ export function main([esrpCliPath, type, folderPath, pattern]: string[]) { } } -if (require.main === module) { +if (import.meta.main) { main(process.argv.slice(2)); process.exit(0); } diff --git a/build/azure-pipelines/common/waitForArtifacts.js b/build/azure-pipelines/common/waitForArtifacts.js deleted file mode 100644 index b9ffb73962d99..0000000000000 --- a/build/azure-pipelines/common/waitForArtifacts.js +++ /dev/null @@ -1,46 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const publish_1 = require("../common/publish"); -const retry_1 = require("../common/retry"); -async function getPipelineArtifacts() { - const result = await (0, publish_1.requestAZDOAPI)('artifacts'); - return result.value.filter(a => !/sbom$/.test(a.name)); -} -async function main(artifacts) { - if (artifacts.length === 0) { - throw new Error(`Usage: node waitForArtifacts.js ...`); - } - // This loop will run for 30 minutes and waits to the x64 and arm64 artifacts - // to be uploaded to the pipeline by the `macOS` and `macOSARM64` jobs. As soon - // as these artifacts are found, the loop completes and the `macOSUnivesrsal` - // job resumes. - for (let index = 0; index < 60; index++) { - try { - console.log(`Waiting for artifacts (${artifacts.join(', ')}) to be uploaded (${index + 1}/60)...`); - const allArtifacts = await (0, retry_1.retry)(() => getPipelineArtifacts()); - console.log(` * Artifacts attached to the pipelines: ${allArtifacts.length > 0 ? allArtifacts.map(a => a.name).join(', ') : 'none'}`); - const foundArtifacts = allArtifacts.filter(a => artifacts.includes(a.name)); - console.log(` * Found artifacts: ${foundArtifacts.length > 0 ? foundArtifacts.map(a => a.name).join(', ') : 'none'}`); - if (foundArtifacts.length === artifacts.length) { - console.log(` * All artifacts were found`); - return; - } - } - catch (err) { - console.error(`ERROR: Failed to get pipeline artifacts: ${err}`); - } - await new Promise(c => setTimeout(c, 30_000)); - } - throw new Error(`ERROR: Artifacts (${artifacts.join(', ')}) were not uploaded within 30 minutes.`); -} -main(process.argv.splice(2)).then(() => { - process.exit(0); -}, err => { - console.error(err); - process.exit(1); -}); -//# sourceMappingURL=waitForArtifacts.js.map \ No newline at end of file diff --git a/build/azure-pipelines/common/waitForArtifacts.ts b/build/azure-pipelines/common/waitForArtifacts.ts index 3fed6cd38d2ef..1b48a70d9944c 100644 --- a/build/azure-pipelines/common/waitForArtifacts.ts +++ b/build/azure-pipelines/common/waitForArtifacts.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Artifact, requestAZDOAPI } from '../common/publish'; -import { retry } from '../common/retry'; +import { type Artifact, requestAZDOAPI } from '../common/publish.ts'; +import { retry } from '../common/retry.ts'; async function getPipelineArtifacts(): Promise { const result = await requestAZDOAPI<{ readonly value: Artifact[] }>('artifacts'); @@ -13,7 +13,7 @@ async function getPipelineArtifacts(): Promise { async function main(artifacts: string[]): Promise { if (artifacts.length === 0) { - throw new Error(`Usage: node waitForArtifacts.js ...`); + throw new Error(`Usage: node waitForArtifacts.ts ...`); } // This loop will run for 30 minutes and waits to the x64 and arm64 artifacts diff --git a/build/azure-pipelines/config/CredScanSuppressions.json b/build/azure-pipelines/config/CredScanSuppressions.json index bf52c06cf899c..7cb84e9d0ca27 100644 --- a/build/azure-pipelines/config/CredScanSuppressions.json +++ b/build/azure-pipelines/config/CredScanSuppressions.json @@ -4,12 +4,19 @@ { "file": [ "src/vs/base/test/common/uri.test.ts", - "src/vs/workbench/api/test/browser/extHostTelemetry.test.ts" + "src/vs/workbench/api/test/browser/extHostTelemetry.test.ts", + "src/vs/base/test/common/yaml.test.ts" ], "_justification": "These are dummy credentials in tests." }, { "file": [ + ".build/linux/deb/amd64/code-amd64/usr/share/code/resources/app/extensions/github-authentication/dist/extension.js", + ".build/linux/deb/amd64/code-amd64/usr/share/code/resources/app/extensions/emmet/dist/node/emmetNodeMain.js", + ".build/linux/deb/armhf/code-armhf/usr/share/code/resources/app/extensions/github-authentication/dist/extension.js", + ".build/linux/deb/armhf/code-armhf/usr/share/code/resources/app/extensions/emmet/dist/node/emmetNodeMain.js", + ".build/linux/deb/arm64/code-arm64/usr/share/code/resources/app/extensions/github-authentication/dist/extension.js", + ".build/linux/deb/arm64/code-arm64/usr/share/code/resources/app/extensions/emmet/dist/node/emmetNodeMain.js", ".build/linux/rpm/x86_64/rpmbuild/BUILD/usr/share/code/resources/app/extensions/github-authentication/dist/extension.js", ".build/linux/rpm/x86_64/rpmbuild/BUILD/usr/share/code/resources/app/extensions/emmet/dist/node/emmetNodeMain.js", ".build/linux/rpm/armv7hl/rpmbuild/BUILD/usr/share/code/resources/app/extensions/github-authentication/dist/extension.js", @@ -33,6 +40,12 @@ }, { "file": [ + ".build/linux/deb/amd64/code-insiders-amd64/usr/share/code-insiders/resources/app/extensions/github-authentication/dist/extension.js", + ".build/linux/deb/amd64/code-insiders-amd64/usr/share/code-insiders/resources/app/extensions/emmet/dist/node/emmetNodeMain.js", + ".build/linux/deb/armhf/code-insiders-armhf/usr/share/code-insiders/resources/app/extensions/github-authentication/dist/extension.js", + ".build/linux/deb/armhf/code-insiders-armhf/usr/share/code-insiders/resources/app/extensions/emmet/dist/node/emmetNodeMain.js", + ".build/linux/deb/arm64/code-insiders-arm64/usr/share/code-insiders/resources/app/extensions/github-authentication/dist/extension.js", + ".build/linux/deb/arm64/code-insiders-arm64/usr/share/code-insiders/resources/app/extensions/emmet/dist/node/emmetNodeMain.js", ".build/linux/rpm/x86_64/rpmbuild/BUILD/usr/share/code-insiders/resources/app/extensions/github-authentication/dist/extension.js", ".build/linux/rpm/x86_64/rpmbuild/BUILD/usr/share/code-insiders/resources/app/extensions/emmet/dist/node/emmetNodeMain.js", ".build/linux/rpm/armv7hl/rpmbuild/BUILD/usr/share/code-insiders/resources/app/extensions/github-authentication/dist/extension.js", @@ -56,6 +69,12 @@ }, { "file": [ + ".build/linux/deb/amd64/code-exploration-amd64/usr/share/code-exploration/resources/app/extensions/github-authentication/dist/extension.js", + ".build/linux/deb/amd64/code-exploration-amd64/usr/share/code-exploration/resources/app/extensions/emmet/dist/node/emmetNodeMain.js", + ".build/linux/deb/armhf/code-exploration-armhf/usr/share/code-exploration/resources/app/extensions/github-authentication/dist/extension.js", + ".build/linux/deb/armhf/code-exploration-armhf/usr/share/code-exploration/resources/app/extensions/emmet/dist/node/emmetNodeMain.js", + ".build/linux/deb/arm64/code-exploration-arm64/usr/share/code-exploration/resources/app/extensions/github-authentication/dist/extension.js", + ".build/linux/deb/arm64/code-exploration-arm64/usr/share/code-exploration/resources/app/extensions/emmet/dist/node/emmetNodeMain.js", ".build/linux/rpm/x86_64/rpmbuild/BUILD/usr/share/code-exploration/resources/app/extensions/github-authentication/dist/extension.js", ".build/linux/rpm/x86_64/rpmbuild/BUILD/usr/share/code-exploration/resources/app/extensions/emmet/dist/node/emmetNodeMain.js", ".build/linux/rpm/armv7hl/rpmbuild/BUILD/usr/share/code-exploration/resources/app/extensions/github-authentication/dist/extension.js", diff --git a/build/azure-pipelines/darwin/cli-build-darwin.yml b/build/azure-pipelines/darwin/cli-build-darwin.yml deleted file mode 100644 index 1d8dffc464d38..0000000000000 --- a/build/azure-pipelines/darwin/cli-build-darwin.yml +++ /dev/null @@ -1,88 +0,0 @@ -parameters: - - name: VSCODE_QUALITY - type: string - - name: VSCODE_BUILD_MACOS - type: boolean - default: false - - name: VSCODE_BUILD_MACOS_ARM64 - type: boolean - default: false - - name: VSCODE_CHECK_ONLY - type: boolean - default: false - -steps: - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - template: ../cli/cli-apply-patches.yml@self - - - task: Npm@1 - displayName: Download openssl prebuilt - inputs: - command: custom - customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 - customRegistry: useFeed - customFeed: "Monaco/openssl-prebuilt" - workingDir: $(Build.ArtifactStagingDirectory) - - - script: | - set -e - mkdir $(Build.ArtifactStagingDirectory)/openssl - tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl - displayName: Extract openssl prebuilt - - - template: ../cli/install-rust-posix.yml@self - parameters: - targets: - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - x86_64-apple-darwin - - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - aarch64-apple-darwin - - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_TARGET: x86_64-apple-darwin - VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_x64_cli - VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-osx/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-osx/include - - - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_TARGET: aarch64-apple-darwin - VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_arm64_cli - VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/include - - - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_x64_cli.zip - artifactName: unsigned_vscode_cli_darwin_x64_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code macOS x64 CLI (unsigned)" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish unsigned_vscode_cli_darwin_x64_cli artifact - - - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_arm64_cli.zip - artifactName: unsigned_vscode_cli_darwin_arm64_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code macOS arm64 CLI (unsigned)" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish unsigned_vscode_cli_darwin_arm64_cli artifact diff --git a/build/azure-pipelines/darwin/codesign.js b/build/azure-pipelines/darwin/codesign.js deleted file mode 100644 index edc3a5f6f8095..0000000000000 --- a/build/azure-pipelines/darwin/codesign.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const codesign_1 = require("../common/codesign"); -const publish_1 = require("../common/publish"); -async function main() { - const arch = (0, publish_1.e)('VSCODE_ARCH'); - const esrpCliDLLPath = (0, publish_1.e)('EsrpCliDllPath'); - const pipelineWorkspace = (0, publish_1.e)('PIPELINE_WORKSPACE'); - const folder = `${pipelineWorkspace}/unsigned_vscode_client_darwin_${arch}_archive`; - const glob = `VSCode-darwin-${arch}.zip`; - // Codesign - (0, codesign_1.printBanner)('Codesign'); - const codeSignTask = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-darwin', folder, glob); - await (0, codesign_1.streamProcessOutputAndCheckResult)('Codesign', codeSignTask); - // Notarize - (0, codesign_1.printBanner)('Notarize'); - const notarizeTask = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'notarize-darwin', folder, glob); - await (0, codesign_1.streamProcessOutputAndCheckResult)('Notarize', notarizeTask); -} -main().then(() => { - process.exit(0); -}, err => { - console.error(`ERROR: ${err}`); - process.exit(1); -}); -//# sourceMappingURL=codesign.js.map \ No newline at end of file diff --git a/build/azure-pipelines/darwin/codesign.ts b/build/azure-pipelines/darwin/codesign.ts index a9de0206d6ef3..848fb0f46473a 100644 --- a/build/azure-pipelines/darwin/codesign.ts +++ b/build/azure-pipelines/darwin/codesign.ts @@ -3,15 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { printBanner, spawnCodesignProcess, streamProcessOutputAndCheckResult } from '../common/codesign'; -import { e } from '../common/publish'; +import { printBanner, spawnCodesignProcess, streamProcessOutputAndCheckResult } from '../common/codesign.ts'; +import { e } from '../common/publish.ts'; async function main() { const arch = e('VSCODE_ARCH'); const esrpCliDLLPath = e('EsrpCliDllPath'); const pipelineWorkspace = e('PIPELINE_WORKSPACE'); - const folder = `${pipelineWorkspace}/unsigned_vscode_client_darwin_${arch}_archive`; + const folder = `${pipelineWorkspace}/vscode_client_darwin_${arch}_archive`; const glob = `VSCode-darwin-${arch}.zip`; // Codesign diff --git a/build/azure-pipelines/darwin/product-build-darwin-ci.yml b/build/azure-pipelines/darwin/product-build-darwin-ci.yml new file mode 100644 index 0000000000000..3920c4ec799c2 --- /dev/null +++ b/build/azure-pipelines/darwin/product-build-darwin-ci.yml @@ -0,0 +1,46 @@ +parameters: + - name: VSCODE_CIBUILD + type: boolean + - name: VSCODE_TEST_SUITE + type: string + +jobs: + - job: macOS${{ parameters.VSCODE_TEST_SUITE }} + displayName: ${{ parameters.VSCODE_TEST_SUITE }} Tests + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: arm64 + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/crashes + artifactName: crash-dump-macos-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) + displayName: Publish Crash Reports + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/node_modules + artifactName: node-modules-macos-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) + displayName: Publish Node Modules + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/logs + artifactName: logs-macos-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) + displayName: Publish Log Files + sbomEnabled: false + isProduction: false + condition: succeededOrFailed() + steps: + - template: ./steps/product-build-darwin-compile.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }} + ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Electron') }}: + VSCODE_RUN_ELECTRON_TESTS: true + ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Browser') }}: + VSCODE_RUN_BROWSER_TESTS: true + ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Remote') }}: + VSCODE_RUN_REMOTE_TESTS: true diff --git a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml b/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml index b3d01ca7ff167..94eee5e476c2a 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml @@ -3,64 +3,84 @@ parameters: type: boolean - name: VSCODE_BUILD_MACOS_ARM64 type: boolean - - name: VSCODE_QUALITY - type: string -steps: - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download +jobs: + - job: macOSCLISign + timeoutInMinutes: 90 + templateContext: + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out + outputs: + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_x64_cli/vscode_cli_darwin_x64_cli.zip + artifactName: vscode_cli_darwin_x64_cli + displayName: Publish signed artifact with ID vscode_cli_darwin_x64_cli + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/unsigned_vscode_cli_darwin_x64_cli + sbomPackageName: "VS Code macOS x64 CLI" + sbomPackageVersion: $(Build.SourceVersion) + - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_darwin_arm64_cli/vscode_cli_darwin_arm64_cli.zip + artifactName: vscode_cli_darwin_arm64_cli + displayName: Publish signed artifact with ID vscode_cli_darwin_arm64_cli + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/sign/unsigned_vscode_cli_darwin_arm64_cli + sbomPackageName: "VS Code macOS arm64 CLI" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc - - script: node build/setup-npm-registry.js $NPM_REGISTRY build - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM + - script: node build/setup-npm-registry.ts $NPM_REGISTRY build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM - - script: | - set -e + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies + - script: | + set -e - - template: ../cli/cli-darwin-sign.yml@self - parameters: - VSCODE_CLI_ARTIFACTS: - - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - unsigned_vscode_cli_darwin_x64_cli - - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: - - unsigned_vscode_cli_darwin_arm64_cli + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies + + - template: ./steps/product-build-darwin-cli-sign.yml@self + parameters: + VSCODE_CLI_ARTIFACTS: + - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: + - unsigned_vscode_cli_darwin_x64_cli + - ${{ if eq(parameters.VSCODE_BUILD_MACOS_ARM64, true) }}: + - unsigned_vscode_cli_darwin_arm64_cli diff --git a/build/azure-pipelines/darwin/product-build-darwin-cli.yml b/build/azure-pipelines/darwin/product-build-darwin-cli.yml new file mode 100644 index 0000000000000..35a9b3566cea6 --- /dev/null +++ b/build/azure-pipelines/darwin/product-build-darwin-cli.yml @@ -0,0 +1,83 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_CHECK_ONLY + type: boolean + default: false + - name: VSCODE_QUALITY + type: string + +jobs: +- job: macOSCLI_${{ parameters.VSCODE_ARCH }} + displayName: macOS (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 60 + pool: + name: AcesShared + os: macOS + variables: + # todo@connor4312 to diagnose build flakes + MSRUSTUP_LOG: debug + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + templateContext: + outputs: + - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli.zip + artifactName: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + displayName: Publish unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli artifact + sbomEnabled: false + isProduction: false + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../cli/cli-apply-patches.yml@self + + - task: Npm@1 + displayName: Download openssl prebuilt + inputs: + command: custom + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 + customRegistry: useFeed + customFeed: "Monaco/openssl-prebuilt" + workingDir: $(Build.ArtifactStagingDirectory) + + - script: | + set -e + mkdir $(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + displayName: Extract openssl prebuilt + + - template: ../cli/install-rust-posix.yml@self + parameters: + targets: + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - x86_64-apple-darwin + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - aarch64-apple-darwin + + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_TARGET: x86_64-apple-darwin + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_x64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-osx/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-osx/include + + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_TARGET: aarch64-apple-darwin + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_darwin_arm64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-osx/include diff --git a/build/azure-pipelines/darwin/product-build-darwin-node-modules.yml b/build/azure-pipelines/darwin/product-build-darwin-node-modules.yml new file mode 100644 index 0000000000000..19b38a60952d1 --- /dev/null +++ b/build/azure-pipelines/darwin/product-build-darwin-node-modules.yml @@ -0,0 +1,98 @@ +parameters: + - name: VSCODE_ARCH + type: string + +jobs: + - job: macOSNodeModules_${{ parameters.VSCODE_ARCH }} + displayName: macOS (${{ upper(parameters.VSCODE_ARCH) }}) + pool: + name: AcesShared + os: macOS + timeoutInMinutes: 60 + variables: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" + + - script: node build/setup-npm-registry.ts $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts darwin $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + c++ --version + xcode-select -print-path + python3 -m pip install setuptools + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + npm_config_arch: $(VSCODE_ARCH) + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + # Avoid using dlopen to load Kerberos on macOS which can cause missing libraries + # https://github.com/mongodb-js/kerberos/commit/04044d2814ad1d01e77f1ce87f26b03d86692cf2 + # flipped the default to support legacy linux distros which shouldn't happen + # on macOS. + GYP_DEFINES: "kerberos_use_rtld=false" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: node build/azure-pipelines/distro/mixin-npm.ts + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive diff --git a/build/azure-pipelines/darwin/product-build-darwin-test.yml b/build/azure-pipelines/darwin/product-build-darwin-test.yml deleted file mode 100644 index c542cacaf199c..0000000000000 --- a/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ /dev/null @@ -1,223 +0,0 @@ -parameters: - - name: VSCODE_QUALITY - type: string - - name: VSCODE_RUN_ELECTRON_TESTS - type: boolean - - name: VSCODE_RUN_BROWSER_TESTS - type: boolean - - name: VSCODE_RUN_REMOTE_TESTS - type: boolean - - name: VSCODE_TEST_ARTIFACT_NAME - type: string - - name: PUBLISH_TASK_NAME - type: string - default: PublishPipelineArtifact@0 - -steps: - - script: npm exec -- npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Download Electron and Playwright - retryCountOnTaskFailure: 3 - - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - script: ./scripts/test.sh --tfs "Unit Tests" - displayName: 🧪 Run unit tests (Electron) - timeoutInMinutes: 15 - - script: npm run test-node - displayName: 🧪 Run unit tests (node.js) - timeoutInMinutes: 15 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - script: npm run test-browser-no-install -- --browser webkit --tfs "Browser Unit Tests" - env: - DEBUG: "*browser*" - displayName: 🧪 Run unit tests (Browser, Webkit) - timeoutInMinutes: 30 - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - script: ./scripts/test.sh --build --tfs "Unit Tests" - displayName: 🧪 Run unit tests (Electron) - timeoutInMinutes: 15 - - script: npm run test-node -- --build - displayName: 🧪 Run unit tests (node.js) - timeoutInMinutes: 15 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - script: npm run test-browser-no-install -- --build --browser webkit --tfs "Browser Unit Tests" - env: - DEBUG: "*browser*" - displayName: 🧪 Run unit tests (Browser, Webkit) - timeoutInMinutes: 30 - - - script: | - set -e - npm run gulp \ - compile-extension:configuration-editing \ - compile-extension:css-language-features-server \ - compile-extension:emmet \ - compile-extension:git \ - compile-extension:github-authentication \ - compile-extension:html-language-features-server \ - compile-extension:ipynb \ - compile-extension:notebook-renderers \ - compile-extension:json-language-features-server \ - compile-extension:markdown-language-features \ - compile-extension-media \ - compile-extension:microsoft-authentication \ - compile-extension:typescript-language-features \ - compile-extension:vscode-api-tests \ - compile-extension:vscode-colorize-tests \ - compile-extension:vscode-colorize-perf-tests \ - compile-extension:vscode-test-resolver - displayName: Build integration tests - - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - script: ./scripts/test-integration.sh --tfs "Integration Tests" - displayName: 🧪 Run integration tests (Electron) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - script: ./scripts/test-web-integration.sh --browser webkit - displayName: 🧪 Run integration tests (Browser, Webkit) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: - - script: ./scripts/test-remote-integration.sh - displayName: 🧪 Run integration tests (Remote) - timeoutInMinutes: 20 - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT="$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) - displayName: 🧪 Run integration tests (Electron) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - script: ./scripts/test-web-integration.sh --browser webkit - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH)-web - displayName: 🧪 Run integration tests (Browser, Webkit) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ - ./scripts/test-remote-integration.sh - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) - displayName: 🧪 Run integration tests (Remote) - timeoutInMinutes: 20 - - - script: ps -ef - displayName: Diagnostics before smoke test run - continueOnError: true - condition: succeededOrFailed() - - # - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - # - script: npm run compile - # workingDirectory: test/smoke - # displayName: Compile smoke tests - - # - script: npm run gulp compile-extension-media - # displayName: Compile extensions for smoke tests - - # - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - # - script: npm run smoketest-no-compile -- --tracing - # timeoutInMinutes: 20 - # displayName: 🧪 Run smoke tests (Electron) - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - npm run smoketest-no-compile -- --tracing --build "$APP_ROOT/$APP_NAME" - timeoutInMinutes: 20 - displayName: 🧪 Run smoke tests (Electron) - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - script: npm run smoketest-no-compile -- --web --tracing --headless - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH)-web - timeoutInMinutes: 20 - displayName: 🧪 Run smoke tests (Browser, Chromium) - - - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: - - script: | - set -e - npm run gulp compile-extension:vscode-test-resolver - APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) - APP_NAME="`ls $APP_ROOT | head -n 1`" - npm run smoketest-no-compile -- --tracing --remote --build "$APP_ROOT/$APP_NAME" - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) - timeoutInMinutes: 20 - displayName: 🧪 Run smoke tests (Remote) - - - script: ps -ef - displayName: Diagnostics after smoke test run - continueOnError: true - condition: succeededOrFailed() - - - task: ${{ parameters.PUBLISH_TASK_NAME }} - inputs: - targetPath: .build/crashes - ${{ if eq(parameters.VSCODE_TEST_ARTIFACT_NAME, '') }}: - artifactName: crash-dump-macos-$(VSCODE_ARCH)-$(System.JobAttempt) - ${{ else }}: - artifactName: crash-dump-macos-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) - sbomEnabled: false - displayName: "Publish Crash Reports" - continueOnError: true - condition: failed() - - # In order to properly symbolify above crash reports - # (if any), we need the compiled native modules too - - task: ${{ parameters.PUBLISH_TASK_NAME }} - inputs: - targetPath: node_modules - ${{ if eq(parameters.VSCODE_TEST_ARTIFACT_NAME, '') }}: - artifactName: node-modules-macos-$(VSCODE_ARCH)-$(System.JobAttempt) - ${{ else }}: - artifactName: node-modules-macos-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) - sbomEnabled: false - displayName: "Publish Node Modules" - continueOnError: true - condition: failed() - - - task: ${{ parameters.PUBLISH_TASK_NAME }} - inputs: - targetPath: .build/logs - ${{ if eq(parameters.VSCODE_TEST_ARTIFACT_NAME, '') }}: - artifactName: logs-macos-$(VSCODE_ARCH)-$(System.JobAttempt) - ${{ else }}: - artifactName: logs-macos-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) - sbomEnabled: false - displayName: "Publish Log Files" - continueOnError: true - condition: succeededOrFailed() - - - task: PublishTestResults@2 - displayName: Publish Tests Results - inputs: - testResultsFiles: "*-results.xml" - searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" - condition: succeededOrFailed() diff --git a/build/azure-pipelines/darwin/product-build-darwin-universal.yml b/build/azure-pipelines/darwin/product-build-darwin-universal.yml index 9e9537fea7259..81bff1ae5f632 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-universal.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-universal.yml @@ -1,152 +1,161 @@ -steps: - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - template: ../distro/download-distro.yml@self - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" - - - script: node build/setup-npm-registry.js $NPM_REGISTRY build - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: | - set -e - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies - - - pwsh: node build/azure-pipelines/common/waitForArtifacts.js unsigned_vscode_client_darwin_x64_archive unsigned_vscode_client_darwin_arm64_archive - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: Wait for x64 and arm64 artifacts - - - download: current - artifact: unsigned_vscode_client_darwin_x64_archive - displayName: Download x64 artifact - - - download: current - artifact: unsigned_vscode_client_darwin_arm64_archive - displayName: Download arm64 artifact - - - script: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - - - script: | - set -e - unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_x64_archive/VSCode-darwin-x64.zip -d $(agent.builddirectory)/VSCode-darwin-x64 & - unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_arm64_archive/VSCode-darwin-arm64.zip -d $(agent.builddirectory)/VSCode-darwin-arm64 & - wait - DEBUG=* node build/darwin/create-universal-app.js $(agent.builddirectory) - displayName: Create Universal App - - - script: | - set -e - APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - APP_PATH="$APP_ROOT/$APP_NAME" node build/darwin/verify-macho.js universal - displayName: Verify arch of Mach-O objects - - - script: | - set -e - security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain - security default-keychain -s $(agent.tempdirectory)/buildagent.keychain - security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain - echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 - security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign - export CODESIGN_IDENTITY=$(security find-identity -v -p codesigning $(agent.tempdirectory)/buildagent.keychain | grep -oEi "([0-9A-F]{40})" | head -n 1) - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain - DEBUG=electron-osx-sign* node build/darwin/sign.js $(agent.builddirectory) - displayName: Set Hardened Entitlements - - - script: | - set -e - mkdir -p $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive - pushd $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) && zip -r -X -y $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip * && popd - displayName: Archive build - - - task: UseDotNet@2 - inputs: - version: 6.x - - - task: EsrpCodeSigning@5 - inputs: - UseMSIAuthentication: true - ConnectedServiceName: vscode-esrp - AppRegistrationClientId: $(ESRP_CLIENT_ID) - AppRegistrationTenantId: $(ESRP_TENANT_ID) - AuthAKVName: vscode-esrp - AuthSignCertName: esrp-sign - FolderPath: . - Pattern: noop - displayName: 'Install ESRP Tooling' - - - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Codesign - - - script: node build/azure-pipelines/common/sign $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll notarize-darwin $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Notarize - - - script: unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) - displayName: Extract signed app - - - script: | - set -e - APP_ROOT="$(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - APP_PATH="$APP_ROOT/$APP_NAME" - codesign -dv --deep --verbose=4 "$APP_PATH" - "$APP_PATH/Contents/Resources/app/bin/code" --export-default-configuration=.build - displayName: Verify signature - condition: and(succeeded(), ne(variables['VSCODE_ARCH'], 'arm64')) - - - script: mv $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-x64.zip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin.zip - displayName: Rename x64 build to its legacy name - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-universal.zip - artifactName: vscode_client_darwin_$(VSCODE_ARCH)_archive - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) - sbomPackageName: "VS Code macOS $(VSCODE_ARCH)" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish client archive +jobs: + - job: macOSUniversal + displayName: macOS (UNIVERSAL) + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: universal + BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-universal.zip + artifactName: vscode_client_darwin_$(VSCODE_ARCH)_archive + displayName: Publish client archive + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) + sbomPackageName: "VS Code macOS $(VSCODE_ARCH)" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" + + - script: node build/setup-npm-registry.ts $NPM_REGISTRY build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies + + - pwsh: node -- build/azure-pipelines/common/waitForArtifacts.ts unsigned_vscode_client_darwin_x64_archive unsigned_vscode_client_darwin_arm64_archive + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Wait for x64 and arm64 artifacts + + - download: current + artifact: unsigned_vscode_client_darwin_x64_archive + displayName: Download x64 artifact + + - download: current + artifact: unsigned_vscode_client_darwin_arm64_archive + displayName: Download arm64 artifact + + - script: node build/azure-pipelines/distro/mixin-quality.ts + displayName: Mixin distro quality + + - script: | + set -e + unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_x64_archive/VSCode-darwin-x64.zip -d $(agent.builddirectory)/VSCode-darwin-x64 & + unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_arm64_archive/VSCode-darwin-arm64.zip -d $(agent.builddirectory)/VSCode-darwin-arm64 & + wait + DEBUG=* node build/darwin/create-universal-app.ts $(agent.builddirectory) + displayName: Create Universal App + + - script: | + set -e + APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" node build/darwin/verify-macho.ts universal + displayName: Verify arch of Mach-O objects + + - script: | + set -e + security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + security default-keychain -s $(agent.tempdirectory)/buildagent.keychain + security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 + security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign + export CODESIGN_IDENTITY=$(security find-identity -v -p codesigning $(agent.tempdirectory)/buildagent.keychain | grep -oEi "([0-9A-F]{40})" | head -n 1) + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain + DEBUG=electron-osx-sign* node build/darwin/sign.ts $(agent.builddirectory) + displayName: Set Hardened Entitlements + + - script: | + set -e + mkdir -p $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive + pushd $(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) && zip -r -X -y $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip * && popd + displayName: Archive build + + - task: UseDotNet@2 + inputs: + version: 6.x + + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' + + - script: node build/azure-pipelines/common/sign.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Codesign + + - script: node build/azure-pipelines/common/sign.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll notarize-darwin $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive VSCode-darwin-$(VSCODE_ARCH).zip + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Notarize + + - script: unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) + displayName: Extract signed app + + - script: | + set -e + APP_ROOT="$(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" + codesign -dv --deep --verbose=4 "$APP_PATH" + "$APP_PATH/Contents/Resources/app/bin/code" --export-default-configuration=.build + displayName: Verify signature + + - script: | + set -e + mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive + mv $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip + displayName: Move artifact to out directory diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index a6072c8f4fa2e..770a54f7925d3 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -1,8 +1,6 @@ parameters: - name: VSCODE_ARCH type: string - - name: VSCODE_QUALITY - type: string - name: VSCODE_CIBUILD type: boolean - name: VSCODE_RUN_ELECTRON_TESTS @@ -14,326 +12,68 @@ parameters: - name: VSCODE_RUN_REMOTE_TESTS type: boolean default: false - - name: VSCODE_TEST_ARTIFACT_NAME - type: string - default: "" - -steps: - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - checkout: self - fetchDepth: 1 - retryCountOnTaskFailure: 3 - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - template: ../distro/download-distro.yml@self - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output - - - script: node build/setup-npm-registry.js $NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js darwin $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash - displayName: Prepare node_modules cache key - - - task: Cache@2 - inputs: - key: '"node_modules" | .build/packagelockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: | - set -e - c++ --version - xcode-select -print-path - python3 -m pip install setuptools - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - env: - npm_config_arch: $(VSCODE_ARCH) - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - # Avoid using dlopen to load Kerberos on macOS which can cause missing libraries - # https://github.com/mongodb-js/kerberos/commit/04044d2814ad1d01e77f1ce87f26b03d86692cf2 - # flipped the default to support legacy linux distros which shouldn't happen - # on macOS. - GYP_DEFINES: "kerberos_use_rtld=false" - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: node build/azure-pipelines/distro/mixin-npm - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Mixin distro node modules - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - - - template: ../common/install-builtin-extensions.yml@self - - - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'oss')) }}: - - script: node build/lib/policies darwin - displayName: Generate policy definitions - retryCountOnTaskFailure: 3 - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - npm run gulp vscode-darwin-$(VSCODE_ARCH)-min-ci - echo "##vso[task.setvariable variable=BUILT_CLIENT]true" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build client - - - script: | - set -e - npm run gulp vscode-reh-darwin-$(VSCODE_ARCH)-min-ci - mv ../vscode-reh-darwin-$(VSCODE_ARCH) ../vscode-server-darwin-$(VSCODE_ARCH) # TODO@joaomoreno - ARCHIVE_PATH=".build/darwin/server/vscode-server-darwin-$(VSCODE_ARCH).zip" - mkdir -p $(dirname $ARCHIVE_PATH) - (cd .. && zip -Xry $(Build.SourcesDirectory)/$ARCHIVE_PATH vscode-server-darwin-$(VSCODE_ARCH)) - echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build server - - - script: | - set -e - npm run gulp vscode-reh-web-darwin-$(VSCODE_ARCH)-min-ci - mv ../vscode-reh-web-darwin-$(VSCODE_ARCH) ../vscode-server-darwin-$(VSCODE_ARCH)-web # TODO@joaomoreno - ARCHIVE_PATH=".build/darwin/server/vscode-server-darwin-$(VSCODE_ARCH)-web.zip" - mkdir -p $(dirname $ARCHIVE_PATH) - (cd .. && zip -Xry $(Build.SourcesDirectory)/$ARCHIVE_PATH vscode-server-darwin-$(VSCODE_ARCH)-web) - echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build server (web) - - - ${{ else }}: - - script: npm run gulp transpile-client-esbuild transpile-extensions - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Transpile - - - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'oss')) }}: - - task: DownloadPipelineArtifact@2 - inputs: - artifact: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli - patterns: "**" - path: $(Build.ArtifactStagingDirectory)/cli - displayName: Download VS Code CLI - - - script: | - set -e - APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - APP_PATH="$APP_ROOT/$APP_NAME" - unzip $(Build.ArtifactStagingDirectory)/cli/*.zip -d $(Build.ArtifactStagingDirectory)/cli - CLI_APP_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").tunnelApplicationName") - APP_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").applicationName") - mv "$(Build.ArtifactStagingDirectory)/cli/$APP_NAME" "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME" - chmod +x "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME" - displayName: Make CLI executable - - - script: | - set -e - APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - APP_PATH="$APP_ROOT/$APP_NAME" node build/darwin/verify-macho.js $(VSCODE_ARCH) - APP_PATH="$(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)" node build/darwin/verify-macho.js $(VSCODE_ARCH) - displayName: Verify arch of Mach-O objects - - # Setting hardened entitlements is a requirement for: - # * Apple notarization - # * Running tests on Big Sur (because Big Sur has additional security precautions) - - script: | - set -e - security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain - security default-keychain -s $(agent.tempdirectory)/buildagent.keychain - security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain - echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 - security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign - export CODESIGN_IDENTITY=$(security find-identity -v -p codesigning $(agent.tempdirectory)/buildagent.keychain | grep -oEi "([0-9A-F]{40})" | head -n 1) - security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain - DEBUG=electron-osx-sign* node build/darwin/sign.js $(agent.builddirectory) - displayName: Set Hardened Entitlements - - - script: | - set -e - ARCHIVE_PATH="$(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip" - mkdir -p $(dirname $ARCHIVE_PATH) - (cd ../VSCode-darwin-$(VSCODE_ARCH) && zip -Xry $ARCHIVE_PATH *) - echo "##vso[task.setvariable variable=CLIENT_PATH]$ARCHIVE_PATH" - condition: and(succeededOrFailed(), eq(variables['BUILT_CLIENT'], 'true')) - displayName: Package client - - - pwsh: node build/azure-pipelines/common/checkForArtifact.js CLIENT_ARCHIVE_UPLOADED unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: Check for client artifact - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(CLIENT_PATH) - artifactName: unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive - sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH) - sbomPackageName: "VS Code macOS $(VSCODE_ARCH) (unsigned)" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeeded(), ne(variables['CLIENT_PATH'], ''), eq(variables['CLIENT_ARCHIVE_UPLOADED'], 'false')) - displayName: Publish client archive (unsigned) - - - task: UseDotNet@2 - inputs: - version: 6.x - - - task: EsrpCodeSigning@5 - inputs: - UseMSIAuthentication: true - ConnectedServiceName: vscode-esrp - AppRegistrationClientId: $(ESRP_CLIENT_ID) - AppRegistrationTenantId: $(ESRP_TENANT_ID) - AuthAKVName: vscode-esrp - AuthSignCertName: esrp-sign - FolderPath: . - Pattern: noop - displayName: 'Install ESRP Tooling' - - - pwsh: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)/_tasks | Select-Object -last 1).FullName - $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName - echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version/net6.0/esrpcli.dll" - displayName: Find ESRP CLI - - - script: npx deemon --detach --wait node build/azure-pipelines/darwin/codesign.js - env: - EsrpCliDllPath: $(EsrpCliDllPath) - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Codesign & Notarize - - - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: - - template: product-build-darwin-test.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_TEST_ARTIFACT_NAME: ${{ parameters.VSCODE_TEST_ARTIFACT_NAME }} - VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} - VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} - VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - PUBLISH_TASK_NAME: 1ES.PublishPipelineArtifact@1 - - - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'oss')) }}: - - script: npx deemon --attach node build/azure-pipelines/darwin/codesign.js - condition: succeededOrFailed() - displayName: "Post-job: ✍️ Codesign & Notarize" - - - script: unzip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) - displayName: Extract signed app - - - script: | - set -e - APP_ROOT="$(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)" - APP_NAME="`ls $APP_ROOT | head -n 1`" - APP_PATH="$APP_ROOT/$APP_NAME" - codesign -dv --deep --verbose=4 "$APP_PATH" - "$APP_PATH/Contents/Resources/app/bin/code" --export-default-configuration=.build - displayName: Verify signature - - - script: mv $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-x64.zip $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin.zip - displayName: Rename x64 build to its legacy name - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: - targetPath: $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-arm64.zip - ${{ else }}: - targetPath: $(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin.zip - artifactName: vscode_client_darwin_$(VSCODE_ARCH)_archive - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) - sbomPackageName: "VS Code macOS $(VSCODE_ARCH)" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish client archive - - - script: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" - condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) - displayName: Generate artifact prefix - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_server_darwin_$(VSCODE_ARCH)_archive-unsigned - sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH) - sbomPackageName: "VS Code macOS $(VSCODE_ARCH) Server" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], '')) - displayName: Publish server archive - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_darwin_$(VSCODE_ARCH)_archive-unsigned - sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)-web - sbomPackageName: "VS Code macOS $(VSCODE_ARCH) Web" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) - displayName: Publish web server archive +jobs: + - job: macOS_${{ parameters.VSCODE_ARCH }} + displayName: macOS (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ + templateContext: + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out + outputs: + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/crashes + artifactName: crash-dump-macos-$(VSCODE_ARCH)-$(System.JobAttempt) + displayName: "Publish Crash Reports" + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/node_modules + artifactName: node-modules-macos-$(VSCODE_ARCH)-$(System.JobAttempt) + displayName: "Publish Node Modules" + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/logs + artifactName: logs-macos-$(VSCODE_ARCH)-$(System.JobAttempt) + displayName: "Publish Log Files" + sbomEnabled: false + isProduction: false + condition: succeededOrFailed() + - output: pipelineArtifact + ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin.zip + ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-arm64.zip + artifactName: vscode_client_darwin_$(VSCODE_ARCH)_archive + displayName: Publish client archive + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) + sbomPackageName: "VS Code macOS $(VSCODE_ARCH)" + sbomPackageVersion: $(Build.SourceVersion) + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_server_darwin_$(VSCODE_ARCH)_archive/vscode-server-darwin-$(VSCODE_ARCH).zip + artifactName: vscode_server_darwin_$(VSCODE_ARCH)_archive-unsigned + displayName: Publish server archive + sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH) + sbomPackageName: "VS Code macOS $(VSCODE_ARCH) Server" + sbomPackageVersion: $(Build.SourceVersion) + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_web_darwin_$(VSCODE_ARCH)_archive/vscode-server-darwin-$(VSCODE_ARCH)-web.zip + artifactName: vscode_web_darwin_$(VSCODE_ARCH)_archive-unsigned + displayName: Publish web server archive + sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)-web + sbomPackageName: "VS Code macOS $(VSCODE_ARCH) Web" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ./steps/product-build-darwin-compile.yml@self + parameters: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }} + VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} + VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} + VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml new file mode 100644 index 0000000000000..1cd0fe2a8245f --- /dev/null +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-cli-sign.yml @@ -0,0 +1,53 @@ +parameters: + - name: VSCODE_CLI_ARTIFACTS + type: object + default: [] + +steps: + - task: UseDotNet@2 + inputs: + version: 6.x + + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' + + - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: + - task: DownloadPipelineArtifact@2 + displayName: Download ${{ target }} + inputs: + artifact: ${{ target }} + path: $(Build.ArtifactStagingDirectory)/pkg/${{ target }} + + - task: ExtractFiles@1 + displayName: Extract artifact + inputs: + archiveFilePatterns: $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/*.zip + destinationFolder: $(Build.ArtifactStagingDirectory)/sign/${{ target }} + + - script: node build/azure-pipelines/common/sign.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll sign-darwin $(Build.ArtifactStagingDirectory)/pkg "*.zip" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Codesign + + - script: node build/azure-pipelines/common/sign.ts $(Agent.RootDirectory)/_tasks/EsrpCodeSigning_*/*/net6.0/esrpcli.dll notarize-darwin $(Build.ArtifactStagingDirectory)/pkg "*.zip" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Notarize + + - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: + - script: | + set -e + ASSET_ID=$(echo "${{ target }}" | sed "s/unsigned_//") + mkdir -p $(Build.ArtifactStagingDirectory)/out/$ASSET_ID + mv $(Build.ArtifactStagingDirectory)/pkg/${{ target }}/${{ target }}.zip $(Build.ArtifactStagingDirectory)/out/$ASSET_ID/$ASSET_ID.zip + echo "##vso[task.setvariable variable=ASSET_ID]$ASSET_ID" + displayName: Set asset id variable diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml new file mode 100644 index 0000000000000..1d38413bde492 --- /dev/null +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-compile.yml @@ -0,0 +1,306 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_CIBUILD + type: boolean + - name: VSCODE_RUN_ELECTRON_TESTS + type: boolean + default: false + - name: VSCODE_RUN_BROWSER_TESTS + type: boolean + default: false + - name: VSCODE_RUN_REMOTE_TESTS + type: boolean + default: false + +steps: + - template: ../../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password,macos-developer-certificate,macos-developer-certificate-key" + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output + + - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + displayName: Extract compilation output + + - script: node build/setup-npm-registry.ts $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts darwin $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: tar -xzf .build/node_modules_cache/cache.tgz + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Extract node_modules cache + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + c++ --version + xcode-select -print-path + python3 -m pip install setuptools + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + npm_config_arch: $(VSCODE_ARCH) + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + # Avoid using dlopen to load Kerberos on macOS which can cause missing libraries + # https://github.com/mongodb-js/kerberos/commit/04044d2814ad1d01e77f1ce87f26b03d86692cf2 + # flipped the default to support legacy linux distros which shouldn't happen + # on macOS. + GYP_DEFINES: "kerberos_use_rtld=false" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: node build/azure-pipelines/distro/mixin-npm.ts + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive + + - script: node build/azure-pipelines/distro/mixin-quality.ts + displayName: Mixin distro quality + + - template: ../../common/install-builtin-extensions.yml@self + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npm run copy-policy-dto --prefix build && node build/lib/policies/policyGenerator.ts build/lib/policies/policyData.jsonc darwin + displayName: Generate policy definitions + retryCountOnTaskFailure: 3 + + - script: | + set -e + npm run gulp vscode-darwin-$(VSCODE_ARCH)-min-ci + echo "##vso[task.setvariable variable=BUILT_CLIENT]true" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build client + + - script: | + set -e + npm run gulp vscode-reh-darwin-$(VSCODE_ARCH)-min-ci + mv ../vscode-reh-darwin-$(VSCODE_ARCH) ../vscode-server-darwin-$(VSCODE_ARCH) # TODO@joaomoreno + ARCHIVE_PATH=".build/darwin/server/vscode-server-darwin-$(VSCODE_ARCH).zip" + mkdir -p $(dirname $ARCHIVE_PATH) + (cd .. && zip -Xry $(Build.SourcesDirectory)/$ARCHIVE_PATH vscode-server-darwin-$(VSCODE_ARCH)) + echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server + + - script: | + set -e + npm run gulp vscode-reh-web-darwin-$(VSCODE_ARCH)-min-ci + mv ../vscode-reh-web-darwin-$(VSCODE_ARCH) ../vscode-server-darwin-$(VSCODE_ARCH)-web # TODO@joaomoreno + ARCHIVE_PATH=".build/darwin/server/vscode-server-darwin-$(VSCODE_ARCH)-web.zip" + mkdir -p $(dirname $ARCHIVE_PATH) + (cd .. && zip -Xry $(Build.SourcesDirectory)/$ARCHIVE_PATH vscode-server-darwin-$(VSCODE_ARCH)-web) + echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server (web) + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: unsigned_vscode_cli_darwin_$(VSCODE_ARCH)_cli + patterns: "**" + path: $(Build.ArtifactStagingDirectory)/cli + displayName: Download VS Code CLI + + - script: | + set -e + APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" + unzip $(Build.ArtifactStagingDirectory)/cli/*.zip -d $(Build.ArtifactStagingDirectory)/cli + CLI_APP_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").tunnelApplicationName") + APP_NAME=$(node -p "require(\"$APP_PATH/Contents/Resources/app/product.json\").applicationName") + mv "$(Build.ArtifactStagingDirectory)/cli/$APP_NAME" "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME" + chmod +x "$APP_PATH/Contents/Resources/app/bin/$CLI_APP_NAME" + displayName: Make CLI executable + + - script: | + set -e + APP_ROOT="$(Agent.BuildDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" node build/darwin/verify-macho.ts $(VSCODE_ARCH) + APP_PATH="$(Agent.BuildDirectory)/vscode-server-darwin-$(VSCODE_ARCH)" node build/darwin/verify-macho.ts $(VSCODE_ARCH) + displayName: Verify arch of Mach-O objects + + - script: | + set -e + ARCHIVE_PATH="$(Pipeline.Workspace)/unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip" + mkdir -p $(dirname $ARCHIVE_PATH) + (cd ../VSCode-darwin-$(VSCODE_ARCH) && zip -Xry $ARCHIVE_PATH *) + echo "##vso[task.setvariable variable=CLIENT_PATH]$ARCHIVE_PATH" + condition: eq(variables['BUILT_CLIENT'], 'true') + displayName: Package client + + - pwsh: node build/azure-pipelines/common/checkForArtifact.ts CLIENT_ARCHIVE_UPLOADED unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: Check for client artifact + + # We are publishing the unsigned client artifact before running tests + # since the macOS (UNIVERSAL) job is blocked waiting for the artifact. + - template: ../../common/publish-artifact.yml@self + parameters: + targetPath: $(CLIENT_PATH) + artifactName: unsigned_vscode_client_darwin_$(VSCODE_ARCH)_archive + displayName: Publish client archive (unsigned) + sbomEnabled: false + condition: and(ne(variables['CLIENT_PATH'], ''), eq(variables['CLIENT_ARCHIVE_UPLOADED'], 'false')) + + # Hardened entitlements should be set after publishing unsigned client artifacts + # to ensure entitlement signing doesn't modify sha that would affect universal build. + # + # Setting hardened entitlements is a requirement for: + # * Apple notarization + # * Running tests on Big Sur (because Big Sur has additional security precautions) + - script: | + set -e + security create-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + security default-keychain -s $(agent.tempdirectory)/buildagent.keychain + security unlock-keychain -p pwd $(agent.tempdirectory)/buildagent.keychain + echo "$(macos-developer-certificate)" | base64 -D > $(agent.tempdirectory)/cert.p12 + security import $(agent.tempdirectory)/cert.p12 -k $(agent.tempdirectory)/buildagent.keychain -P "$(macos-developer-certificate-key)" -T /usr/bin/codesign + export CODESIGN_IDENTITY=$(security find-identity -v -p codesigning $(agent.tempdirectory)/buildagent.keychain | grep -oEi "([0-9A-F]{40})" | head -n 1) + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k pwd $(agent.tempdirectory)/buildagent.keychain + DEBUG=electron-osx-sign* node build/darwin/sign.ts $(agent.builddirectory) + displayName: Set Hardened Entitlements + + - script: | + set -e + ARCHIVE_PATH="$(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip" + mkdir -p $(dirname $ARCHIVE_PATH) + (cd ../VSCode-darwin-$(VSCODE_ARCH) && zip -Xry $ARCHIVE_PATH *) + echo "##vso[task.setvariable variable=CLIENT_PATH]$ARCHIVE_PATH" + condition: eq(variables['BUILT_CLIENT'], 'true') + displayName: Re-package client after entitlement + + - task: UseDotNet@2 + inputs: + version: 6.x + + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' + + - pwsh: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)/_tasks | Select-Object -last 1).FullName + $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName + echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version/net6.0/esrpcli.dll" + displayName: Find ESRP CLI + + - script: npx deemon --detach --wait node build/azure-pipelines/darwin/codesign.ts + env: + EsrpCliDllPath: $(EsrpCliDllPath) + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Codesign & Notarize + + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - template: product-build-darwin-test.yml@self + parameters: + VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} + VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} + VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npx deemon --attach node build/azure-pipelines/darwin/codesign.ts + condition: succeededOrFailed() + displayName: "Post-job: ✍️ Codesign & Notarize" + + - script: unzip $(Pipeline.Workspace)/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip -d $(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH) + displayName: Extract signed app + + - script: | + set -e + APP_ROOT="$(Build.ArtifactStagingDirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + APP_PATH="$APP_ROOT/$APP_NAME" + codesign -dv --deep --verbose=4 "$APP_PATH" + "$APP_PATH/Contents/Resources/app/bin/code" --export-default-configuration=.build + displayName: Verify signature + + - script: | + set -e + + mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive + if [ "$VSCODE_ARCH" == "x64" ]; then + # Use legacy name for x64 builds + mv $(CLIENT_PATH) $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin.zip + else + mv $(CLIENT_PATH) $(Build.ArtifactStagingDirectory)/out/vscode_client_darwin_$(VSCODE_ARCH)_archive/VSCode-darwin-$(VSCODE_ARCH).zip + fi + + mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_server_darwin_$(VSCODE_ARCH)_archive + mv $(SERVER_PATH) $(Build.ArtifactStagingDirectory)/out/vscode_server_darwin_$(VSCODE_ARCH)_archive/vscode-server-darwin-$(VSCODE_ARCH).zip + + mkdir -p $(Build.ArtifactStagingDirectory)/out/vscode_web_darwin_$(VSCODE_ARCH)_archive + mv $(WEB_PATH) $(Build.ArtifactStagingDirectory)/out/vscode_web_darwin_$(VSCODE_ARCH)_archive/vscode-server-darwin-$(VSCODE_ARCH)-web.zip + displayName: Move artifacts to out directory diff --git a/build/azure-pipelines/darwin/steps/product-build-darwin-test.yml b/build/azure-pipelines/darwin/steps/product-build-darwin-test.yml new file mode 100644 index 0000000000000..1facd03c6eea9 --- /dev/null +++ b/build/azure-pipelines/darwin/steps/product-build-darwin-test.yml @@ -0,0 +1,130 @@ +parameters: + - name: VSCODE_RUN_ELECTRON_TESTS + type: boolean + - name: VSCODE_RUN_BROWSER_TESTS + type: boolean + - name: VSCODE_RUN_REMOTE_TESTS + type: boolean + +steps: + - script: npm exec -- npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download Electron and Playwright + retryCountOnTaskFailure: 3 + + - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: + - script: ./scripts/test.sh --build --tfs "Unit Tests" + displayName: 🧪 Run unit tests (Electron) + timeoutInMinutes: 15 + - script: npm run test-node -- --build + displayName: 🧪 Run unit tests (node.js) + timeoutInMinutes: 15 + + - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: + - script: npm run test-browser-no-install -- --build --browser webkit --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: 🧪 Run unit tests (Browser, Webkit) + timeoutInMinutes: 30 + + - script: | + set -e + npm run gulp \ + compile-extension:configuration-editing \ + compile-extension:css-language-features-server \ + compile-extension:emmet \ + compile-extension:git \ + compile-extension:github-authentication \ + compile-extension:html-language-features-server \ + compile-extension:ipynb \ + compile-extension:notebook-renderers \ + compile-extension:json-language-features-server \ + compile-extension:markdown-language-features \ + compile-extension-media \ + compile-extension:microsoft-authentication \ + compile-extension:typescript-language-features \ + compile-extension:vscode-api-tests \ + compile-extension:vscode-colorize-tests \ + compile-extension:vscode-colorize-perf-tests \ + compile-extension:vscode-test-resolver + displayName: Build integration tests + + - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT="$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH)" + APP_NAME="`ls $APP_ROOT | head -n 1`" + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) + displayName: 🧪 Run integration tests (Electron) + timeoutInMinutes: 20 + + - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: + - script: ./scripts/test-web-integration.sh --browser webkit + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH)-web + displayName: 🧪 Run integration tests (Browser, Webkit) + timeoutInMinutes: 20 + + - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \ + ./scripts/test-remote-integration.sh + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) + displayName: 🧪 Run integration tests (Remote) + timeoutInMinutes: 20 + + - script: ps -ef + displayName: Diagnostics before smoke test run + continueOnError: true + condition: succeededOrFailed() + + - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + npm run smoketest-no-compile -- --tracing --build "$APP_ROOT/$APP_NAME" + timeoutInMinutes: 20 + displayName: 🧪 Run smoke tests (Electron) + + - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: + - script: npm run smoketest-no-compile -- --web --tracing --headless + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH)-web + timeoutInMinutes: 20 + displayName: 🧪 Run smoke tests (Browser, Chromium) + + - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: + - script: | + set -e + npm run gulp compile-extension:vscode-test-resolver + APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + APP_NAME="`ls $APP_ROOT | head -n 1`" + npm run smoketest-no-compile -- --tracing --remote --build "$APP_ROOT/$APP_NAME" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-darwin-$(VSCODE_ARCH) + timeoutInMinutes: 20 + displayName: 🧪 Run smoke tests (Remote) + + - script: ps -ef + displayName: Diagnostics after smoke test run + continueOnError: true + condition: succeededOrFailed() + + - task: PublishTestResults@2 + displayName: Publish Tests Results + inputs: + testResultsFiles: "*-results.xml" + searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" + condition: succeededOrFailed() diff --git a/build/azure-pipelines/distro-build.yml b/build/azure-pipelines/distro-build.yml index ae11345bb6db6..1d0a50b129778 100644 --- a/build/azure-pipelines/distro-build.yml +++ b/build/azure-pipelines/distro-build.yml @@ -12,5 +12,4 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - template: ./distro/download-distro.yml@self diff --git a/build/azure-pipelines/distro/mixin-npm.js b/build/azure-pipelines/distro/mixin-npm.js deleted file mode 100644 index 87958a5d44902..0000000000000 --- a/build/azure-pipelines/distro/mixin-npm.js +++ /dev/null @@ -1,38 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const { dirs } = require('../../npm/dirs'); -function log(...args) { - console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); -} -function mixin(mixinPath) { - if (!fs_1.default.existsSync(`${mixinPath}/node_modules`)) { - log(`Skipping distro npm dependencies: ${mixinPath} (no node_modules)`); - return; - } - log(`Mixing in distro npm dependencies: ${mixinPath}`); - const distroPackageJson = JSON.parse(fs_1.default.readFileSync(`${mixinPath}/package.json`, 'utf8')); - const targetPath = path_1.default.relative('.build/distro/npm', mixinPath); - for (const dependency of Object.keys(distroPackageJson.dependencies)) { - fs_1.default.rmSync(`./${targetPath}/node_modules/${dependency}`, { recursive: true, force: true }); - fs_1.default.cpSync(`${mixinPath}/node_modules/${dependency}`, `./${targetPath}/node_modules/${dependency}`, { recursive: true, force: true, dereference: true }); - } - log(`Mixed in distro npm dependencies: ${mixinPath} ✔︎`); -} -function main() { - log(`Mixing in distro npm dependencies...`); - const mixinPaths = dirs.filter(d => /^.build\/distro\/npm/.test(d)); - for (const mixinPath of mixinPaths) { - mixin(mixinPath); - } -} -main(); -//# sourceMappingURL=mixin-npm.js.map \ No newline at end of file diff --git a/build/azure-pipelines/distro/mixin-npm.ts b/build/azure-pipelines/distro/mixin-npm.ts index 6e32f10db5086..d5caa0a950274 100644 --- a/build/azure-pipelines/distro/mixin-npm.ts +++ b/build/azure-pipelines/distro/mixin-npm.ts @@ -5,9 +5,9 @@ import fs from 'fs'; import path from 'path'; -const { dirs } = require('../../npm/dirs') as { dirs: string[] }; +import { dirs } from '../../npm/dirs.ts'; -function log(...args: any[]): void { +function log(...args: unknown[]): void { console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); } diff --git a/build/azure-pipelines/distro/mixin-quality.js b/build/azure-pipelines/distro/mixin-quality.js deleted file mode 100644 index 335f63ca1fc3e..0000000000000 --- a/build/azure-pipelines/distro/mixin-quality.js +++ /dev/null @@ -1,56 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -function log(...args) { - console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); -} -function main() { - const quality = process.env['VSCODE_QUALITY']; - if (!quality) { - throw new Error('Missing VSCODE_QUALITY, skipping mixin'); - } - log(`Mixing in distro quality...`); - const basePath = `.build/distro/mixin/${quality}`; - for (const name of fs_1.default.readdirSync(basePath)) { - const distroPath = path_1.default.join(basePath, name); - const ossPath = path_1.default.relative(basePath, distroPath); - if (ossPath === 'product.json') { - const distro = JSON.parse(fs_1.default.readFileSync(distroPath, 'utf8')); - const oss = JSON.parse(fs_1.default.readFileSync(ossPath, 'utf8')); - let builtInExtensions = oss.builtInExtensions; - if (Array.isArray(distro.builtInExtensions)) { - log('Overwriting built-in extensions:', distro.builtInExtensions.map(e => e.name)); - builtInExtensions = distro.builtInExtensions; - } - else if (distro.builtInExtensions) { - const include = distro.builtInExtensions['include'] ?? []; - const exclude = distro.builtInExtensions['exclude'] ?? []; - log('OSS built-in extensions:', builtInExtensions.map(e => e.name)); - log('Including built-in extensions:', include.map(e => e.name)); - log('Excluding built-in extensions:', exclude); - builtInExtensions = builtInExtensions.filter(ext => !include.find(e => e.name === ext.name) && !exclude.find(name => name === ext.name)); - builtInExtensions = [...builtInExtensions, ...include]; - log('Final built-in extensions:', builtInExtensions.map(e => e.name)); - } - else { - log('Inheriting OSS built-in extensions', builtInExtensions.map(e => e.name)); - } - const result = { webBuiltInExtensions: oss.webBuiltInExtensions, ...distro, builtInExtensions }; - fs_1.default.writeFileSync(ossPath, JSON.stringify(result, null, '\t'), 'utf8'); - } - else { - fs_1.default.cpSync(distroPath, ossPath, { force: true, recursive: true }); - } - log(distroPath, '✔︎'); - } -} -main(); -//# sourceMappingURL=mixin-quality.js.map \ No newline at end of file diff --git a/build/azure-pipelines/distro/mixin-quality.ts b/build/azure-pipelines/distro/mixin-quality.ts index 29c90f00a65d9..c8ed6886b798e 100644 --- a/build/azure-pipelines/distro/mixin-quality.ts +++ b/build/azure-pipelines/distro/mixin-quality.ts @@ -23,7 +23,7 @@ interface Product { readonly webBuiltInExtensions?: IBuiltInExtension[]; } -function log(...args: any[]): void { +function log(...args: unknown[]): void { console.log(`[${new Date().toLocaleTimeString('en', { hour12: false })}]`, '[distro]', ...args); } diff --git a/build/azure-pipelines/linux/cli-build-linux.yml b/build/azure-pipelines/linux/cli-build-linux.yml deleted file mode 100644 index dba949395de3d..0000000000000 --- a/build/azure-pipelines/linux/cli-build-linux.yml +++ /dev/null @@ -1,159 +0,0 @@ -parameters: - - name: VSCODE_BUILD_LINUX - type: boolean - default: false - - name: VSCODE_BUILD_LINUX_ARM64 - type: boolean - default: false - - name: VSCODE_BUILD_LINUX_ARMHF - type: boolean - default: false - - name: VSCODE_CHECK_ONLY - type: boolean - default: false - - name: VSCODE_QUALITY - type: string - -steps: - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - template: ../cli/cli-apply-patches.yml@self - - - task: Npm@1 - displayName: Download openssl prebuilt - inputs: - command: custom - customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 - customRegistry: useFeed - customFeed: "Monaco/openssl-prebuilt" - workingDir: $(Build.ArtifactStagingDirectory) - - - script: | - set -e - mkdir $(Build.ArtifactStagingDirectory)/openssl - tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl - displayName: Extract openssl prebuilt - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: node build/setup-npm-registry.js $NPM_REGISTRY build - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: | - set -e - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies - - - script: | - set -e - mkdir -p $(Build.SourcesDirectory)/.build - displayName: Create .build folder for misc dependencies - - - template: ../cli/install-rust-posix.yml@self - parameters: - targets: - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: - - aarch64-unknown-linux-gnu - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - x86_64-unknown-linux-gnu - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true) }}: - - armv7-unknown-linux-gnueabihf - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_TARGET: aarch64-unknown-linux-gnu - VSCODE_CLI_ARTIFACT: vscode_cli_linux_arm64_cli - VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux/include - SYSROOT_ARCH: arm64 - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_TARGET: x86_64-unknown-linux-gnu - VSCODE_CLI_ARTIFACT: vscode_cli_linux_x64_cli - VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux/include - SYSROOT_ARCH: amd64 - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_TARGET: armv7-unknown-linux-gnueabihf - VSCODE_CLI_ARTIFACT: vscode_cli_linux_armhf_cli - VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm-linux/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm-linux/include - SYSROOT_ARCH: armhf - - - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true) }}: - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_linux_armhf_cli.tar.gz - artifactName: vscode_cli_linux_armhf_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Linux armhf CLI" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish vscode_cli_linux_armhf_cli artifact - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_linux_x64_cli.tar.gz - artifactName: vscode_cli_linux_x64_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Linux x64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish vscode_cli_linux_x64_cli artifact - - - ${{ if eq(parameters.VSCODE_BUILD_LINUX_ARM64, true) }}: - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_linux_arm64_cli.tar.gz - artifactName: vscode_cli_linux_arm64_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Linux arm64 CLI" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish vscode_cli_linux_arm64_cli artifact diff --git a/build/azure-pipelines/linux/codesign.js b/build/azure-pipelines/linux/codesign.js deleted file mode 100644 index 98b97db566604..0000000000000 --- a/build/azure-pipelines/linux/codesign.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const codesign_1 = require("../common/codesign"); -const publish_1 = require("../common/publish"); -async function main() { - const esrpCliDLLPath = (0, publish_1.e)('EsrpCliDllPath'); - // Start the code sign processes in parallel - // 1. Codesign deb package - // 2. Codesign rpm package - const codesignTask1 = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-pgp', '.build/linux/deb', '*.deb'); - const codesignTask2 = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-pgp', '.build/linux/rpm', '*.rpm'); - // Codesign deb package - (0, codesign_1.printBanner)('Codesign deb package'); - await (0, codesign_1.streamProcessOutputAndCheckResult)('Codesign deb package', codesignTask1); - // Codesign rpm package - (0, codesign_1.printBanner)('Codesign rpm package'); - await (0, codesign_1.streamProcessOutputAndCheckResult)('Codesign rpm package', codesignTask2); -} -main().then(() => { - process.exit(0); -}, err => { - console.error(`ERROR: ${err}`); - process.exit(1); -}); -//# sourceMappingURL=codesign.js.map \ No newline at end of file diff --git a/build/azure-pipelines/linux/codesign.ts b/build/azure-pipelines/linux/codesign.ts index 1f74cc21ee984..67a34d9e7a118 100644 --- a/build/azure-pipelines/linux/codesign.ts +++ b/build/azure-pipelines/linux/codesign.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { printBanner, spawnCodesignProcess, streamProcessOutputAndCheckResult } from '../common/codesign'; -import { e } from '../common/publish'; +import { printBanner, spawnCodesignProcess, streamProcessOutputAndCheckResult } from '../common/codesign.ts'; +import { e } from '../common/publish.ts'; async function main() { const esrpCliDLLPath = e('EsrpCliDllPath'); diff --git a/build/azure-pipelines/linux/product-build-linux-ci.yml b/build/azure-pipelines/linux/product-build-linux-ci.yml new file mode 100644 index 0000000000000..6c6b102891a7e --- /dev/null +++ b/build/azure-pipelines/linux/product-build-linux-ci.yml @@ -0,0 +1,51 @@ +parameters: + - name: VSCODE_CIBUILD + type: boolean + - name: VSCODE_QUALITY + type: string + - name: VSCODE_TEST_SUITE + type: string + +jobs: + - job: Linux${{ parameters.VSCODE_TEST_SUITE }} + displayName: ${{ parameters.VSCODE_TEST_SUITE }} Tests + timeoutInMinutes: 30 + variables: + DISPLAY: ":10" + NPM_ARCH: x64 + VSCODE_ARCH: x64 + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/crashes + artifactName: crash-dump-linux-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) + displayName: Publish Crash Reports + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/node_modules + artifactName: node-modules-linux-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) + displayName: Publish Node Modules + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/logs + artifactName: logs-linux-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) + displayName: Publish Log Files + sbomEnabled: false + isProduction: false + condition: succeededOrFailed() + steps: + - template: ./steps/product-build-linux-compile.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Electron') }}: + VSCODE_RUN_ELECTRON_TESTS: true + ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Browser') }}: + VSCODE_RUN_BROWSER_TESTS: true + ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Remote') }}: + VSCODE_RUN_REMOTE_TESTS: true diff --git a/build/azure-pipelines/linux/product-build-linux-cli.yml b/build/azure-pipelines/linux/product-build-linux-cli.yml new file mode 100644 index 0000000000000..ef160c2cc3849 --- /dev/null +++ b/build/azure-pipelines/linux/product-build-linux-cli.yml @@ -0,0 +1,139 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_CHECK_ONLY + type: boolean + default: false + - name: VSCODE_QUALITY + type: string + +jobs: + - job: LinuxCLI_${{ parameters.VSCODE_ARCH }} + displayName: Linux (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 60 + pool: + name: 1es-ubuntu-22.04-x64 + os: linux + variables: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + templateContext: + outputs: + - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/vscode_cli_linux_$(VSCODE_ARCH)_cli.tar.gz + artifactName: vscode_cli_linux_$(VSCODE_ARCH)_cli + displayName: Publish vscode_cli_linux_$(VSCODE_ARCH)_cli artifact + sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli + sbomPackageName: "VS Code Linux $(VSCODE_ARCH) CLI" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../cli/cli-apply-patches.yml@self + + - task: Npm@1 + displayName: Download openssl prebuilt + inputs: + command: custom + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 + customRegistry: useFeed + customFeed: "Monaco/openssl-prebuilt" + workingDir: $(Build.ArtifactStagingDirectory) + + - script: | + set -e + mkdir $(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + displayName: Extract openssl prebuilt + + - script: node build/setup-npm-registry.ts $NPM_REGISTRY build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies + + - script: | + set -e + mkdir -p $(Build.SourcesDirectory)/.build + displayName: Create .build folder for misc dependencies + + - template: ../cli/install-rust-posix.yml@self + parameters: + targets: + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - x86_64-unknown-linux-gnu + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - aarch64-unknown-linux-gnu + - ${{ if eq(parameters.VSCODE_ARCH, 'armhf') }}: + - armv7-unknown-linux-gnueabihf + + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_TARGET: x86_64-unknown-linux-gnu + VSCODE_CLI_ARTIFACT: vscode_cli_linux_x64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-linux/include + SYSROOT_ARCH: amd64 + + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_TARGET: aarch64-unknown-linux-gnu + VSCODE_CLI_ARTIFACT: vscode_cli_linux_arm64_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-linux/include + SYSROOT_ARCH: arm64 + + - ${{ if eq(parameters.VSCODE_ARCH, 'armhf') }}: + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_CLI_TARGET: armv7-unknown-linux-gnueabihf + VSCODE_CLI_ARTIFACT: vscode_cli_linux_armhf_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm-linux/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm-linux/include + SYSROOT_ARCH: armhf diff --git a/build/azure-pipelines/linux/product-build-linux-node-modules.yml b/build/azure-pipelines/linux/product-build-linux-node-modules.yml new file mode 100644 index 0000000000000..290a3fe1b29ed --- /dev/null +++ b/build/azure-pipelines/linux/product-build-linux-node-modules.yml @@ -0,0 +1,150 @@ +parameters: + - name: NPM_ARCH + type: string + - name: VSCODE_ARCH + type: string + +jobs: + - job: LinuxNodeModules_${{ parameters.VSCODE_ARCH }} + displayName: Linux (${{ upper(parameters.VSCODE_ARCH) }}) + pool: + name: 1es-ubuntu-22.04-x64 + os: linux + timeoutInMinutes: 60 + variables: + NPM_ARCH: ${{ parameters.NPM_ARCH }} + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - script: | + set -e + # Start X server + ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get update + ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get install -y pkg-config \ + xvfb \ + libgtk-3-0 \ + libxkbfile-dev \ + libkrb5-dev \ + libgbm1 \ + rpm + sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + sudo chmod +x /etc/init.d/xvfb + sudo update-rc.d xvfb defaults + sudo service xvfb start + displayName: Setup system services + + - script: node build/setup-npm-registry.ts $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts linux $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + # Step will be used by both verify glibcxx version for remote server and building rpm package, + # hence avoid adding it behind NODE_MODULES_RESTORED condition. + - script: | + set -e + SYSROOT_ARCH=$VSCODE_ARCH + if [ "$SYSROOT_ARCH" == "x64" ]; then + SYSROOT_ARCH="amd64" + fi + export VSCODE_SYSROOT_DIR=$(Build.SourcesDirectory)/.build/sysroots/glibc-2.28-gcc-8.5.0 + SYSROOT_ARCH="$SYSROOT_ARCH" VSCODE_SYSROOT_PREFIX="-glibc-2.28-gcc-8.5.0" node -e 'import { getVSCodeSysroot } from "./build/linux/debian/install-sysroot.ts"; (async () => { await getVSCodeSysroot(process.env["SYSROOT_ARCH"]); })()' + env: + VSCODE_ARCH: $(VSCODE_ARCH) + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download vscode sysroots + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + + source ./build/azure-pipelines/linux/setup-env.sh + + # Run preinstall script before root dependencies are installed + # so that v8 headers are patched correctly for native modules. + node build/npm/preinstall.ts + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + npm_config_arch: $(NPM_ARCH) + VSCODE_ARCH: $(VSCODE_ARCH) + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: node build/azure-pipelines/distro/mixin-npm.ts + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive diff --git a/build/azure-pipelines/linux/product-build-linux-test.yml b/build/azure-pipelines/linux/product-build-linux-test.yml deleted file mode 100644 index 7e9325354a352..0000000000000 --- a/build/azure-pipelines/linux/product-build-linux-test.yml +++ /dev/null @@ -1,254 +0,0 @@ -parameters: - - name: VSCODE_QUALITY - type: string - - name: VSCODE_RUN_ELECTRON_TESTS - type: boolean - - name: VSCODE_RUN_BROWSER_TESTS - type: boolean - - name: VSCODE_RUN_REMOTE_TESTS - type: boolean - - name: VSCODE_TEST_ARTIFACT_NAME - type: string - - name: PUBLISH_TASK_NAME - type: string - default: PublishPipelineArtifact@0 - -steps: - - script: npm exec -- npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Download Electron and Playwright - retryCountOnTaskFailure: 3 - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - ELECTRON_ROOT=.build/electron - sudo chown root $APP_ROOT/chrome-sandbox - sudo chown root $ELECTRON_ROOT/chrome-sandbox - sudo chmod 4755 $APP_ROOT/chrome-sandbox - sudo chmod 4755 $ELECTRON_ROOT/chrome-sandbox - stat $APP_ROOT/chrome-sandbox - stat $ELECTRON_ROOT/chrome-sandbox - displayName: Change setuid helper binary permission - - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - script: ./scripts/test.sh --tfs "Unit Tests" - env: - DISPLAY: ":10" - displayName: 🧪 Run unit tests (Electron) - timeoutInMinutes: 15 - - script: npm run test-node - displayName: 🧪 Run unit tests (node.js) - timeoutInMinutes: 15 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - script: npm run test-browser-no-install -- --browser chromium --tfs "Browser Unit Tests" - env: - DEBUG: "*browser*" - displayName: 🧪 Run unit tests (Browser, Chromium) - timeoutInMinutes: 15 - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - script: ./scripts/test.sh --build --tfs "Unit Tests" - displayName: 🧪 Run unit tests (Electron) - timeoutInMinutes: 15 - - script: npm run test-node -- --build - displayName: 🧪 Run unit tests (node.js) - timeoutInMinutes: 15 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - script: npm run test-browser-no-install -- --build --browser chromium --tfs "Browser Unit Tests" - env: - DEBUG: "*browser*" - displayName: 🧪 Run unit tests (Browser, Chromium) - timeoutInMinutes: 15 - - - script: | - set -e - npm run gulp \ - compile-extension:configuration-editing \ - compile-extension:css-language-features-server \ - compile-extension:emmet \ - compile-extension:git \ - compile-extension:github-authentication \ - compile-extension:html-language-features-server \ - compile-extension:ipynb \ - compile-extension:notebook-renderers \ - compile-extension:json-language-features-server \ - compile-extension:markdown-language-features \ - compile-extension-media \ - compile-extension:microsoft-authentication \ - compile-extension:typescript-language-features \ - compile-extension:vscode-api-tests \ - compile-extension:vscode-colorize-tests \ - compile-extension:vscode-colorize-perf-tests \ - compile-extension:vscode-test-resolver - displayName: Build integration tests - - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - script: ./scripts/test-integration.sh --tfs "Integration Tests" - env: - DISPLAY: ":10" - displayName: 🧪 Run integration tests (Electron) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - script: ./scripts/test-web-integration.sh --browser chromium - displayName: 🧪 Run integration tests (Browser, Chromium) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: - - script: ./scripts/test-remote-integration.sh - displayName: 🧪 Run integration tests (Remote) - timeoutInMinutes: 20 - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - script: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_APP_NAME="$APP_NAME" \ - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - ./scripts/test-integration.sh --build --tfs "Integration Tests" - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) - displayName: 🧪 Run integration tests (Electron) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - script: ./scripts/test-web-integration.sh --browser chromium - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH)-web - displayName: 🧪 Run integration tests (Browser, Chromium) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: - - script: | - set -e - APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") - INTEGRATION_TEST_APP_NAME="$APP_NAME" \ - INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ - ./scripts/test-remote-integration.sh - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) - displayName: 🧪 Run integration tests (Remote) - timeoutInMinutes: 20 - - - script: | - set -e - ps -ef - cat /proc/sys/fs/inotify/max_user_watches - lsof | wc -l - displayName: Diagnostics before smoke test run (processes, max_user_watches, number of opened file handles) - continueOnError: true - condition: succeededOrFailed() - - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: npm run compile - workingDirectory: test/smoke - displayName: Compile smoke tests - - - script: npm run gulp node - displayName: Download node.js for remote smoke tests - retryCountOnTaskFailure: 3 - - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - script: npm run smoketest-no-compile -- --tracing - timeoutInMinutes: 20 - displayName: 🧪 Run smoke tests (Electron) - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - script: npm run smoketest-no-compile -- --web --tracing --headless - timeoutInMinutes: 20 - displayName: 🧪 Run smoke tests (Browser, Chromium) - - - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: - - script: npm run smoketest-no-compile -- --remote --tracing - timeoutInMinutes: 20 - displayName: 🧪 Run smoke tests (Remote) - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - script: npm run smoketest-no-compile -- --tracing --build "$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)" - timeoutInMinutes: 20 - displayName: 🧪 Run smoke tests (Electron) - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - script: npm run smoketest-no-compile -- --web --tracing --headless - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH)-web - timeoutInMinutes: 20 - displayName: 🧪 Run smoke tests (Browser, Chromium) - - - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: - - script: | - set -e - APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH)" \ - npm run smoketest-no-compile -- --tracing --remote --build "$APP_PATH" - timeoutInMinutes: 20 - displayName: 🧪 Run smoke tests (Remote) - - - script: | - set -e - ps -ef - cat /proc/sys/fs/inotify/max_user_watches - lsof | wc -l - displayName: Diagnostics after smoke test run (processes, max_user_watches, number of opened file handles) - continueOnError: true - condition: succeededOrFailed() - - - task: ${{ parameters.PUBLISH_TASK_NAME }} - inputs: - targetPath: .build/crashes - ${{ if eq(parameters.VSCODE_TEST_ARTIFACT_NAME, '') }}: - artifactName: crash-dump-linux-$(VSCODE_ARCH)-$(System.JobAttempt) - ${{ else }}: - artifactName: crash-dump-linux-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) - sbomEnabled: false - displayName: "Publish Crash Reports" - continueOnError: true - condition: failed() - - # In order to properly symbolify above crash reports - # (if any), we need the compiled native modules too - - task: ${{ parameters.PUBLISH_TASK_NAME }} - inputs: - targetPath: node_modules - ${{ if eq(parameters.VSCODE_TEST_ARTIFACT_NAME, '') }}: - artifactName: node-modules-linux-$(VSCODE_ARCH)-$(System.JobAttempt) - ${{ else }}: - artifactName: node-modules-linux-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) - sbomEnabled: false - displayName: "Publish Node Modules" - continueOnError: true - condition: failed() - - - task: ${{ parameters.PUBLISH_TASK_NAME }} - inputs: - targetPath: .build/logs - ${{ if eq(parameters.VSCODE_TEST_ARTIFACT_NAME, '') }}: - artifactName: logs-linux-$(VSCODE_ARCH)-$(System.JobAttempt) - ${{ else }}: - artifactName: logs-linux-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) - sbomEnabled: false - displayName: "Publish Log Files" - continueOnError: true - condition: succeededOrFailed() - - - task: PublishTestResults@2 - displayName: Publish Tests Results - inputs: - testResultsFiles: "*-results.xml" - searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" - condition: succeededOrFailed() diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index dcf3964e0567c..31eb7c3d46668 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -1,10 +1,15 @@ parameters: - - name: VSCODE_QUALITY + - name: NPM_ARCH type: string - name: VSCODE_ARCH type: string - name: VSCODE_CIBUILD type: boolean + - name: VSCODE_QUALITY + type: string + - name: VSCODE_BUILD_LINUX_SNAP + type: boolean + default: false - name: VSCODE_RUN_ELECTRON_TESTS type: boolean default: false @@ -14,433 +19,94 @@ parameters: - name: VSCODE_RUN_REMOTE_TESTS type: boolean default: false - - name: VSCODE_TEST_ARTIFACT_NAME - type: string - default: "" - -steps: - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - checkout: self - fetchDepth: 1 - retryCountOnTaskFailure: 3 - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - template: ../distro/download-distro.yml@self - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output - - - script: | - set -e - # Start X server - ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get update - ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get install -y pkg-config \ - xvfb \ - libgtk-3-0 \ - libxkbfile-dev \ - libkrb5-dev \ - libgbm1 \ - rpm - sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb - sudo chmod +x /etc/init.d/xvfb - sudo update-rc.d xvfb defaults - sudo service xvfb start - displayName: Setup system services - - - script: node build/setup-npm-registry.js $NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js linux $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash - displayName: Prepare node_modules cache key - - - task: Cache@2 - inputs: - key: '"node_modules" | .build/packagelockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: | - set -e - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - # Step will be used by both verify glibcxx version for remote server and building rpm package, - # hence avoid adding it behind NODE_MODULES_RESTORED condition. - - script: | - set -e - SYSROOT_ARCH=$VSCODE_ARCH - if [ "$SYSROOT_ARCH" == "x64" ]; then - SYSROOT_ARCH="amd64" - fi - export VSCODE_SYSROOT_DIR=$(Build.SourcesDirectory)/.build/sysroots/glibc-2.28-gcc-8.5.0 - SYSROOT_ARCH="$SYSROOT_ARCH" VSCODE_SYSROOT_PREFIX="-glibc-2.28-gcc-8.5.0" node -e '(async () => { const { getVSCodeSysroot } = require("./build/linux/debian/install-sysroot.js"); await getVSCodeSysroot(process.env["SYSROOT_ARCH"]); })()' - env: - VSCODE_ARCH: $(VSCODE_ARCH) - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Download vscode sysroots - - - script: | - set -e - - source ./build/azure-pipelines/linux/setup-env.sh - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - env: - npm_config_arch: $(NPM_ARCH) - VSCODE_ARCH: $(VSCODE_ARCH) - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: node build/azure-pipelines/distro/mixin-npm - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Mixin distro node modules - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - - - template: ../common/install-builtin-extensions.yml@self - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - npm run gulp vscode-linux-$(VSCODE_ARCH)-min-ci - ARCHIVE_PATH=".build/linux/client/code-${{ parameters.VSCODE_QUALITY }}-$(VSCODE_ARCH)-$(date +%s).tar.gz" - mkdir -p $(dirname $ARCHIVE_PATH) - echo "##vso[task.setvariable variable=CLIENT_PATH]$ARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build client - - - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - task: DownloadPipelineArtifact@2 - inputs: - artifact: $(ARTIFACT_PREFIX)vscode_cli_linux_$(VSCODE_ARCH)_cli - patterns: "**" - path: $(Build.ArtifactStagingDirectory)/cli - displayName: Download VS Code CLI - - - script: | - set -e - tar -xzvf $(Build.ArtifactStagingDirectory)/cli/*.tar.gz -C $(Build.ArtifactStagingDirectory)/cli - CLI_APP_NAME=$(node -p "require(\"$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/resources/app/product.json\").tunnelApplicationName") - APP_NAME=$(node -p "require(\"$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/resources/app/product.json\").applicationName") - mv $(Build.ArtifactStagingDirectory)/cli/$APP_NAME $(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/bin/$CLI_APP_NAME - displayName: Mix in CLI - - - script: | - set -e - tar -czf $CLIENT_PATH -C .. VSCode-linux-$(VSCODE_ARCH) - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Archive client - - - script: | - set -e - npm run gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci - mv ../vscode-reh-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH) # TODO@joaomoreno - ARCHIVE_PATH=".build/linux/server/vscode-server-linux-$(VSCODE_ARCH).tar.gz" - UNARCHIVE_PATH="`pwd`/../vscode-server-linux-$(VSCODE_ARCH)" - mkdir -p $(dirname $ARCHIVE_PATH) - tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-linux-$(VSCODE_ARCH) - echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH" - echo "##vso[task.setvariable variable=SERVER_UNARCHIVE_PATH]$UNARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build server - - - script: | - set -e - npm run gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci - mv ../vscode-reh-web-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH)-web # TODO@joaomoreno - ARCHIVE_PATH=".build/linux/web/vscode-server-linux-$(VSCODE_ARCH)-web.tar.gz" - mkdir -p $(dirname $ARCHIVE_PATH) - tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-linux-$(VSCODE_ARCH)-web - echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build server (web) - - - ${{ if or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: - - script: | - set -e - - EXPECTED_GLIBC_VERSION="2.28" \ - EXPECTED_GLIBCXX_VERSION="3.4.25" \ - VSCODE_SYSROOT_DIR="$(Build.SourcesDirectory)/.build/sysroots/glibc-2.28-gcc-8.5.0" \ - ./build/azure-pipelines/linux/verify-glibc-requirements.sh - env: - SEARCH_PATH: $(SERVER_UNARCHIVE_PATH) - npm_config_arch: $(NPM_ARCH) - VSCODE_ARCH: $(VSCODE_ARCH) - displayName: Check GLIBC and GLIBCXX dependencies in server archive - - - ${{ else }}: - - script: | - set -e - - EXPECTED_GLIBC_VERSION="2.28" \ - EXPECTED_GLIBCXX_VERSION="3.4.26" \ - VSCODE_SYSROOT_DIR="$(Build.SourcesDirectory)/.build/sysroots/glibc-2.28-gcc-8.5.0" \ - ./build/azure-pipelines/linux/verify-glibc-requirements.sh - env: - SEARCH_PATH: $(SERVER_UNARCHIVE_PATH) - npm_config_arch: $(NPM_ARCH) - VSCODE_ARCH: $(VSCODE_ARCH) - displayName: Check GLIBC and GLIBCXX dependencies in server archive - - - ${{ else }}: - - script: npm run gulp "transpile-client-esbuild" "transpile-extensions" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Transpile client and extensions - - - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'oss')) }}: - - script: | - set -e - npm run gulp "vscode-linux-$(VSCODE_ARCH)-prepare-deb" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Prepare deb package - - - script: | - set -e - npm run gulp "vscode-linux-$(VSCODE_ARCH)-build-deb" - file_output=$(file $(ls .build/linux/deb/*/deb/*.deb)) - if [[ "$file_output" != *"data compression xz"* ]]; then - echo "Error: unknown compression. $file_output" - exit 1 - fi - echo "##vso[task.setvariable variable=DEB_PATH]$(ls .build/linux/deb/*/deb/*.deb)" - displayName: Build deb package - - - script: | - set -e - TRIPLE="" - if [ "$VSCODE_ARCH" == "x64" ]; then - TRIPLE="x86_64-linux-gnu" - elif [ "$VSCODE_ARCH" == "arm64" ]; then - TRIPLE="aarch64-linux-gnu" - elif [ "$VSCODE_ARCH" == "armhf" ]; then - TRIPLE="arm-rpi-linux-gnueabihf" - fi - export VSCODE_SYSROOT_DIR=$(Build.SourcesDirectory)/.build/sysroots/glibc-2.28-gcc-10.5.0 - export STRIP="$VSCODE_SYSROOT_DIR/$TRIPLE/$TRIPLE/bin/strip" - npm run gulp "vscode-linux-$(VSCODE_ARCH)-prepare-rpm" - env: - VSCODE_ARCH: $(VSCODE_ARCH) - displayName: Prepare rpm package - - - script: | - set -e - npm run gulp "vscode-linux-$(VSCODE_ARCH)-build-rpm" - echo "##vso[task.setvariable variable=RPM_PATH]$(ls .build/linux/rpm/*/*.rpm)" - displayName: Build rpm package - - - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: - - task: Docker@1 - inputs: - azureSubscriptionEndpoint: vscode - azureContainerRegistry: vscodehub.azurecr.io - command: login - displayName: Login to Container Registry - - - script: | - set -e - npm run gulp "vscode-linux-$(VSCODE_ARCH)-prepare-snap" - sudo -E docker run -e VSCODE_ARCH -e VSCODE_QUALITY -v $(pwd):/work -w /work vscodehub.azurecr.io/vscode-linux-build-agent:snapcraft-x64 /bin/bash -c "./build/azure-pipelines/linux/build-snap.sh" - - SNAP_ROOT="$(pwd)/.build/linux/snap/$(VSCODE_ARCH)" - SNAP_EXTRACTED_PATH=$(find $SNAP_ROOT -maxdepth 1 -type d -name 'code-*') - SNAP_PATH=$(find $SNAP_ROOT -maxdepth 1 -type f -name '*.snap') - - # SBOM tool doesn't like recursive symlinks - sudo find $SNAP_EXTRACTED_PATH -type l -delete - - echo "##vso[task.setvariable variable=SNAP_EXTRACTED_PATH]$SNAP_EXTRACTED_PATH" - echo "##vso[task.setvariable variable=SNAP_PATH]$SNAP_PATH" - env: - VSCODE_ARCH: $(VSCODE_ARCH) - displayName: Build snap package - - - task: UseDotNet@2 - inputs: - version: 6.x - - - task: EsrpCodeSigning@5 - inputs: - UseMSIAuthentication: true - ConnectedServiceName: vscode-esrp - AppRegistrationClientId: $(ESRP_CLIENT_ID) - AppRegistrationTenantId: $(ESRP_TENANT_ID) - AuthAKVName: vscode-esrp - AuthSignCertName: esrp-sign - FolderPath: . - Pattern: noop - displayName: 'Install ESRP Tooling' - - - pwsh: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)/_tasks | Select-Object -last 1).FullName - $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName - echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version/net6.0/esrpcli.dll" - displayName: Find ESRP CLI - - - script: npx deemon --detach --wait node build/azure-pipelines/linux/codesign.js - env: - EsrpCliDllPath: $(EsrpCliDllPath) - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: ✍️ Codesign deb & rpm - - - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: - - template: product-build-linux-test.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} - VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} - VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} - VSCODE_TEST_ARTIFACT_NAME: ${{ parameters.VSCODE_TEST_ARTIFACT_NAME }} - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - PUBLISH_TASK_NAME: 1ES.PublishPipelineArtifact@1 - - - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'oss')) }}: - - script: npx deemon --attach node build/azure-pipelines/linux/codesign.js - condition: succeededOrFailed() - displayName: "✍️ Post-job: Codesign deb & rpm" - - - script: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" - condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) - displayName: Generate artifact prefix - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(CLIENT_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_client_linux_$(VSCODE_ARCH)_archive-unsigned - sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH) - sbomPackageName: "VS Code Linux $(VSCODE_ARCH) (unsigned)" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['CLIENT_PATH'], '')) - displayName: Publish client archive - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_server_linux_$(VSCODE_ARCH)_archive-unsigned - sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-linux-$(VSCODE_ARCH) - sbomPackageName: "VS Code Linux $(VSCODE_ARCH) Server" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], '')) - displayName: Publish server archive - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_linux_$(VSCODE_ARCH)_archive-unsigned - sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-linux-$(VSCODE_ARCH)-web - sbomPackageName: "VS Code Linux $(VSCODE_ARCH) Web" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) - displayName: Publish web server archive - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(DEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_client_linux_$(VSCODE_ARCH)_deb-package - sbomBuildDropPath: .build/linux/deb - sbomPackageName: "VS Code Linux $(VSCODE_ARCH) DEB" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['DEB_PATH'], '')) - displayName: Publish deb package - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(RPM_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_client_linux_$(VSCODE_ARCH)_rpm-package - sbomBuildDropPath: .build/linux/rpm - sbomPackageName: "VS Code Linux $(VSCODE_ARCH) RPM" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['RPM_PATH'], '')) - displayName: Publish rpm package - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(SNAP_PATH) - artifactName: vscode_client_linux_$(VSCODE_ARCH)_snap - sbomBuildDropPath: $(SNAP_EXTRACTED_PATH) - sbomPackageName: "VS Code Linux $(VSCODE_ARCH) SNAP" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['SNAP_PATH'], '')) - displayName: Publish snap package +jobs: + - job: Linux_${{ parameters.VSCODE_ARCH }} + displayName: Linux (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 90 + variables: + DISPLAY: ":10" + NPM_ARCH: ${{ parameters.NPM_ARCH }} + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + templateContext: + sdl: + binskim: + analyzeTargetGlob: '$(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH)/**/*.node;$(Agent.BuildDirectory)/vscode-server-linux-$(VSCODE_ARCH)/**/*.node;$(Agent.BuildDirectory)/vscode-server-linux-$(VSCODE_ARCH)-web/**/*.node' + preReleaseVersion: '4.3.1' + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out + outputs: + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/crashes + artifactName: crash-dump-linux-$(VSCODE_ARCH)-$(System.JobAttempt) + displayName: Publish Crash Reports + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/node_modules + artifactName: node-modules-linux-$(VSCODE_ARCH)-$(System.JobAttempt) + displayName: Publish Node Modules + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/logs + artifactName: logs-linux-$(VSCODE_ARCH)-$(System.JobAttempt) + displayName: Publish Log Files + sbomEnabled: false + isProduction: false + condition: succeededOrFailed() + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/client/$(CLIENT_ARCHIVE_NAME) + artifactName: vscode_client_linux_$(VSCODE_ARCH)_archive-unsigned + displayName: Publish client archive + sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH) + sbomPackageName: "VS Code Linux $(VSCODE_ARCH) (unsigned)" + sbomPackageVersion: $(Build.SourceVersion) + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/server/vscode-server-linux-$(VSCODE_ARCH).tar.gz + artifactName: vscode_server_linux_$(VSCODE_ARCH)_archive-unsigned + displayName: Publish server archive + sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-linux-$(VSCODE_ARCH) + sbomPackageName: "VS Code Linux $(VSCODE_ARCH) Server" + sbomPackageVersion: $(Build.SourceVersion) + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/web/vscode-server-linux-$(VSCODE_ARCH)-web.tar.gz + artifactName: vscode_web_linux_$(VSCODE_ARCH)_archive-unsigned + displayName: Publish web server archive + sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-linux-$(VSCODE_ARCH)-web + sbomPackageName: "VS Code Linux $(VSCODE_ARCH) Web" + sbomPackageVersion: $(Build.SourceVersion) + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/deb/$(DEB_PACKAGE_NAME) + artifactName: vscode_client_linux_$(VSCODE_ARCH)_deb-package + displayName: Publish deb package + sbomBuildDropPath: .build/linux/deb + sbomPackageName: "VS Code Linux $(VSCODE_ARCH) DEB" + sbomPackageVersion: $(Build.SourceVersion) + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/rpm/$(RPM_PACKAGE_NAME) + artifactName: vscode_client_linux_$(VSCODE_ARCH)_rpm-package + displayName: Publish rpm package + sbomBuildDropPath: .build/linux/rpm + sbomPackageName: "VS Code Linux $(VSCODE_ARCH) RPM" + sbomPackageVersion: $(Build.SourceVersion) + - ${{ if eq(parameters.VSCODE_BUILD_LINUX_SNAP, true) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/snap/$(SNAP_PACKAGE_NAME) + artifactName: vscode_client_linux_$(VSCODE_ARCH)_snap + displayName: Publish snap package + sbomBuildDropPath: $(SNAP_EXTRACTED_PATH) + sbomPackageName: "VS Code Linux $(VSCODE_ARCH) SNAP" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ./steps/product-build-linux-compile.yml@self + parameters: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_BUILD_LINUX_SNAP: ${{ parameters.VSCODE_BUILD_LINUX_SNAP }} + VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} + VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} + VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} diff --git a/build/azure-pipelines/linux/setup-env.sh b/build/azure-pipelines/linux/setup-env.sh index 6805633b9ba7b..8e14d2eba0332 100755 --- a/build/azure-pipelines/linux/setup-env.sh +++ b/build/azure-pipelines/linux/setup-env.sh @@ -13,19 +13,19 @@ if [ -d "$VSCODE_CLIENT_SYSROOT_DIR" ]; then echo "Using cached client sysroot" else echo "Downloading client sysroot" - SYSROOT_ARCH="$SYSROOT_ARCH" VSCODE_SYSROOT_DIR="$VSCODE_CLIENT_SYSROOT_DIR" node -e '(async () => { const { getVSCodeSysroot } = require("./build/linux/debian/install-sysroot.js"); await getVSCodeSysroot(process.env["SYSROOT_ARCH"]); })()' + SYSROOT_ARCH="$SYSROOT_ARCH" VSCODE_SYSROOT_DIR="$VSCODE_CLIENT_SYSROOT_DIR" node -e 'import { getVSCodeSysroot } from "./build/linux/debian/install-sysroot.ts"; (async () => { await getVSCodeSysroot(process.env["SYSROOT_ARCH"]); })()' fi if [ -d "$VSCODE_REMOTE_SYSROOT_DIR" ]; then echo "Using cached remote sysroot" else echo "Downloading remote sysroot" - SYSROOT_ARCH="$SYSROOT_ARCH" VSCODE_SYSROOT_DIR="$VSCODE_REMOTE_SYSROOT_DIR" VSCODE_SYSROOT_PREFIX="-glibc-2.28-gcc-8.5.0" node -e '(async () => { const { getVSCodeSysroot } = require("./build/linux/debian/install-sysroot.js"); await getVSCodeSysroot(process.env["SYSROOT_ARCH"]); })()' + SYSROOT_ARCH="$SYSROOT_ARCH" VSCODE_SYSROOT_DIR="$VSCODE_REMOTE_SYSROOT_DIR" VSCODE_SYSROOT_PREFIX="-glibc-2.28-gcc-8.5.0" node -e 'import { getVSCodeSysroot } from "./build/linux/debian/install-sysroot.ts"; (async () => { await getVSCodeSysroot(process.env["SYSROOT_ARCH"]); })()' fi if [ "$npm_config_arch" == "x64" ]; then # Download clang based on chromium revision used by vscode - curl -s https://raw.githubusercontent.com/chromium/chromium/138.0.7204.100/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux + curl -s https://raw.githubusercontent.com/chromium/chromium/142.0.7444.235/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux # Download libcxx headers and objects from upstream electron releases DEBUG=libcxx-fetcher \ @@ -33,13 +33,13 @@ if [ "$npm_config_arch" == "x64" ]; then VSCODE_LIBCXX_HEADERS_DIR=$PWD/.build/libcxx_headers \ VSCODE_LIBCXXABI_HEADERS_DIR=$PWD/.build/libcxxabi_headers \ VSCODE_ARCH="$npm_config_arch" \ - node build/linux/libcxx-fetcher.js + node build/linux/libcxx-fetcher.ts # Set compiler toolchain # Flags for the client build are based on - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/138.0.7204.100:build/config/arm.gni - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/138.0.7204.100:build/config/compiler/BUILD.gn - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/138.0.7204.100:build/config/c++/BUILD.gn + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/142.0.7444.235:build/config/arm.gni + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/142.0.7444.235:build/config/compiler/BUILD.gn + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/142.0.7444.235:build/config/c++/BUILD.gn export CC="$PWD/.build/CR_Clang/bin/clang --gcc-toolchain=$VSCODE_CLIENT_SYSROOT_DIR/x86_64-linux-gnu" export CXX="$PWD/.build/CR_Clang/bin/clang++ --gcc-toolchain=$VSCODE_CLIENT_SYSROOT_DIR/x86_64-linux-gnu" export CXXFLAGS="-nostdinc++ -D__NO_INLINE__ -DSPDLOG_USE_STD_FORMAT -I$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit -D_LIBCPP_ABI_NAMESPACE=Cr -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE --sysroot=$VSCODE_CLIENT_SYSROOT_DIR/x86_64-linux-gnu/x86_64-linux-gnu/sysroot" diff --git a/build/azure-pipelines/linux/steps/product-build-linux-compile.yml b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml new file mode 100644 index 0000000000000..89199ebbbb14c --- /dev/null +++ b/build/azure-pipelines/linux/steps/product-build-linux-compile.yml @@ -0,0 +1,415 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_CIBUILD + type: boolean + - name: VSCODE_QUALITY + type: string + - name: VSCODE_BUILD_LINUX_SNAP + type: boolean + default: false + - name: VSCODE_RUN_ELECTRON_TESTS + type: boolean + default: false + - name: VSCODE_RUN_BROWSER_TESTS + type: boolean + default: false + - name: VSCODE_RUN_REMOTE_TESTS + type: boolean + default: false + +steps: + - template: ../../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output + + - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + displayName: Extract compilation output + + - script: | + set -e + # Start X server + ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get update + ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get install -y pkg-config \ + xvfb \ + libgtk-3-0 \ + libxkbfile-dev \ + libkrb5-dev \ + libgbm1 \ + rpm + sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + sudo chmod +x /etc/init.d/xvfb + sudo update-rc.d xvfb defaults + sudo service xvfb start + displayName: Setup system services + + - script: node build/setup-npm-registry.ts $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts linux $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: tar -xzf .build/node_modules_cache/cache.tgz + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Extract node_modules cache + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + # Step will be used by both verify glibcxx version for remote server and building rpm package, + # hence avoid adding it behind NODE_MODULES_RESTORED condition. + - script: | + set -e + SYSROOT_ARCH=$VSCODE_ARCH + if [ "$SYSROOT_ARCH" == "x64" ]; then + SYSROOT_ARCH="amd64" + fi + export VSCODE_SYSROOT_DIR=$(Build.SourcesDirectory)/.build/sysroots/glibc-2.28-gcc-8.5.0 + SYSROOT_ARCH="$SYSROOT_ARCH" VSCODE_SYSROOT_PREFIX="-glibc-2.28-gcc-8.5.0" node -e 'import { getVSCodeSysroot } from "./build/linux/debian/install-sysroot.ts"; (async () => { await getVSCodeSysroot(process.env["SYSROOT_ARCH"]); })()' + env: + VSCODE_ARCH: $(VSCODE_ARCH) + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download vscode sysroots + + - script: | + set -e + + source ./build/azure-pipelines/linux/setup-env.sh + + # Run preinstall script before root dependencies are installed + # so that v8 headers are patched correctly for native modules. + node build/npm/preinstall.ts + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + npm_config_arch: $(NPM_ARCH) + VSCODE_ARCH: $(VSCODE_ARCH) + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: node build/azure-pipelines/distro/mixin-npm.ts + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive + + - script: node build/azure-pipelines/distro/mixin-quality.ts + displayName: Mixin distro quality + + - template: ../../common/install-builtin-extensions.yml@self + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npm run copy-policy-dto --prefix build && node build/lib/policies/policyGenerator.ts build/lib/policies/policyData.jsonc linux + displayName: Generate policy definitions + retryCountOnTaskFailure: 3 + + - script: | + set -e + npm run gulp vscode-linux-$(VSCODE_ARCH)-min-ci + ARCHIVE_PATH=".build/linux/client/code-${{ parameters.VSCODE_QUALITY }}-$(VSCODE_ARCH)-$(date +%s).tar.gz" + mkdir -p $(dirname $ARCHIVE_PATH) + echo "##vso[task.setvariable variable=CLIENT_PATH]$ARCHIVE_PATH" + echo "##vso[task.setvariable variable=CLIENT_ARCHIVE_NAME]$(basename $ARCHIVE_PATH)" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build client + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: $(ARTIFACT_PREFIX)vscode_cli_linux_$(VSCODE_ARCH)_cli + patterns: "**" + path: $(Build.ArtifactStagingDirectory)/cli + displayName: Download VS Code CLI + + - script: | + set -e + tar -xzvf $(Build.ArtifactStagingDirectory)/cli/*.tar.gz -C $(Build.ArtifactStagingDirectory)/cli + CLI_APP_NAME=$(node -p "require(\"$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/resources/app/product.json\").tunnelApplicationName") + APP_NAME=$(node -p "require(\"$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/resources/app/product.json\").applicationName") + mv $(Build.ArtifactStagingDirectory)/cli/$APP_NAME $(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/bin/$CLI_APP_NAME + displayName: Mix in CLI + + - script: | + set -e + tar -czf $CLIENT_PATH -C .. VSCode-linux-$(VSCODE_ARCH) + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Archive client + + - script: | + set -e + npm run gulp vscode-reh-linux-$(VSCODE_ARCH)-min-ci + mv ../vscode-reh-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH) # TODO@joaomoreno + ARCHIVE_PATH=".build/linux/server/vscode-server-linux-$(VSCODE_ARCH).tar.gz" + UNARCHIVE_PATH="`pwd`/../vscode-server-linux-$(VSCODE_ARCH)" + mkdir -p $(dirname $ARCHIVE_PATH) + tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-linux-$(VSCODE_ARCH) + echo "##vso[task.setvariable variable=SERVER_PATH]$ARCHIVE_PATH" + echo "##vso[task.setvariable variable=SERVER_UNARCHIVE_PATH]$UNARCHIVE_PATH" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server + + - script: | + set -e + npm run gulp vscode-reh-web-linux-$(VSCODE_ARCH)-min-ci + mv ../vscode-reh-web-linux-$(VSCODE_ARCH) ../vscode-server-linux-$(VSCODE_ARCH)-web # TODO@joaomoreno + ARCHIVE_PATH=".build/linux/web/vscode-server-linux-$(VSCODE_ARCH)-web.tar.gz" + mkdir -p $(dirname $ARCHIVE_PATH) + tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-server-linux-$(VSCODE_ARCH)-web + echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server (web) + + - ${{ if or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: + - script: | + set -e + + EXPECTED_GLIBC_VERSION="2.28" \ + EXPECTED_GLIBCXX_VERSION="3.4.25" \ + VSCODE_SYSROOT_DIR="$(Build.SourcesDirectory)/.build/sysroots/glibc-2.28-gcc-8.5.0" \ + ./build/azure-pipelines/linux/verify-glibc-requirements.sh + env: + SEARCH_PATH: $(SERVER_UNARCHIVE_PATH) + npm_config_arch: $(NPM_ARCH) + VSCODE_ARCH: $(VSCODE_ARCH) + displayName: Check GLIBC and GLIBCXX dependencies in server archive + + - ${{ else }}: + - script: | + set -e + + EXPECTED_GLIBC_VERSION="2.28" \ + EXPECTED_GLIBCXX_VERSION="3.4.26" \ + VSCODE_SYSROOT_DIR="$(Build.SourcesDirectory)/.build/sysroots/glibc-2.28-gcc-8.5.0" \ + ./build/azure-pipelines/linux/verify-glibc-requirements.sh + env: + SEARCH_PATH: $(SERVER_UNARCHIVE_PATH) + npm_config_arch: $(NPM_ARCH) + VSCODE_ARCH: $(VSCODE_ARCH) + displayName: Check GLIBC and GLIBCXX dependencies in server archive + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: | + set -e + npm run gulp "vscode-linux-$(VSCODE_ARCH)-prepare-deb" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Prepare deb package + + - script: | + set -e + npm run gulp "vscode-linux-$(VSCODE_ARCH)-build-deb" + mkdir -p .build/linux/deb + cp .build/linux/deb/*/deb/*.deb .build/linux/deb/ + file_output=$(file $(ls .build/linux/deb/*.deb)) + if [[ "$file_output" != *"data compression xz"* ]]; then + echo "Error: unknown compression. $file_output" + exit 1 + fi + echo "##vso[task.setvariable variable=DEB_PATH]$(ls .build/linux/deb/*.deb)" + echo "##vso[task.setvariable variable=DEB_PACKAGE_NAME]$(basename $(ls .build/linux/deb/*.deb))" + displayName: Build deb package + + - script: | + set -e + TRIPLE="" + if [ "$VSCODE_ARCH" == "x64" ]; then + TRIPLE="x86_64-linux-gnu" + elif [ "$VSCODE_ARCH" == "arm64" ]; then + TRIPLE="aarch64-linux-gnu" + elif [ "$VSCODE_ARCH" == "armhf" ]; then + TRIPLE="arm-rpi-linux-gnueabihf" + fi + export VSCODE_SYSROOT_DIR=$(Build.SourcesDirectory)/.build/sysroots/glibc-2.28-gcc-10.5.0 + export STRIP="$VSCODE_SYSROOT_DIR/$TRIPLE/$TRIPLE/bin/strip" + npm run gulp "vscode-linux-$(VSCODE_ARCH)-prepare-rpm" + env: + VSCODE_ARCH: $(VSCODE_ARCH) + displayName: Prepare rpm package + + - script: | + set -e + npm run gulp "vscode-linux-$(VSCODE_ARCH)-build-rpm" + mkdir -p .build/linux/rpm + cp .build/linux/rpm/*/*.rpm .build/linux/rpm/ + echo "##vso[task.setvariable variable=RPM_PATH]$(ls .build/linux/rpm/*.rpm)" + echo "##vso[task.setvariable variable=RPM_PACKAGE_NAME]$(basename $(ls .build/linux/rpm/*.rpm))" + displayName: Build rpm package + + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - task: Docker@1 + inputs: + azureSubscriptionEndpoint: vscode + azureContainerRegistry: vscodehub.azurecr.io + command: login + displayName: Login to Container Registry + + - ${{ if eq(parameters.VSCODE_BUILD_LINUX_SNAP, true) }}: + - script: | + set -e + npm run gulp "vscode-linux-$(VSCODE_ARCH)-prepare-snap" + sudo -E docker run -e VSCODE_ARCH -e VSCODE_QUALITY -v $(pwd):/work -w /work vscodehub.azurecr.io/vscode-linux-build-agent:snapcraft-x64 /bin/bash -c "./build/azure-pipelines/linux/build-snap.sh" + + SNAP_ROOT="$(pwd)/.build/linux/snap/$(VSCODE_ARCH)" + SNAP_EXTRACTED_PATH=$(find $SNAP_ROOT -maxdepth 1 -type d -name 'code-*') + + mkdir -p .build/linux/snap + cp $(find $SNAP_ROOT -maxdepth 1 -type f -name '*.snap') .build/linux/snap/ + + # SBOM tool doesn't like recursive symlinks + sudo find $SNAP_EXTRACTED_PATH -type l -delete + + echo "##vso[task.setvariable variable=SNAP_EXTRACTED_PATH]$SNAP_EXTRACTED_PATH" + echo "##vso[task.setvariable variable=SNAP_PATH]$(ls .build/linux/snap/*.snap)" + echo "##vso[task.setvariable variable=SNAP_PACKAGE_NAME]$(basename $(ls .build/linux/snap/*.snap))" + env: + VSCODE_ARCH: $(VSCODE_ARCH) + displayName: Build snap package + + - task: UseDotNet@2 + inputs: + version: 6.x + + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' + + - pwsh: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)/_tasks | Select-Object -last 1).FullName + $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName + echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version/net6.0/esrpcli.dll" + displayName: Find ESRP CLI + + - script: npx deemon --detach --wait node build/azure-pipelines/linux/codesign.ts + env: + EsrpCliDllPath: $(EsrpCliDllPath) + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Codesign deb & rpm + + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - template: product-build-linux-test.yml@self + parameters: + VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} + VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} + VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - script: npx deemon --attach node build/azure-pipelines/linux/codesign.ts + condition: succeededOrFailed() + displayName: "✍️ Post-job: Codesign deb & rpm" + + - script: | + set -e + + mkdir -p $(Build.ArtifactStagingDirectory)/out/client + mv $(CLIENT_PATH) $(Build.ArtifactStagingDirectory)/out/client/$(CLIENT_ARCHIVE_NAME) + + mkdir -p $(Build.ArtifactStagingDirectory)/out/server + mv $(SERVER_PATH) $(Build.ArtifactStagingDirectory)/out/server/$(basename $(SERVER_PATH)) + + mkdir -p $(Build.ArtifactStagingDirectory)/out/web + mv $(WEB_PATH) $(Build.ArtifactStagingDirectory)/out/web/$(basename $(WEB_PATH)) + + mkdir -p $(Build.ArtifactStagingDirectory)/out/deb + mv $(DEB_PATH) $(Build.ArtifactStagingDirectory)/out/deb/$(DEB_PACKAGE_NAME) + + mkdir -p $(Build.ArtifactStagingDirectory)/out/rpm + mv $(RPM_PATH) $(Build.ArtifactStagingDirectory)/out/rpm/$(RPM_PACKAGE_NAME) + + if [ -n "$SNAP_PATH" ]; then + mkdir -p $(Build.ArtifactStagingDirectory)/out/snap + mv $(SNAP_PATH) $(Build.ArtifactStagingDirectory)/out/snap/$(SNAP_PACKAGE_NAME) + fi + + # SBOM generation uses hard links which are not supported by the Linux kernel + # for files that have the SUID bit set, so we need to remove the SUID bit from + # the chrome-sandbox file. + sudo ls -l $(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH)/chrome-sandbox + sudo chmod u-s $(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH)/chrome-sandbox + sudo ls -l $(Agent.BuildDirectory)/VSCode-linux-$(VSCODE_ARCH)/chrome-sandbox + displayName: Move artifacts to out directory diff --git a/build/azure-pipelines/linux/steps/product-build-linux-test.yml b/build/azure-pipelines/linux/steps/product-build-linux-test.yml new file mode 100644 index 0000000000000..2b46c9e007f1e --- /dev/null +++ b/build/azure-pipelines/linux/steps/product-build-linux-test.yml @@ -0,0 +1,145 @@ +parameters: + - name: VSCODE_RUN_ELECTRON_TESTS + type: boolean + - name: VSCODE_RUN_BROWSER_TESTS + type: boolean + - name: VSCODE_RUN_REMOTE_TESTS + type: boolean + +steps: + - script: npm exec -- npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download Electron and Playwright + retryCountOnTaskFailure: 3 + + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + ELECTRON_ROOT=.build/electron + sudo chown root $APP_ROOT/chrome-sandbox + sudo chown root $ELECTRON_ROOT/chrome-sandbox + sudo chmod 4755 $APP_ROOT/chrome-sandbox + sudo chmod 4755 $ELECTRON_ROOT/chrome-sandbox + stat $APP_ROOT/chrome-sandbox + stat $ELECTRON_ROOT/chrome-sandbox + displayName: Change setuid helper binary permission + + - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: + - script: ./scripts/test.sh --build --tfs "Unit Tests" + displayName: 🧪 Run unit tests (Electron) + timeoutInMinutes: 15 + - script: npm run test-node -- --build + displayName: 🧪 Run unit tests (node.js) + timeoutInMinutes: 15 + + - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: + - script: npm run test-browser-no-install -- --build --browser chromium --tfs "Browser Unit Tests" + env: + DEBUG: "*browser*" + displayName: 🧪 Run unit tests (Browser, Chromium) + timeoutInMinutes: 15 + + - script: | + set -e + npm run gulp \ + compile-extension:configuration-editing \ + compile-extension:css-language-features-server \ + compile-extension:emmet \ + compile-extension:git \ + compile-extension:github-authentication \ + compile-extension:html-language-features-server \ + compile-extension:ipynb \ + compile-extension:notebook-renderers \ + compile-extension:json-language-features-server \ + compile-extension:markdown-language-features \ + compile-extension-media \ + compile-extension:microsoft-authentication \ + compile-extension:typescript-language-features \ + compile-extension:vscode-api-tests \ + compile-extension:vscode-colorize-tests \ + compile-extension:vscode-colorize-perf-tests \ + compile-extension:vscode-test-resolver + displayName: Build integration tests + + - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: + - script: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + INTEGRATION_TEST_APP_NAME="$APP_NAME" \ + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + ./scripts/test-integration.sh --build --tfs "Integration Tests" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) + displayName: 🧪 Run integration tests (Electron) + timeoutInMinutes: 20 + + - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: + - script: ./scripts/test-web-integration.sh --browser chromium + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH)-web + displayName: 🧪 Run integration tests (Browser, Chromium) + timeoutInMinutes: 20 + + - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: + - script: | + set -e + APP_ROOT=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + APP_NAME=$(node -p "require(\"$APP_ROOT/resources/app/product.json\").applicationName") + INTEGRATION_TEST_APP_NAME="$APP_NAME" \ + INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \ + ./scripts/test-remote-integration.sh + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH) + displayName: 🧪 Run integration tests (Remote) + timeoutInMinutes: 20 + + - script: | + set -e + ps -ef + cat /proc/sys/fs/inotify/max_user_watches + lsof | wc -l + displayName: Diagnostics before smoke test run (processes, max_user_watches, number of opened file handles) + continueOnError: true + condition: succeededOrFailed() + + - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: + - script: npm run smoketest-no-compile -- --tracing --build "$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)" + timeoutInMinutes: 20 + displayName: 🧪 Run smoke tests (Electron) + + - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: + - script: npm run smoketest-no-compile -- --web --tracing --headless + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH)-web + timeoutInMinutes: 20 + displayName: 🧪 Run smoke tests (Browser, Chromium) + + - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: + - script: | + set -e + APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-server-linux-$(VSCODE_ARCH)" \ + npm run smoketest-no-compile -- --tracing --remote --build "$APP_PATH" + timeoutInMinutes: 20 + displayName: 🧪 Run smoke tests (Remote) + + - script: | + set -e + ps -ef + cat /proc/sys/fs/inotify/max_user_watches + lsof | wc -l + displayName: Diagnostics after smoke test run (processes, max_user_watches, number of opened file handles) + continueOnError: true + condition: succeededOrFailed() + + - task: PublishTestResults@2 + displayName: Publish Tests Results + inputs: + testResultsFiles: "*-results.xml" + searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" + condition: succeededOrFailed() diff --git a/build/azure-pipelines/linux/verify-glibc-requirements.sh b/build/azure-pipelines/linux/verify-glibc-requirements.sh index 529417761f992..3db90471faa3c 100755 --- a/build/azure-pipelines/linux/verify-glibc-requirements.sh +++ b/build/azure-pipelines/linux/verify-glibc-requirements.sh @@ -10,7 +10,7 @@ elif [ "$VSCODE_ARCH" == "armhf" ]; then fi # Get all files with .node extension from server folder -files=$(find $SEARCH_PATH -name "*.node" -not -path "*prebuilds*" -not -path "*extensions/node_modules/@parcel/watcher*" -o -type f -executable -name "node") +files=$(find $SEARCH_PATH -name "*.node" -not -path "*prebuilds*" -not -path "*extensions/node_modules/@vscode/watcher*" -o -type f -executable -name "node") echo "Verifying requirements for files: $files" diff --git a/build/azure-pipelines/oss/product-build-pr-cache-darwin.yml b/build/azure-pipelines/oss/product-build-pr-cache-darwin.yml deleted file mode 100644 index d382918a6c341..0000000000000 --- a/build/azure-pipelines/oss/product-build-pr-cache-darwin.yml +++ /dev/null @@ -1,79 +0,0 @@ -steps: - - checkout: self - fetchDepth: 1 - retryCountOnTaskFailure: 3 - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - script: node build/setup-npm-registry.js $NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js darwin $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash - displayName: Prepare node_modules cache key - - - task: Cache@2 - inputs: - key: '"node_modules" | .build/packagelockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: | - set -e - c++ --version - xcode-select -print-path - python3 -m pip install setuptools - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - env: - npm_config_arch: $(VSCODE_ARCH) - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - # Avoid using dlopen to load Kerberos on macOS which can cause missing libraries - # https://github.com/mongodb-js/kerberos/commit/04044d2814ad1d01e77f1ce87f26b03d86692cf2 - # flipped the default to support legacy linux distros which shouldn't happen - # on macOS. - GYP_DEFINES: "kerberos_use_rtld=false" - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive diff --git a/build/azure-pipelines/oss/product-build-pr-cache-linux.yml b/build/azure-pipelines/oss/product-build-pr-cache-linux.yml deleted file mode 100644 index b4a2cc3a4807b..0000000000000 --- a/build/azure-pipelines/oss/product-build-pr-cache-linux.yml +++ /dev/null @@ -1,76 +0,0 @@ -steps: - - checkout: self - fetchDepth: 1 - retryCountOnTaskFailure: 3 - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - script: node build/setup-npm-registry.js $NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js linux $VSCODE_ARCH $(node -p process.arch) > .build/packagelockhash - displayName: Prepare node_modules cache key - - - task: Cache@2 - inputs: - key: '"node_modules" | .build/packagelockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: | - set -e - ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get update - ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get install -y libkrb5-dev - displayName: Setup system services - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive diff --git a/build/azure-pipelines/oss/product-build-pr-cache-win32.yml b/build/azure-pipelines/oss/product-build-pr-cache-win32.yml deleted file mode 100644 index f4a82587567f2..0000000000000 --- a/build/azure-pipelines/oss/product-build-pr-cache-win32.yml +++ /dev/null @@ -1,71 +0,0 @@ -steps: - - checkout: self - fetchDepth: 1 - retryCountOnTaskFailure: 3 - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - powershell: node build/setup-npm-registry.js $env:NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - pwsh: | - mkdir .build -ea 0 - node build/azure-pipelines/common/computeNodeModulesCacheKey.js win32 $(VSCODE_ARCH) $(node -p process.arch) > .build/packagelockhash - displayName: Prepare node_modules cache key - - - task: Cache@2 - inputs: - key: '"node_modules" | .build/packagelockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - powershell: 7z.exe x .build/node_modules_cache/cache.7z -aoa - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - exec { npm config set registry "$env:NPM_REGISTRY" } - $NpmrcPath = (npm config get userconfig) - echo "##vso[task.setvariable variable=NPMRC_PATH]$NpmrcPath" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { npm ci } - env: - npm_config_arch: $(VSCODE_ARCH) - npm_config_foreground_scripts: "true" - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - retryCountOnTaskFailure: 5 - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt } - exec { mkdir -Force .build/node_modules_cache } - exec { 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt } - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive diff --git a/build/azure-pipelines/product-build-macos.yml b/build/azure-pipelines/product-build-macos.yml index cc8985c07ca1c..57d88a6c3d752 100644 --- a/build/azure-pipelines/product-build-macos.yml +++ b/build/azure-pipelines/product-build-macos.yml @@ -28,6 +28,8 @@ variables: value: ${{ parameters.VSCODE_QUALITY }} - name: VSCODE_CIBUILD value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }} + - name: VSCODE_STEP_ON_IT + value: false - name: skipComponentGovernanceDetection value: true - name: ComponentDetection.Timeout @@ -53,8 +55,6 @@ extends: tsa: enabled: true configFile: $(Build.SourcesDirectory)/build/azure-pipelines/config/tsaoptions.json - binskim: - analyzeTargetGlob: '+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.exe;+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.node;+:file|$(Agent.BuildDirectory)/VSCode-*/**/*.dll;-:file|$(Build.SourcesDirectory)/.build/**/system-setup/VSCodeSetup*.exe;-:file|$(Build.SourcesDirectory)/.build/**/user-setup/VSCodeUserSetup*.exe' codeql: runSourceLanguagesInSourceAnalysis: true compiled: @@ -73,64 +73,34 @@ extends: image: onebranch.azurecr.io/linux/ubuntu-2004-arm64:latest stages: - stage: Compile + pool: + name: AcesShared + os: macOS + demands: + - ImageOverride -equals ACES_VM_SharedPool_Sequoia jobs: - - job: Compile - timeoutInMinutes: 90 - pool: - name: ACESLabTest - os: macOS - steps: - - template: build/azure-pipelines/product-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + - template: build/azure-pipelines/product-compile.yml@self - stage: macOS dependsOn: - Compile pool: - name: ACESLabTest + name: AcesShared os: macOS + demands: + - ImageOverride -equals ACES_VM_SharedPool_Sequoia variables: BUILDSECMON_OPT_IN: true jobs: - - job: macOSElectronTest - displayName: Electron Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: arm64 - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: electron - VSCODE_RUN_ELECTRON_TESTS: true - - - job: macOSBrowserTest - displayName: Browser Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: arm64 - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: browser - VSCODE_RUN_BROWSER_TESTS: true - - - job: macOSRemoteTest - displayName: Remote Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: arm64 - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: remote - VSCODE_RUN_REMOTE_TESTS: true + - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self + parameters: + VSCODE_CIBUILD: true + VSCODE_TEST_SUITE: Electron + - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self + parameters: + VSCODE_CIBUILD: true + VSCODE_TEST_SUITE: Browser + - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self + parameters: + VSCODE_CIBUILD: true + VSCODE_TEST_SUITE: Remote diff --git a/build/azure-pipelines/product-build-pr.yml b/build/azure-pipelines/product-build-pr.yml deleted file mode 100644 index f7aff453fec0d..0000000000000 --- a/build/azure-pipelines/product-build-pr.yml +++ /dev/null @@ -1,231 +0,0 @@ -trigger: - - release/* - -pr: - branches: - include: ["release/*"] - -variables: - - name: Codeql.SkipTaskAutoInjection - value: true - - name: skipComponentGovernanceDetection - value: true - - name: NPM_REGISTRY - value: "none" - - name: CARGO_REGISTRY - value: "none" - - name: VSCODE_CIBUILD - value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }} - - name: VSCODE_QUALITY - value: oss - - name: VSCODE_STEP_ON_IT - value: false - -stages: - - ${{ if ne(variables['VSCODE_CIBUILD'], true) }}: - - stage: Compile - displayName: Compile & Hygiene - dependsOn: [] - jobs: - - job: Compile - displayName: Compile & Hygiene - pool: 1es-oss-ubuntu-22.04-x64 - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - steps: - - template: product-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - - stage: Test - displayName: Test - dependsOn: [] - jobs: - - job: Linuxx64ElectronTest - displayName: Linux (Electron) - pool: 1es-oss-ubuntu-22.04-x64 - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: electron - VSCODE_RUN_ELECTRON_TESTS: true - - - job: Linuxx64BrowserTest - displayName: Linux (Browser) - pool: 1es-oss-ubuntu-22.04-x64 - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: browser - VSCODE_RUN_BROWSER_TESTS: true - - - job: Linuxx64RemoteTest - displayName: Linux (Remote) - pool: 1es-oss-ubuntu-22.04-x64 - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: linux/product-build-linux.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: remote - VSCODE_RUN_REMOTE_TESTS: true - - - job: LinuxCLI - displayName: Linux (CLI) - pool: 1es-oss-ubuntu-22.04-x64 - timeoutInMinutes: 30 - steps: - - template: cli/test.yml@self - - - job: Windowsx64ElectronTests - displayName: Windows (Electron) - pool: 1es-oss-windows-2022-x64 - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - steps: - - template: win32/product-build-win32.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: electron - VSCODE_RUN_ELECTRON_TESTS: true - - - job: Windowsx64BrowserTests - displayName: Windows (Browser) - pool: 1es-oss-windows-2022-x64 - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - steps: - - template: win32/product-build-win32.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: browser - VSCODE_RUN_BROWSER_TESTS: true - - - job: Windowsx64RemoteTests - displayName: Windows (Remote) - pool: 1es-oss-windows-2022-x64 - timeoutInMinutes: 60 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - steps: - - template: win32/product-build-win32.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: remote - VSCODE_RUN_REMOTE_TESTS: true - - - job: macOSx64ElectronTests - displayName: macOS (Electron) - pool: - vmImage: macOS-14 - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: electron - VSCODE_RUN_ELECTRON_TESTS: true - - - job: macOSx64BrowserTests - displayName: macOS (Browser) - pool: - vmImage: macOS-14 - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: browser - VSCODE_RUN_BROWSER_TESTS: true - - - job: macOSx64RemoteTests - displayName: macOS (Remote) - pool: - vmImage: macOS-14 - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - steps: - - template: darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: remote - VSCODE_RUN_REMOTE_TESTS: true - - - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - - stage: NodeModuleCache - jobs: - - job: Linuxx64MaintainNodeModulesCache - displayName: Linux (Maintain node_modules cache) - pool: 1es-oss-ubuntu-22.04-x64 - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - steps: - - template: oss/product-build-pr-cache-linux.yml@self - - - job: Windowsx64MaintainNodeModulesCache - displayName: Windows (Maintain node_modules cache) - pool: 1es-oss-windows-2022-x64 - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - steps: - - template: oss/product-build-pr-cache-win32.yml@self - - - job: macOSx64MaintainNodeModulesCache - displayName: macOS (Maintain node_modules cache) - pool: - vmImage: macOS-14 - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - steps: - - template: oss/product-build-pr-cache-darwin.yml@self diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 12e7b05d4bacb..516b3b4fffd5f 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -41,6 +41,10 @@ parameters: displayName: "🎯 Linux x64" type: boolean default: true + - name: VSCODE_BUILD_LINUX_SNAP + displayName: "🎯 Linux x64 Snap" + type: boolean + default: true - name: VSCODE_BUILD_LINUX_ARM64 displayName: "🎯 Linux arm64" type: boolean @@ -94,10 +98,7 @@ variables: - name: VSCODE_PRIVATE_BUILD value: ${{ ne(variables['Build.Repository.Uri'], 'https://github.com/microsoft/vscode.git') }} - name: NPM_REGISTRY - ${{ if in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }}: # disable terrapin when in VSCODE_CIBUILD - value: none - ${{ else }}: - value: ${{ parameters.NPM_REGISTRY }} + value: ${{ parameters.NPM_REGISTRY }} - name: CARGO_REGISTRY value: ${{ parameters.CARGO_REGISTRY }} - name: VSCODE_QUALITY @@ -105,7 +106,7 @@ variables: - name: VSCODE_BUILD_STAGE_WINDOWS value: ${{ or(eq(parameters.VSCODE_BUILD_WIN32, true), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_LINUX - value: ${{ or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }} + value: ${{ or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_SNAP, true), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_ALPINE value: ${{ or(eq(parameters.VSCODE_BUILD_ALPINE, true), eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true)) }} - name: VSCODE_BUILD_STAGE_MACOS @@ -189,132 +190,98 @@ extends: image: onebranch.azurecr.io/linux/ubuntu-2004-arm64:latest stages: - stage: Compile + pool: + name: AcesShared + os: macOS jobs: - - job: Compile - timeoutInMinutes: 90 - pool: - name: AcesShared - os: macOS - steps: - - template: build/azure-pipelines/product-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + - template: build/azure-pipelines/product-compile.yml@self - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - stage: CompileCLI dependsOn: [] jobs: - - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: - - job: CLILinuxX64 - pool: - name: 1es-ubuntu-22.04-x64 - os: linux - steps: - - template: build/azure-pipelines/linux/cli-build-linux.yml@self - parameters: - VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_LINUX: ${{ parameters.VSCODE_BUILD_LINUX }} + - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: + - template: build/azure-pipelines/win32/product-build-win32-cli.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: - - job: CLILinuxGnuARM - pool: - name: 1es-ubuntu-22.04-x64 - os: linux - steps: - - template: build/azure-pipelines/linux/cli-build-linux.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_LINUX_ARMHF: ${{ parameters.VSCODE_BUILD_LINUX_ARMHF }} + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: + - template: build/azure-pipelines/win32/product-build-win32-cli.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + + - ${{ if eq(parameters.VSCODE_BUILD_LINUX, true) }}: + - template: build/azure-pipelines/linux/product-build-linux-cli.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: - - job: CLILinuxGnuAarch64 - pool: - name: 1es-ubuntu-22.04-x64 - os: linux - steps: - - template: build/azure-pipelines/linux/cli-build-linux.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_LINUX_ARM64: ${{ parameters.VSCODE_BUILD_LINUX_ARM64 }} + - template: build/azure-pipelines/linux/product-build-linux-cli.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: + - template: build/azure-pipelines/linux/product-build-linux-cli.yml@self + parameters: + VSCODE_ARCH: armhf + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE, true)) }}: - - job: CLIAlpineX64 - pool: - name: 1es-ubuntu-22.04-x64 - os: linux - steps: - - template: build/azure-pipelines/alpine/cli-build-alpine.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_ALPINE: ${{ parameters.VSCODE_BUILD_ALPINE }} + - template: build/azure-pipelines/alpine/product-build-alpine-cli.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true)) }}: - - job: CLIAlpineARM64 - pool: - name: 1es-ubuntu-22.04-x64 - os: linux - steps: - - template: build/azure-pipelines/alpine/cli-build-alpine.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_ALPINE_ARM64: ${{ parameters.VSCODE_BUILD_ALPINE_ARM64 }} + - template: build/azure-pipelines/alpine/product-build-alpine-cli.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - ${{ if eq(parameters.VSCODE_BUILD_MACOS, true) }}: - - job: CLIMacOSX64 - pool: - name: Azure Pipelines - image: macOS-13 - os: macOS - variables: - # todo@connor4312 to diagnose build flakes - - name: MSRUSTUP_LOG - value: debug - steps: - - template: build/azure-pipelines/darwin/cli-build-darwin.yml@self - parameters: - VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }} + - template: build/azure-pipelines/darwin/product-build-darwin-cli.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: - - job: CLIMacOSARM64 - pool: - name: Azure Pipelines - image: macOS-13 - os: macOS - variables: - # todo@connor4312 to diagnose build flakes - - name: MSRUSTUP_LOG - value: debug - steps: - - template: build/azure-pipelines/darwin/cli-build-darwin.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }} - - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - job: CLIWindowsX64 - pool: - name: 1es-windows-2022-x64 - os: windows - steps: - - template: build/azure-pipelines/win32/cli-build-win32.yml@self - parameters: - VSCODE_CHECK_ONLY: ${{ variables.VSCODE_CIBUILD }} - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_WIN32: ${{ parameters.VSCODE_BUILD_WIN32 }} + - template: build/azure-pipelines/darwin/product-build-darwin-cli.yml@self + parameters: + VSCODE_ARCH: arm64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - job: CLIWindowsARM64 - pool: - name: 1es-windows-2022-x64 - os: windows - steps: - - template: build/azure-pipelines/win32/cli-build-win32.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_WIN32_ARM64: ${{ parameters.VSCODE_BUILD_WIN32_ARM64 }} + - ${{ if and(eq(variables['VSCODE_CIBUILD'], true), eq(parameters.VSCODE_COMPILE_ONLY, false)) }}: + - stage: node_modules + dependsOn: [] + jobs: + - template: build/azure-pipelines/win32/product-build-win32-node-modules.yml@self + parameters: + VSCODE_ARCH: arm64 + - template: build/azure-pipelines/linux/product-build-linux-node-modules.yml@self + parameters: + NPM_ARCH: arm64 + VSCODE_ARCH: arm64 + - template: build/azure-pipelines/linux/product-build-linux-node-modules.yml@self + parameters: + NPM_ARCH: arm + VSCODE_ARCH: armhf + - template: build/azure-pipelines/alpine/product-build-alpine-node-modules.yml@self + parameters: + VSCODE_ARCH: x64 + - template: build/azure-pipelines/alpine/product-build-alpine-node-modules.yml@self + parameters: + VSCODE_ARCH: arm64 + - template: build/azure-pipelines/darwin/product-build-darwin-node-modules.yml@self + parameters: + VSCODE_ARCH: x64 + - template: build/azure-pipelines/web/product-build-web-node-modules.yml@self - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false)) }}: - stage: APIScan @@ -341,89 +308,44 @@ extends: os: windows jobs: - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - - job: WindowsElectronTests - displayName: Electron Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - steps: - - template: build/azure-pipelines/win32/product-build-win32.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_ARCH: x64 - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: electron - VSCODE_RUN_ELECTRON_TESTS: true - - job: WindowsBrowserTests - displayName: Browser Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - steps: - - template: build/azure-pipelines/win32/product-build-win32.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_ARCH: x64 - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: browser - VSCODE_RUN_BROWSER_TESTS: true - - job: WindowsRemoteTests - displayName: Remote Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - steps: - - template: build/azure-pipelines/win32/product-build-win32.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_ARCH: x64 - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: remote - VSCODE_RUN_REMOTE_TESTS: true + - template: build/azure-pipelines/win32/product-build-win32-ci.yml@self + parameters: + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_TEST_SUITE: Electron + - template: build/azure-pipelines/win32/product-build-win32-ci.yml@self + parameters: + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_TEST_SUITE: Browser + - template: build/azure-pipelines/win32/product-build-win32-ci.yml@self + parameters: + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_TEST_SUITE: Remote - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32, true)) }}: - - job: Windows - timeoutInMinutes: 120 - variables: + - template: build/azure-pipelines/win32/product-build-win32.yml@self + parameters: VSCODE_ARCH: x64 - templateContext: - sdl: - suppression: - suppressionFile: $(Build.SourcesDirectory)\.config\guardian\.gdnsuppress - steps: - - template: build/azure-pipelines/win32/product-build-win32.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_ARCH: x64 - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_RUN_ELECTRON_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_BROWSER_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_REMOTE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - - - job: WindowsCLISign - timeoutInMinutes: 90 - steps: - - template: build/azure-pipelines/win32/product-build-win32-cli-sign.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_WIN32: ${{ parameters.VSCODE_BUILD_WIN32 }} - VSCODE_BUILD_WIN32_ARM64: ${{ parameters.VSCODE_BUILD_WIN32_ARM64 }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_RUN_ELECTRON_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_BROWSER_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_REMOTE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - - job: WindowsARM64 - timeoutInMinutes: 90 - variables: + - template: build/azure-pipelines/win32/product-build-win32.yml@self + parameters: VSCODE_ARCH: arm64 - templateContext: - sdl: - suppression: - suppressionFile: $(Build.SourcesDirectory)\.config\guardian\.gdnsuppress - steps: - - template: build/azure-pipelines/win32/product-build-win32.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_ARCH: arm64 - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_WIN32, true), eq(parameters.VSCODE_BUILD_WIN32_ARM64, true))) }}: + - template: build/azure-pipelines/win32/product-build-win32-cli-sign.yml@self + parameters: + VSCODE_BUILD_WIN32: ${{ parameters.VSCODE_BUILD_WIN32 }} + VSCODE_BUILD_WIN32_ARM64: ${{ parameters.VSCODE_BUILD_WIN32_ARM64 }} - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_LINUX'], true)) }}: - stage: Linux @@ -436,92 +358,49 @@ extends: os: linux jobs: - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - - job: Linuxx64ElectronTest - displayName: Electron Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: build/azure-pipelines/linux/product-build-linux.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: electron - VSCODE_RUN_ELECTRON_TESTS: true - - job: Linuxx64BrowserTest - displayName: Browser Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: build/azure-pipelines/linux/product-build-linux.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: browser - VSCODE_RUN_BROWSER_TESTS: true - - job: Linuxx64RemoteTest - displayName: Remote Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: x64 + - template: build/azure-pipelines/linux/product-build-linux-ci.yml@self + parameters: + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_TEST_SUITE: Electron + - template: build/azure-pipelines/linux/product-build-linux-ci.yml@self + parameters: + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_TEST_SUITE: Browser + - template: build/azure-pipelines/linux/product-build-linux-ci.yml@self + parameters: + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_TEST_SUITE: Remote + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_LINUX, true), eq(parameters.VSCODE_BUILD_LINUX_SNAP, true))) }}: + - template: build/azure-pipelines/linux/product-build-linux.yml@self + parameters: NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: build/azure-pipelines/linux/product-build-linux.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: remote - VSCODE_RUN_REMOTE_TESTS: true - - - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX, true)) }}: - - job: Linuxx64 - timeoutInMinutes: 90 - variables: VSCODE_ARCH: x64 - NPM_ARCH: x64 - DISPLAY: ":10" - steps: - - template: build/azure-pipelines/linux/product-build-linux.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_RUN_ELECTRON_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_BROWSER_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_REMOTE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_ELECTRON_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_BROWSER_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_REMOTE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_BUILD_LINUX_SNAP: ${{ parameters.VSCODE_BUILD_LINUX_SNAP }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: - - job: LinuxArmhf - variables: - VSCODE_ARCH: armhf + - template: build/azure-pipelines/linux/product-build-linux.yml@self + parameters: NPM_ARCH: arm - steps: - - template: build/azure-pipelines/linux/product-build-linux.yml@self - parameters: - VSCODE_ARCH: armhf - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_ARCH: armhf + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: - - job: LinuxArm64 - variables: - VSCODE_ARCH: arm64 + - template: build/azure-pipelines/linux/product-build-linux.yml@self + parameters: NPM_ARCH: arm64 - steps: - - template: build/azure-pipelines/linux/product-build-linux.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_ARCH: arm64 + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_ALPINE'], true)) }}: - stage: Alpine @@ -529,26 +408,15 @@ extends: - Compile - ${{ if or(eq(parameters.VSCODE_BUILD_LINUX, true),eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true),eq(parameters.VSCODE_BUILD_LINUX_ARM64, true),eq(parameters.VSCODE_BUILD_ALPINE, true),eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true),eq(parameters.VSCODE_BUILD_MACOS, true),eq(parameters.VSCODE_BUILD_MACOS_ARM64, true),eq(parameters.VSCODE_BUILD_WIN32, true),eq(parameters.VSCODE_BUILD_WIN32_ARM64, true)) }}: - CompileCLI - pool: - name: 1es-ubuntu-22.04-x64 - os: linux jobs: - ${{ if eq(parameters.VSCODE_BUILD_ALPINE, true) }}: - - job: LinuxAlpine - variables: + - template: build/azure-pipelines/alpine/product-build-alpine.yml@self + parameters: VSCODE_ARCH: x64 - NPM_ARCH: x64 - steps: - - template: build/azure-pipelines/alpine/product-build-alpine.yml@self - - ${{ if eq(parameters.VSCODE_BUILD_ALPINE_ARM64, true) }}: - - job: LinuxAlpineArm64 - timeoutInMinutes: 120 - variables: + - template: build/azure-pipelines/alpine/product-build-alpine.yml@self + parameters: VSCODE_ARCH: arm64 - NPM_ARCH: arm64 - steps: - - template: build/azure-pipelines/alpine/product-build-alpine.yml@self - ${{ if and(eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_MACOS'], true)) }}: - stage: macOS @@ -563,123 +431,58 @@ extends: BUILDSECMON_OPT_IN: true jobs: - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - - job: macOSElectronTest - displayName: Electron Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: arm64 - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: electron - VSCODE_RUN_ELECTRON_TESTS: true - - job: macOSBrowserTest - displayName: Browser Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: arm64 - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: browser - VSCODE_RUN_BROWSER_TESTS: true - - job: macOSRemoteTest - displayName: Remote Tests - timeoutInMinutes: 30 - variables: - VSCODE_ARCH: arm64 - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_TEST_ARTIFACT_NAME: remote - VSCODE_RUN_REMOTE_TESTS: true + - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self + parameters: + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_TEST_SUITE: Electron + - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self + parameters: + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_TEST_SUITE: Browser + - template: build/azure-pipelines/darwin/product-build-darwin-ci.yml@self + parameters: + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_TEST_SUITE: Remote - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS, true)) }}: - - job: macOS - timeoutInMinutes: 90 - variables: + - template: build/azure-pipelines/darwin/product-build-darwin.yml@self + parameters: VSCODE_ARCH: x64 - BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: x64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - - - job: macOSCLI - timeoutInMinutes: 90 - steps: - - template: build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml@self - parameters: - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }} - VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true)) }}: - - job: macOSARM64 - timeoutInMinutes: 90 - variables: + - template: build/azure-pipelines/darwin/product-build-darwin.yml@self + parameters: VSCODE_ARCH: arm64 - BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ - steps: - - template: build/azure-pipelines/darwin/product-build-darwin.yml@self - parameters: - VSCODE_ARCH: arm64 - VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} - VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} - VSCODE_RUN_ELECTRON_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_BROWSER_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - VSCODE_RUN_REMOTE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_CIBUILD: ${{ variables.VSCODE_CIBUILD }} + VSCODE_RUN_ELECTRON_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_BROWSER_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} + VSCODE_RUN_REMOTE_TESTS: ${{ eq(parameters.VSCODE_STEP_ON_IT, false) }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(variables['VSCODE_BUILD_MACOS_UNIVERSAL'], true)) }}: - - job: macOSUniversal - timeoutInMinutes: 90 - variables: - VSCODE_ARCH: universal - BUILDS_API_URL: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ - steps: - - template: build/azure-pipelines/darwin/product-build-darwin-universal.yml@self + - template: build/azure-pipelines/darwin/product-build-darwin-universal.yml@self + + - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), or(eq(parameters.VSCODE_BUILD_MACOS, true), eq(parameters.VSCODE_BUILD_MACOS_ARM64, true))) }}: + - template: build/azure-pipelines/darwin/product-build-darwin-cli-sign.yml@self + parameters: + VSCODE_BUILD_MACOS: ${{ parameters.VSCODE_BUILD_MACOS }} + VSCODE_BUILD_MACOS_ARM64: ${{ parameters.VSCODE_BUILD_MACOS_ARM64 }} - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_COMPILE_ONLY, false), eq(variables['VSCODE_BUILD_STAGE_WEB'], true)) }}: - stage: Web dependsOn: - Compile - pool: - name: 1es-ubuntu-22.04-x64 - os: linux jobs: - - ${{ if eq(parameters.VSCODE_BUILD_WEB, true) }}: - - job: Web - variables: - VSCODE_ARCH: x64 - steps: - - template: build/azure-pipelines/web/product-build-web.yml@self + - template: build/azure-pipelines/web/product-build-web.yml@self - ${{ if eq(variables['VSCODE_PUBLISH'], 'true') }}: - stage: Publish dependsOn: [] - pool: - name: 1es-windows-2022-x64 - os: windows - variables: - - name: BUILDS_API_URL - value: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ jobs: - - job: PublishBuild - timeoutInMinutes: 180 - displayName: Publish Build - steps: - - template: build/azure-pipelines/product-publish.yml@self + - template: build/azure-pipelines/product-publish.yml@self + parameters: + VSCODE_QUALITY: ${{ variables.VSCODE_QUALITY }} + VSCODE_SCHEDULEDBUILD: ${{ variables.VSCODE_SCHEDULEDBUILD }} - ${{ if and(parameters.VSCODE_RELEASE, eq(variables['VSCODE_PRIVATE_BUILD'], false)) }}: - stage: ApproveRelease @@ -695,12 +498,10 @@ extends: - name: skipComponentGovernanceDetection value: true - - ${{ if or(and(parameters.VSCODE_RELEASE, eq(variables['VSCODE_PRIVATE_BUILD'], false)), and(in(parameters.VSCODE_QUALITY, 'insider', 'exploration'), eq(variables['VSCODE_SCHEDULEDBUILD'], true))) }}: - stage: Release dependsOn: - Publish - - ${{ if and(parameters.VSCODE_RELEASE, eq(variables['VSCODE_PRIVATE_BUILD'], false)) }}: - - ApproveRelease + - ApproveRelease pool: name: 1es-ubuntu-22.04-x64 os: linux diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index a69942b9d0ccd..09fee37b2ebae 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -1,176 +1,154 @@ -parameters: - - name: VSCODE_QUALITY - type: string - -steps: - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - template: ./distro/download-distro.yml@self - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - script: node build/setup-npm-registry.js $NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js compile $(node -p process.arch) > .build/packagelockhash - displayName: Prepare node_modules cache key - - - task: Cache@2 - inputs: - key: '"node_modules" | .build/packagelockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: sudo apt update -y && sudo apt install -y build-essential pkg-config libx11-dev libx11-xcb-dev libxkbfile-dev libnotify-bin libkrb5-dev - displayName: Install build tools - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: node build/azure-pipelines/distro/mixin-npm - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Mixin distro node modules - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive - - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: npm run compile - workingDirectory: build - displayName: Compile /build/ folder - - - script: .github/workflows/check-clean-git-state.sh - displayName: Check /build/ folder - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - - - template: common/install-builtin-extensions.yml@self - - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: npm exec -- npm-run-all -lp core-ci-pr extensions-ci-pr hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Compile & Hygiene (OSS) - - ${{ else }}: - - script: npm exec -- npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Compile & Hygiene (non-OSS) - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - npm run compile - displayName: Compile smoke test suites (non-OSS) - workingDirectory: test/smoke - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - - script: | - set -e - npm run compile - displayName: Compile integration test suites (non-OSS) - workingDirectory: test/integration/browser - condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - - - task: AzureCLI@2 - displayName: Fetch secrets - inputs: - azureSubscription: vscode - scriptType: pscore - scriptLocation: inlineScript - addSpnToEnvironment: true - inlineScript: | - Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" - Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" - Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" - - - script: | - set -e - AZURE_STORAGE_ACCOUNT="vscodeweb" \ - AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ - AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ - node build/azure-pipelines/upload-sourcemaps - displayName: Upload sourcemaps to Azure - - - script: ./build/azure-pipelines/common/extract-telemetry.sh - displayName: Generate lists of telemetry events - - - script: tar -cz --exclude='.build/node_modules_cache' --exclude='.build/node_modules_list.txt' --exclude='.build/distro' -f $(Build.ArtifactStagingDirectory)/compilation.tar.gz $(ls -d .build out-* test/integration/browser/out test/smoke/out test/automation/out 2>/dev/null) - displayName: Compress compilation artifact - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/compilation.tar.gz - artifactName: Compilation - sbomEnabled: false - displayName: Publish compilation artifact - - - script: npm run download-builtin-extensions-cg - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Download component details of built-in extensions - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: "Component Detection" - inputs: - sourceScanPath: $(Build.SourcesDirectory) - alertWarningLevel: Medium - continueOnError: true +jobs: + - job: Compile + timeoutInMinutes: 60 + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/compilation.tar.gz + artifactName: Compilation + displayName: Publish compilation artifact + isProduction: false + sbomEnabled: false + steps: + - template: ./common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ./distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - script: node build/setup-npm-registry.ts $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts compile $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: tar -xzf .build/node_modules_cache/cache.tgz + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Extract node_modules cache + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: node build/azure-pipelines/distro/mixin-npm.ts + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive + + - script: node build/azure-pipelines/distro/mixin-quality.ts + displayName: Mixin distro quality + + - template: common/install-builtin-extensions.yml@self + + - script: npm exec -- npm-run-all -lp core-ci extensions-ci hygiene eslint valid-layers-check define-class-fields-check vscode-dts-compile-check tsec-compile-check test-build-scripts + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Compile & Hygiene + + - script: | + set -e + npm run compile + displayName: Compile smoke test suites (non-OSS) + workingDirectory: test/smoke + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + + - script: | + set -e + npm run compile + displayName: Compile integration test suites (non-OSS) + workingDirectory: test/integration/browser + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + + - task: AzureCLI@2 + displayName: Fetch secrets + inputs: + azureSubscription: vscode + scriptType: pscore + scriptLocation: inlineScript + addSpnToEnvironment: true + inlineScript: | + Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" + Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" + Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" + + - script: | + set -e + AZURE_STORAGE_ACCOUNT="vscodeweb" \ + AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ + AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ + node build/azure-pipelines/upload-sourcemaps.ts + displayName: Upload sourcemaps to Azure + + - script: ./build/azure-pipelines/common/extract-telemetry.sh + displayName: Generate lists of telemetry events + + - script: tar -cz --exclude='.build/node_modules_cache' --exclude='.build/node_modules_list.txt' --exclude='.build/distro' -f $(Build.ArtifactStagingDirectory)/compilation.tar.gz $(ls -d .build out-* test/integration/browser/out test/smoke/out test/automation/out 2>/dev/null) + displayName: Compress compilation artifact + + - script: npm run download-builtin-extensions-cg + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download component details of built-in extensions + + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: "Component Detection" + inputs: + sourceScanPath: $(Build.SourcesDirectory) + alertWarningLevel: Medium + continueOnError: true diff --git a/build/azure-pipelines/product-npm-package-validate.yml b/build/azure-pipelines/product-npm-package-validate.yml index 05f2cd8e00eec..d596f9f7b3751 100644 --- a/build/azure-pipelines/product-npm-package-validate.yml +++ b/build/azure-pipelines/product-npm-package-validate.yml @@ -3,92 +3,109 @@ trigger: none pr: branches: include: ["main"] - paths: - include: ["package.json", "package-lock.json"] variables: - name: NPM_REGISTRY value: "https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/npm/registry/" - - name: VSCODE_CIBUILD - value: ${{ in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI') }} - name: VSCODE_QUALITY value: oss jobs: - - ${{ if ne(variables['VSCODE_CIBUILD'], true) }}: - - job: ValidateNpmPackage - displayName: Valiate NPM package against Terrapin - pool: - name: 1es-ubuntu-22.04-x64 - os: linux - timeoutInMinutes: 40000 - continueOnError: true - variables: - VSCODE_ARCH: x64 - steps: - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download + - job: ValidateNpmPackages + displayName: Valiate NPM packages against Terrapin + pool: + name: 1es-ubuntu-22.04-x64 + os: linux + timeoutInMinutes: 40000 + continueOnError: true + variables: + VSCODE_ARCH: x64 + steps: + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc - - script: node build/setup-npm-registry.js $NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry + - script: | + set -e + echo "Checking if package.json or package-lock.json files were modified..." - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - echo "NPMRC Path: $(npm config get userconfig)" - echo "NPM Registry: $(npm config get registry)" - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM + # Get the list of changed files in the PR + git fetch origin main + CHANGED_FILES=$(git diff --name-only origin/main...HEAD) + echo "Changed files:" + echo "$CHANGED_FILES" - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication + # Check if package.json or package-lock.json are in the changed files + if echo "$CHANGED_FILES" | grep -E '^(package\.json|package-lock\.json)$'; then + echo "##vso[task.setvariable variable=SHOULD_VALIDATE]true" + echo "Package files were modified, proceeding with validation" + else + echo "##vso[task.setvariable variable=SHOULD_VALIDATE]false" + echo "No package files were modified, skipping validation" + echo "##vso[task.complete result=Succeeded;]Pipeline completed successfully - no package validation needed" + fi + displayName: Check if package files were modified - - script: sudo apt update -y && sudo apt install -y build-essential pkg-config libx11-dev libx11-xcb-dev libxkbfile-dev libnotify-bin libkrb5-dev - displayName: Install build tools - condition: succeeded() + - script: node build/setup-npm-registry.ts $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none'), eq(variables['SHOULD_VALIDATE'], 'true')) + displayName: Setup NPM Registry - - script: | - set -e + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + echo "NPMRC Path: $(npm config get userconfig)" + echo "NPM Registry: $(npm config get registry)" + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none'), eq(variables['SHOULD_VALIDATE'], 'true')) + displayName: Setup NPM - for attempt in {1..6}; do - if [ $attempt -gt 1 ]; then - echo "Attempt $attempt: Waiting for 1 hour before retrying..." - sleep 3600 - fi + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none'), eq(variables['SHOULD_VALIDATE'], 'true')) + displayName: Setup NPM Authentication + + - script: sudo apt update -y && sudo apt install -y build-essential pkg-config libx11-dev libx11-xcb-dev libxkbfile-dev libnotify-bin libkrb5-dev + displayName: Install build tools + condition: and(succeeded(), eq(variables['SHOULD_VALIDATE'], 'true')) + + - script: | + set -e + + for attempt in {1..12}; do + if [ $attempt -gt 1 ]; then + echo "Attempt $attempt: Waiting for 30 minutes before retrying..." + sleep 1800 + fi - echo "Attempt $attempt: Running npm ci" - if npm i --ignore-scripts; then - if node build/npm/postinstall.js; then - echo "npm i succeeded on attempt $attempt" - exit 0 - else - echo "node build/npm/postinstall.js failed on attempt $attempt" - fi + echo "Attempt $attempt: Running npm ci" + if npm i --ignore-scripts; then + if node build/npm/postinstall.ts; then + echo "npm i succeeded on attempt $attempt" + exit 0 else - echo "npm i failed on attempt $attempt" + echo "node build/npm/postinstall.ts failed on attempt $attempt" fi - done + else + echo "npm i failed on attempt $attempt" + fi + done - echo "npm i failed after 6 attempts" - exit 1 - env: - npm_command: 'install --ignore-scripts' - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install dependencies with retries - timeoutInMinutes: 400 + echo "npm i failed after 12 attempts" + exit 1 + env: + npm_command: 'install --ignore-scripts' + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies with retries + timeoutInMinutes: 400 + condition: and(succeeded(), eq(variables['SHOULD_VALIDATE'], 'true')) - - script: .github/workflows/check-clean-git-state.sh - displayName: Check clean git state + - script: .github/workflows/check-clean-git-state.sh + displayName: Check clean git state + condition: and(succeeded(), eq(variables['SHOULD_VALIDATE'], 'true')) diff --git a/build/azure-pipelines/product-publish.yml b/build/azure-pipelines/product-publish.yml index 27d6c2b366b15..165fb177a9a3a 100644 --- a/build/azure-pipelines/product-publish.yml +++ b/build/azure-pipelines/product-publish.yml @@ -1,96 +1,116 @@ -steps: - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download +parameters: + - name: VSCODE_QUALITY + type: string + - name: VSCODE_SCHEDULEDBUILD + type: boolean - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" +jobs: + - job: PublishBuild + displayName: Publish Build + timeoutInMinutes: 180 + pool: + name: 1es-windows-2022-x64 + os: windows + variables: + - name: BUILDS_API_URL + value: $(System.CollectionUri)$(System.TeamProject)/_apis/build/builds/$(Build.BuildId)/ + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(Pipeline.Workspace)/artifacts_processed_$(System.StageAttempt)/artifacts_processed_$(System.StageAttempt).txt + artifactName: artifacts_processed_$(System.StageAttempt) + displayName: Publish the artifacts processed for this stage attempt + sbomEnabled: false + isProduction: false + condition: always() + steps: + - template: ./common/checkout.yml@self - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get ESRP Secrets" - inputs: - azureSubscription: vscode-esrp - KeyVaultName: vscode-esrp - SecretsFilter: esrp-auth,esrp-sign + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc - # allow-any-unicode-next-line - - pwsh: Write-Host "##vso[build.addbuildtag]🚀" - displayName: Add build tag + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" - - pwsh: | - npm ci - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install build dependencies + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get ESRP Secrets" + inputs: + azureSubscription: vscode-esrp + KeyVaultName: vscode-esrp + SecretsFilter: esrp-auth,esrp-sign - - download: current - patterns: "**/artifacts_processed_*.txt" - displayName: Download all artifacts_processed text files + # allow-any-unicode-next-line + - pwsh: Write-Host "##vso[build.addbuildtag]🚀" + displayName: Add build tag - - task: AzureCLI@2 - displayName: Fetch secrets - inputs: - azureSubscription: vscode - scriptType: pscore - scriptLocation: inlineScript - addSpnToEnvironment: true - inlineScript: | - Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" - Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" - Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" + - pwsh: | + npm ci + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install build dependencies - - pwsh: | - . build/azure-pipelines/win32/exec.ps1 + - download: current + patterns: "**/artifacts_processed_*.txt" + displayName: Download all artifacts_processed text files - if (Test-Path "$(Pipeline.Workspace)/artifacts_processed_*/artifacts_processed_*.txt") { - Write-Host "Artifacts already processed so a build must have already been created." - return - } + - task: AzureCLI@2 + displayName: Fetch secrets + inputs: + azureSubscription: vscode + scriptType: pscore + scriptLocation: inlineScript + addSpnToEnvironment: true + inlineScript: | + Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" + Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" + Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" - $VERSION = node -p "require('./package.json').version" - Write-Host "Creating build with version: $VERSION" - exec { node build/azure-pipelines/common/createBuild.js $VERSION } - env: - AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" - AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" - AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" - displayName: Create build if it hasn't been created before + - pwsh: | + . build/azure-pipelines/win32/exec.ps1 - - pwsh: | - $publishAuthTokens = (node build/azure-pipelines/common/getPublishAuthTokens) - Write-Host "##vso[task.setvariable variable=PUBLISH_AUTH_TOKENS;issecret=true]$publishAuthTokens" - env: - AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" - AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" - AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" - displayName: Get publish auth tokens + if (Test-Path "$(Pipeline.Workspace)/artifacts_processed_*/artifacts_processed_*.txt") { + Write-Host "Artifacts already processed so a build must have already been created." + return + } - - pwsh: node build/azure-pipelines/common/publish.js - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" - AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" - AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - PUBLISH_AUTH_TOKENS: "$(PUBLISH_AUTH_TOKENS)" - RELEASE_TENANT_ID: "$(ESRP_TENANT_ID)" - RELEASE_CLIENT_ID: "$(ESRP_CLIENT_ID)" - RELEASE_AUTH_CERT: "$(esrp-auth)" - RELEASE_REQUEST_SIGNING_CERT: "$(esrp-sign)" - displayName: Process artifacts - retryCountOnTaskFailure: 3 + $VERSION = node -p "require('./package.json').version" + Write-Host "Creating build with version: $VERSION" + exec { node build/azure-pipelines/common/createBuild.ts $VERSION } + env: + AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" + AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" + AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" + displayName: Create build if it hasn't been created before - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Pipeline.Workspace)/artifacts_processed_$(System.StageAttempt)/artifacts_processed_$(System.StageAttempt).txt - artifactName: artifacts_processed_$(System.StageAttempt) - sbomEnabled: false - displayName: Publish the artifacts processed for this stage attempt - condition: always() + - pwsh: | + $publishAuthTokens = (node build/azure-pipelines/common/getPublishAuthTokens.ts) + Write-Host "##vso[task.setvariable variable=PUBLISH_AUTH_TOKENS;issecret=true]$publishAuthTokens" + env: + AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" + AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" + AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" + displayName: Get publish auth tokens + + - pwsh: node build/azure-pipelines/common/publish.ts + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + PUBLISH_AUTH_TOKENS: "$(PUBLISH_AUTH_TOKENS)" + RELEASE_TENANT_ID: "$(ESRP_TENANT_ID)" + RELEASE_CLIENT_ID: "$(ESRP_CLIENT_ID)" + RELEASE_AUTH_CERT: "$(esrp-auth)" + RELEASE_REQUEST_SIGNING_CERT: "$(esrp-sign)" + displayName: Process artifacts + retryCountOnTaskFailure: 3 + + - ${{ if and(in(parameters.VSCODE_QUALITY, 'insider', 'exploration'), eq(parameters.VSCODE_SCHEDULEDBUILD, true)) }}: + - script: node build/azure-pipelines/common/releaseBuild.ts + env: + PUBLISH_AUTH_TOKENS: "$(PUBLISH_AUTH_TOKENS)" + displayName: Release build diff --git a/build/azure-pipelines/product-release.yml b/build/azure-pipelines/product-release.yml index 87896f9340b82..72b33a78ad1c2 100644 --- a/build/azure-pipelines/product-release.yml +++ b/build/azure-pipelines/product-release.yml @@ -3,11 +3,12 @@ parameters: type: boolean steps: + - template: ./common/checkout.yml@self + - task: NodeTool@0 inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - task: AzureCLI@2 displayName: Fetch secrets @@ -23,12 +24,18 @@ steps: - script: npm ci workingDirectory: build - displayName: Install /build dependencies + displayName: Install build dependencies + + - pwsh: | + $publishAuthTokens = (node build/azure-pipelines/common/getPublishAuthTokens.ts) + Write-Host "##vso[task.setvariable variable=PUBLISH_AUTH_TOKENS;issecret=true]$publishAuthTokens" + env: + AZURE_TENANT_ID: "$(AZURE_TENANT_ID)" + AZURE_CLIENT_ID: "$(AZURE_CLIENT_ID)" + AZURE_ID_TOKEN: "$(AZURE_ID_TOKEN)" + displayName: Get publish auth tokens - - script: | - set -e - AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ - AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ - node build/azure-pipelines/common/releaseBuild.js ${{ parameters.VSCODE_RELEASE }} + - script: node build/azure-pipelines/common/releaseBuild.ts ${{ parameters.VSCODE_RELEASE }} displayName: Release build + env: + PUBLISH_AUTH_TOKENS: "$(PUBLISH_AUTH_TOKENS)" diff --git a/build/azure-pipelines/publish-types/check-version.js b/build/azure-pipelines/publish-types/check-version.js deleted file mode 100644 index 5bd80a69bbfca..0000000000000 --- a/build/azure-pipelines/publish-types/check-version.js +++ /dev/null @@ -1,40 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const child_process_1 = __importDefault(require("child_process")); -let tag = ''; -try { - tag = child_process_1.default - .execSync('git describe --tags `git rev-list --tags --max-count=1`') - .toString() - .trim(); - if (!isValidTag(tag)) { - throw Error(`Invalid tag ${tag}`); - } -} -catch (err) { - console.error(err); - console.error('Failed to update types'); - process.exit(1); -} -function isValidTag(t) { - if (t.split('.').length !== 3) { - return false; - } - const [major, minor, bug] = t.split('.'); - // Only release for tags like 1.34.0 - if (bug !== '0') { - return false; - } - if (isNaN(parseInt(major, 10)) || isNaN(parseInt(minor, 10))) { - return false; - } - return true; -} -//# sourceMappingURL=check-version.js.map \ No newline at end of file diff --git a/build/azure-pipelines/publish-types/publish-types.yml b/build/azure-pipelines/publish-types/publish-types.yml index 5f60ae5a2624c..25dbf1f185a41 100644 --- a/build/azure-pipelines/publish-types/publish-types.yml +++ b/build/azure-pipelines/publish-types/publish-types.yml @@ -14,7 +14,6 @@ steps: inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - bash: | TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) @@ -35,7 +34,7 @@ steps: - bash: | # Install build dependencies (cd build && npm ci) - node build/azure-pipelines/publish-types/check-version.js + node build/azure-pipelines/publish-types/check-version.ts displayName: Check version - bash: | @@ -43,7 +42,7 @@ steps: git config --global user.name "VSCode" git clone https://$(GITHUB_TOKEN)@github.com/DefinitelyTyped/DefinitelyTyped.git --depth=1 - node build/azure-pipelines/publish-types/update-types.js + node build/azure-pipelines/publish-types/update-types.ts TAG_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`) diff --git a/build/azure-pipelines/publish-types/update-types.js b/build/azure-pipelines/publish-types/update-types.js deleted file mode 100644 index 29f9bfcf66eb1..0000000000000 --- a/build/azure-pipelines/publish-types/update-types.js +++ /dev/null @@ -1,76 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const fs_1 = __importDefault(require("fs")); -const child_process_1 = __importDefault(require("child_process")); -const path_1 = __importDefault(require("path")); -let tag = ''; -try { - tag = child_process_1.default - .execSync('git describe --tags `git rev-list --tags --max-count=1`') - .toString() - .trim(); - const dtsUri = `https://raw.githubusercontent.com/microsoft/vscode/${tag}/src/vscode-dts/vscode.d.ts`; - const outPath = path_1.default.resolve(process.cwd(), 'DefinitelyTyped/types/vscode/index.d.ts'); - child_process_1.default.execSync(`curl ${dtsUri} --output ${outPath}`); - updateDTSFile(outPath, tag); - console.log(`Done updating vscode.d.ts at ${outPath}`); -} -catch (err) { - console.error(err); - console.error('Failed to update types'); - process.exit(1); -} -function updateDTSFile(outPath, tag) { - const oldContent = fs_1.default.readFileSync(outPath, 'utf-8'); - const newContent = getNewFileContent(oldContent, tag); - fs_1.default.writeFileSync(outPath, newContent); -} -function repeat(str, times) { - const result = new Array(times); - for (let i = 0; i < times; i++) { - result[i] = str; - } - return result.join(''); -} -function convertTabsToSpaces(str) { - return str.replace(/\t/gm, value => repeat(' ', value.length)); -} -function getNewFileContent(content, tag) { - const oldheader = [ - `/*---------------------------------------------------------------------------------------------`, - ` * Copyright (c) Microsoft Corporation. All rights reserved.`, - ` * Licensed under the MIT License. See License.txt in the project root for license information.`, - ` *--------------------------------------------------------------------------------------------*/` - ].join('\n'); - return convertTabsToSpaces(getNewFileHeader(tag) + content.slice(oldheader.length)); -} -function getNewFileHeader(tag) { - const [major, minor] = tag.split('.'); - const shorttag = `${major}.${minor}`; - const header = [ - `// Type definitions for Visual Studio Code ${shorttag}`, - `// Project: https://github.com/microsoft/vscode`, - `// Definitions by: Visual Studio Code Team, Microsoft `, - `// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped`, - ``, - `/*---------------------------------------------------------------------------------------------`, - ` * Copyright (c) Microsoft Corporation. All rights reserved.`, - ` * Licensed under the MIT License.`, - ` * See https://github.com/microsoft/vscode/blob/main/LICENSE.txt for license information.`, - ` *--------------------------------------------------------------------------------------------*/`, - ``, - `/**`, - ` * Type Definition for Visual Studio Code ${shorttag} Extension API`, - ` * See https://code.visualstudio.com/api for more information`, - ` */` - ].join('\n'); - return header; -} -//# sourceMappingURL=update-types.js.map \ No newline at end of file diff --git a/build/azure-pipelines/publish-types/update-types.ts b/build/azure-pipelines/publish-types/update-types.ts index 0f99b07cf9a3b..05482ab452e3b 100644 --- a/build/azure-pipelines/publish-types/update-types.ts +++ b/build/azure-pipelines/publish-types/update-types.ts @@ -14,22 +14,30 @@ try { .toString() .trim(); + const [major, minor] = tag.split('.'); + const shorttag = `${major}.${minor}`; + const dtsUri = `https://raw.githubusercontent.com/microsoft/vscode/${tag}/src/vscode-dts/vscode.d.ts`; - const outPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/vscode/index.d.ts'); - cp.execSync(`curl ${dtsUri} --output ${outPath}`); + const outDtsPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/vscode/index.d.ts'); + cp.execSync(`curl ${dtsUri} --output ${outDtsPath}`); + + updateDTSFile(outDtsPath, shorttag); - updateDTSFile(outPath, tag); + const outPackageJsonPath = path.resolve(process.cwd(), 'DefinitelyTyped/types/vscode/package.json'); + const packageJson = JSON.parse(fs.readFileSync(outPackageJsonPath, 'utf-8')); + packageJson.version = shorttag + '.9999'; + fs.writeFileSync(outPackageJsonPath, JSON.stringify(packageJson, null, 2) + '\n'); - console.log(`Done updating vscode.d.ts at ${outPath}`); + console.log(`Done updating vscode.d.ts at ${outDtsPath} and package.json to version ${packageJson.version}`); } catch (err) { console.error(err); console.error('Failed to update types'); process.exit(1); } -function updateDTSFile(outPath: string, tag: string) { +function updateDTSFile(outPath: string, shorttag: string) { const oldContent = fs.readFileSync(outPath, 'utf-8'); - const newContent = getNewFileContent(oldContent, tag); + const newContent = getNewFileContent(oldContent, shorttag); fs.writeFileSync(outPath, newContent); } @@ -46,7 +54,7 @@ function convertTabsToSpaces(str: string): string { return str.replace(/\t/gm, value => repeat(' ', value.length)); } -function getNewFileContent(content: string, tag: string) { +function getNewFileContent(content: string, shorttag: string) { const oldheader = [ `/*---------------------------------------------------------------------------------------------`, ` * Copyright (c) Microsoft Corporation. All rights reserved.`, @@ -54,13 +62,10 @@ function getNewFileContent(content: string, tag: string) { ` *--------------------------------------------------------------------------------------------*/` ].join('\n'); - return convertTabsToSpaces(getNewFileHeader(tag) + content.slice(oldheader.length)); + return convertTabsToSpaces(getNewFileHeader(shorttag) + content.slice(oldheader.length)); } -function getNewFileHeader(tag: string) { - const [major, minor] = tag.split('.'); - const shorttag = `${major}.${minor}`; - +function getNewFileHeader(shorttag: string) { const header = [ `// Type definitions for Visual Studio Code ${shorttag}`, `// Project: https://github.com/microsoft/vscode`, diff --git a/build/azure-pipelines/upload-cdn.js b/build/azure-pipelines/upload-cdn.js deleted file mode 100644 index f8247450f2580..0000000000000 --- a/build/azure-pipelines/upload-cdn.js +++ /dev/null @@ -1,120 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const event_stream_1 = __importDefault(require("event-stream")); -const vinyl_1 = __importDefault(require("vinyl")); -const vinyl_fs_1 = __importDefault(require("vinyl-fs")); -const gulp_filter_1 = __importDefault(require("gulp-filter")); -const gulp_gzip_1 = __importDefault(require("gulp-gzip")); -const mime_1 = __importDefault(require("mime")); -const identity_1 = require("@azure/identity"); -const azure = require('gulp-azure-storage'); -const commit = process.env['BUILD_SOURCEVERSION']; -const credential = new identity_1.ClientAssertionCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], () => Promise.resolve(process.env['AZURE_ID_TOKEN'])); -mime_1.default.define({ - 'application/typescript': ['ts'], - 'application/json': ['code-snippets'], -}); -// From default AFD configuration -const MimeTypesToCompress = new Set([ - 'application/eot', - 'application/font', - 'application/font-sfnt', - 'application/javascript', - 'application/json', - 'application/opentype', - 'application/otf', - 'application/pkcs7-mime', - 'application/truetype', - 'application/ttf', - 'application/typescript', - 'application/vnd.ms-fontobject', - 'application/xhtml+xml', - 'application/xml', - 'application/xml+rss', - 'application/x-font-opentype', - 'application/x-font-truetype', - 'application/x-font-ttf', - 'application/x-httpd-cgi', - 'application/x-javascript', - 'application/x-mpegurl', - 'application/x-opentype', - 'application/x-otf', - 'application/x-perl', - 'application/x-ttf', - 'font/eot', - 'font/ttf', - 'font/otf', - 'font/opentype', - 'image/svg+xml', - 'text/css', - 'text/csv', - 'text/html', - 'text/javascript', - 'text/js', - 'text/markdown', - 'text/plain', - 'text/richtext', - 'text/tab-separated-values', - 'text/xml', - 'text/x-script', - 'text/x-component', - 'text/x-java-source' -]); -function wait(stream) { - return new Promise((c, e) => { - stream.on('end', () => c()); - stream.on('error', (err) => e(err)); - }); -} -async function main() { - const files = []; - const options = (compressed) => ({ - account: process.env.AZURE_STORAGE_ACCOUNT, - credential, - container: '$web', - prefix: `${process.env.VSCODE_QUALITY}/${commit}/`, - contentSettings: { - contentEncoding: compressed ? 'gzip' : undefined, - cacheControl: 'max-age=31536000, public' - } - }); - const all = vinyl_fs_1.default.src('**', { cwd: '../vscode-web', base: '../vscode-web', dot: true }) - .pipe((0, gulp_filter_1.default)(f => !f.isDirectory())); - const compressed = all - .pipe((0, gulp_filter_1.default)(f => MimeTypesToCompress.has(mime_1.default.lookup(f.path)))) - .pipe((0, gulp_gzip_1.default)({ append: false })) - .pipe(azure.upload(options(true))); - const uncompressed = all - .pipe((0, gulp_filter_1.default)(f => !MimeTypesToCompress.has(mime_1.default.lookup(f.path)))) - .pipe(azure.upload(options(false))); - const out = event_stream_1.default.merge(compressed, uncompressed) - .pipe(event_stream_1.default.through(function (f) { - console.log('Uploaded:', f.relative); - files.push(f.relative); - this.emit('data', f); - })); - console.log(`Uploading files to CDN...`); // debug - await wait(out); - const listing = new vinyl_1.default({ - path: 'files.txt', - contents: Buffer.from(files.join('\n')), - stat: { mode: 0o666 } - }); - const filesOut = event_stream_1.default.readArray([listing]) - .pipe((0, gulp_gzip_1.default)({ append: false })) - .pipe(azure.upload(options(true))); - console.log(`Uploading: files.txt (${files.length} files)`); // debug - await wait(filesOut); -} -main().catch(err => { - console.error(err); - process.exit(1); -}); -//# sourceMappingURL=upload-cdn.js.map \ No newline at end of file diff --git a/build/azure-pipelines/upload-cdn.ts b/build/azure-pipelines/upload-cdn.ts index 61d7cea523ca0..e3a715b4e53c0 100644 --- a/build/azure-pipelines/upload-cdn.ts +++ b/build/azure-pipelines/upload-cdn.ts @@ -10,7 +10,8 @@ import filter from 'gulp-filter'; import gzip from 'gulp-gzip'; import mime from 'mime'; import { ClientAssertionCredential } from '@azure/identity'; -const azure = require('gulp-azure-storage'); +import { VinylStat } from '../lib/util.ts'; +import azure from 'gulp-azure-storage'; const commit = process.env['BUILD_SOURCEVERSION']; const credential = new ClientAssertionCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, () => Promise.resolve(process.env['AZURE_ID_TOKEN']!)); @@ -70,7 +71,7 @@ const MimeTypesToCompress = new Set([ function wait(stream: es.ThroughStream): Promise { return new Promise((c, e) => { stream.on('end', () => c()); - stream.on('error', (err: any) => e(err)); + stream.on('error', (err) => e(err)); }); } @@ -112,7 +113,7 @@ async function main(): Promise { const listing = new Vinyl({ path: 'files.txt', contents: Buffer.from(files.join('\n')), - stat: { mode: 0o666 } as any + stat: new VinylStat({ mode: 0o666 }) }); const filesOut = es.readArray([listing]) diff --git a/build/azure-pipelines/upload-nlsmetadata.js b/build/azure-pipelines/upload-nlsmetadata.js deleted file mode 100644 index e89a6497d704f..0000000000000 --- a/build/azure-pipelines/upload-nlsmetadata.js +++ /dev/null @@ -1,127 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const event_stream_1 = __importDefault(require("event-stream")); -const vinyl_fs_1 = __importDefault(require("vinyl-fs")); -const gulp_merge_json_1 = __importDefault(require("gulp-merge-json")); -const gulp_gzip_1 = __importDefault(require("gulp-gzip")); -const identity_1 = require("@azure/identity"); -const path = require("path"); -const fs_1 = require("fs"); -const azure = require('gulp-azure-storage'); -const commit = process.env['BUILD_SOURCEVERSION']; -const credential = new identity_1.ClientAssertionCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], () => Promise.resolve(process.env['AZURE_ID_TOKEN'])); -function main() { - return new Promise((c, e) => { - const combinedMetadataJson = event_stream_1.default.merge( - // vscode: we are not using `out-build/nls.metadata.json` here because - // it includes metadata for translators for `keys`. but for our purpose - // we want only the `keys` and `messages` as `string`. - event_stream_1.default.merge(vinyl_fs_1.default.src('out-build/nls.keys.json', { base: 'out-build' }), vinyl_fs_1.default.src('out-build/nls.messages.json', { base: 'out-build' })) - .pipe((0, gulp_merge_json_1.default)({ - fileName: 'vscode.json', - jsonSpace: '', - concatArrays: true, - edit: (parsedJson, file) => { - if (file.base === 'out-build') { - if (file.basename === 'nls.keys.json') { - return { keys: parsedJson }; - } - else { - return { messages: parsedJson }; - } - } - } - })), - // extensions - vinyl_fs_1.default.src('.build/extensions/**/nls.metadata.json', { base: '.build/extensions' }), vinyl_fs_1.default.src('.build/extensions/**/nls.metadata.header.json', { base: '.build/extensions' }), vinyl_fs_1.default.src('.build/extensions/**/package.nls.json', { base: '.build/extensions' })).pipe((0, gulp_merge_json_1.default)({ - fileName: 'combined.nls.metadata.json', - jsonSpace: '', - concatArrays: true, - edit: (parsedJson, file) => { - if (file.basename === 'vscode.json') { - return { vscode: parsedJson }; - } - // Handle extensions and follow the same structure as the Core nls file. - switch (file.basename) { - case 'package.nls.json': - // put package.nls.json content in Core NlsMetadata format - // language packs use the key "package" to specify that - // translations are for the package.json file - parsedJson = { - messages: { - package: Object.values(parsedJson) - }, - keys: { - package: Object.keys(parsedJson) - }, - bundles: { - main: ['package'] - } - }; - break; - case 'nls.metadata.header.json': - parsedJson = { header: parsedJson }; - break; - case 'nls.metadata.json': { - // put nls.metadata.json content in Core NlsMetadata format - const modules = Object.keys(parsedJson); - const json = { - keys: {}, - messages: {}, - bundles: { - main: [] - } - }; - for (const module of modules) { - json.messages[module] = parsedJson[module].messages; - json.keys[module] = parsedJson[module].keys; - json.bundles.main.push(module); - } - parsedJson = json; - break; - } - } - // Get extension id and use that as the key - const folderPath = path.join(file.base, file.relative.split('/')[0]); - const manifest = (0, fs_1.readFileSync)(path.join(folderPath, 'package.json'), 'utf-8'); - const manifestJson = JSON.parse(manifest); - const key = manifestJson.publisher + '.' + manifestJson.name; - return { [key]: parsedJson }; - }, - })); - const nlsMessagesJs = vinyl_fs_1.default.src('out-build/nls.messages.js', { base: 'out-build' }); - event_stream_1.default.merge(combinedMetadataJson, nlsMessagesJs) - .pipe((0, gulp_gzip_1.default)({ append: false })) - .pipe(vinyl_fs_1.default.dest('./nlsMetadata')) - .pipe(event_stream_1.default.through(function (data) { - console.log(`Uploading ${data.path}`); - // trigger artifact upload - console.log(`##vso[artifact.upload containerfolder=nlsmetadata;artifactname=${data.basename}]${data.path}`); - this.emit('data', data); - })) - .pipe(azure.upload({ - account: process.env.AZURE_STORAGE_ACCOUNT, - credential, - container: '$web', - prefix: `nlsmetadata/${commit}/`, - contentSettings: { - contentEncoding: 'gzip', - cacheControl: 'max-age=31536000, public' - } - })) - .on('end', () => c()) - .on('error', (err) => e(err)); - }); -} -main().catch(err => { - console.error(err); - process.exit(1); -}); -//# sourceMappingURL=upload-nlsmetadata.js.map \ No newline at end of file diff --git a/build/azure-pipelines/upload-nlsmetadata.ts b/build/azure-pipelines/upload-nlsmetadata.ts index 1a4f2665617d8..9d6a803e1697a 100644 --- a/build/azure-pipelines/upload-nlsmetadata.ts +++ b/build/azure-pipelines/upload-nlsmetadata.ts @@ -9,9 +9,9 @@ import vfs from 'vinyl-fs'; import merge from 'gulp-merge-json'; import gzip from 'gulp-gzip'; import { ClientAssertionCredential } from '@azure/identity'; -import path = require('path'); +import path from 'path'; import { readFileSync } from 'fs'; -const azure = require('gulp-azure-storage'); +import azure from 'gulp-azure-storage'; const commit = process.env['BUILD_SOURCEVERSION']; const credential = new ClientAssertionCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, () => Promise.resolve(process.env['AZURE_ID_TOKEN']!)); @@ -134,7 +134,7 @@ function main(): Promise { } })) .on('end', () => c()) - .on('error', (err: any) => e(err)); + .on('error', (err: unknown) => e(err)); }); } diff --git a/build/azure-pipelines/upload-sourcemaps.js b/build/azure-pipelines/upload-sourcemaps.js deleted file mode 100644 index cac1ae3caf205..0000000000000 --- a/build/azure-pipelines/upload-sourcemaps.js +++ /dev/null @@ -1,101 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const path_1 = __importDefault(require("path")); -const event_stream_1 = __importDefault(require("event-stream")); -const vinyl_fs_1 = __importDefault(require("vinyl-fs")); -const util = __importStar(require("../lib/util")); -const dependencies_1 = require("../lib/dependencies"); -const identity_1 = require("@azure/identity"); -const azure = require('gulp-azure-storage'); -const root = path_1.default.dirname(path_1.default.dirname(__dirname)); -const commit = process.env['BUILD_SOURCEVERSION']; -const credential = new identity_1.ClientAssertionCredential(process.env['AZURE_TENANT_ID'], process.env['AZURE_CLIENT_ID'], () => Promise.resolve(process.env['AZURE_ID_TOKEN'])); -// optionally allow to pass in explicit base/maps to upload -const [, , base, maps] = process.argv; -function src(base, maps = `${base}/**/*.map`) { - return vinyl_fs_1.default.src(maps, { base }) - .pipe(event_stream_1.default.mapSync((f) => { - f.path = `${f.base}/core/${f.relative}`; - return f; - })); -} -function main() { - const sources = []; - // vscode client maps (default) - if (!base) { - const vs = src('out-vscode-min'); // client source-maps only - sources.push(vs); - const productionDependencies = (0, dependencies_1.getProductionDependencies)(root); - const productionDependenciesSrc = productionDependencies.map((d) => path_1.default.relative(root, d)).map((d) => `./${d}/**/*.map`); - const nodeModules = vinyl_fs_1.default.src(productionDependenciesSrc, { base: '.' }) - .pipe(util.cleanNodeModules(path_1.default.join(root, 'build', '.moduleignore'))) - .pipe(util.cleanNodeModules(path_1.default.join(root, 'build', `.moduleignore.${process.platform}`))); - sources.push(nodeModules); - const extensionsOut = vinyl_fs_1.default.src(['.build/extensions/**/*.js.map', '!**/node_modules/**'], { base: '.build' }); - sources.push(extensionsOut); - } - // specific client base/maps - else { - sources.push(src(base, maps)); - } - return new Promise((c, e) => { - event_stream_1.default.merge(...sources) - .pipe(event_stream_1.default.through(function (data) { - console.log('Uploading Sourcemap', data.relative); // debug - this.emit('data', data); - })) - .pipe(azure.upload({ - account: process.env.AZURE_STORAGE_ACCOUNT, - credential, - container: '$web', - prefix: `sourcemaps/${commit}/` - })) - .on('end', () => c()) - .on('error', (err) => e(err)); - }); -} -main().catch(err => { - console.error(err); - process.exit(1); -}); -//# sourceMappingURL=upload-sourcemaps.js.map \ No newline at end of file diff --git a/build/azure-pipelines/upload-sourcemaps.ts b/build/azure-pipelines/upload-sourcemaps.ts index 0c51827fef417..d5a72de54bf03 100644 --- a/build/azure-pipelines/upload-sourcemaps.ts +++ b/build/azure-pipelines/upload-sourcemaps.ts @@ -7,12 +7,13 @@ import path from 'path'; import es from 'event-stream'; import Vinyl from 'vinyl'; import vfs from 'vinyl-fs'; -import * as util from '../lib/util'; -import { getProductionDependencies } from '../lib/dependencies'; +import * as util from '../lib/util.ts'; +import { getProductionDependencies } from '../lib/dependencies.ts'; import { ClientAssertionCredential } from '@azure/identity'; -const azure = require('gulp-azure-storage'); +import Stream from 'stream'; +import azure from 'gulp-azure-storage'; -const root = path.dirname(path.dirname(__dirname)); +const root = path.dirname(path.dirname(import.meta.dirname)); const commit = process.env['BUILD_SOURCEVERSION']; const credential = new ClientAssertionCredential(process.env['AZURE_TENANT_ID']!, process.env['AZURE_CLIENT_ID']!, () => Promise.resolve(process.env['AZURE_ID_TOKEN']!)); @@ -28,7 +29,7 @@ function src(base: string, maps = `${base}/**/*.map`) { } function main(): Promise { - const sources: any[] = []; + const sources: Stream[] = []; // vscode client maps (default) if (!base) { @@ -64,7 +65,7 @@ function main(): Promise { prefix: `sourcemaps/${commit}/` })) .on('end', () => c()) - .on('error', (err: any) => e(err)); + .on('error', (err) => e(err)); }); } diff --git a/build/azure-pipelines/web/product-build-web-node-modules.yml b/build/azure-pipelines/web/product-build-web-node-modules.yml new file mode 100644 index 0000000000000..75a0cc6cd6e75 --- /dev/null +++ b/build/azure-pipelines/web/product-build-web-node-modules.yml @@ -0,0 +1,90 @@ +jobs: + - job: WebNodeModules + displayName: Web + pool: + name: 1es-ubuntu-22.04-x64 + os: linux + timeoutInMinutes: 60 + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - script: node build/setup-npm-registry.ts $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts web $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get update + ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get install -y libkrb5-dev + displayName: Setup system services + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: node build/azure-pipelines/distro/mixin-npm.ts + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 3f94460dfafc0..71932745be7fb 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -1,173 +1,169 @@ -steps: - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - template: ../distro/download-distro.yml@self - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz - displayName: Extract compilation output - - - script: node build/setup-npm-registry.js $NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.js web $(node -p process.arch) > .build/packagelockhash - displayName: Prepare node_modules cache key - - - task: Cache@2 - inputs: - key: '"node_modules" | .build/packagelockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - script: tar -xzf .build/node_modules_cache/cache.tgz - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - script: | - set -e - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - npm config set registry "$NPM_REGISTRY" - echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - script: | - set -e - ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get update - ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get install -y libkrb5-dev - displayName: Setup system services - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e - - for i in {1..5}; do # try 5 times - npm ci && break - if [ $i -eq 5 ]; then - echo "Npm install failed too many times" >&2 - exit 1 - fi - echo "Npm install failed $i, trying again..." - done - env: - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: node build/azure-pipelines/distro/mixin-npm - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Mixin distro node modules - - - script: | - set -e - node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt - mkdir -p .build/node_modules_cache - tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive - - - script: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - - - template: ../common/install-builtin-extensions.yml@self - - - script: | - set -e - npm run gulp vscode-web-min-ci - ARCHIVE_PATH=".build/web/vscode-web.tar.gz" - mkdir -p $(dirname $ARCHIVE_PATH) - tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-web - echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build - - - task: AzureCLI@2 - displayName: Fetch secrets from Azure - inputs: - azureSubscription: vscode - scriptType: pscore - scriptLocation: inlineScript - addSpnToEnvironment: true - inlineScript: | - Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" - Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" - Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" - - - script: | - set -e - AZURE_STORAGE_ACCOUNT="vscodeweb" \ - AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ - AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ - node build/azure-pipelines/upload-cdn - displayName: Upload to CDN - - - script: | - set -e - AZURE_STORAGE_ACCOUNT="vscodeweb" \ - AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ - AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ - node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.js.map - displayName: Upload sourcemaps (Web Main) - - - script: | - set -e - AZURE_STORAGE_ACCOUNT="vscodeweb" \ - AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ - AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ - node build/azure-pipelines/upload-sourcemaps out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.internal.js.map - displayName: Upload sourcemaps (Web Internal) - - - script: | - set -e - AZURE_STORAGE_ACCOUNT="vscodeweb" \ - AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ - AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ - AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ - node build/azure-pipelines/upload-nlsmetadata - displayName: Upload NLS Metadata - - - script: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" - condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) - displayName: Generate artifact prefix - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_linux_standalone_archive-unsigned - sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-web - sbomPackageName: "VS Code Web" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) - displayName: Publish web archive +jobs: + - job: Web + displayName: Web + timeoutInMinutes: 30 + pool: + name: 1es-ubuntu-22.04-x64 + os: linux + variables: + VSCODE_ARCH: x64 + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/web/vscode-web.tar.gz + artifactName: vscode_web_linux_standalone_archive-unsigned + displayName: Publish web archive + sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-web + sbomPackageName: "VS Code Linux x64 Web (Standalone)" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output + + - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz + displayName: Extract compilation output + + - script: node build/setup-npm-registry.ts $NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - script: mkdir -p .build && node build/azure-pipelines/common/computeNodeModulesCacheKey.ts web $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - script: tar -xzf .build/node_modules_cache/cache.tgz + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Extract node_modules cache + + - script: | + set -e + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + npm config set registry "$NPM_REGISTRY" + echo "##vso[task.setvariable variable=NPMRC_PATH]$(npm config get userconfig)" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - script: | + set -e + ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get update + ./build/azure-pipelines/linux/apt-retry.sh sudo apt-get install -y libkrb5-dev + displayName: Setup system services + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: | + set -e + + for i in {1..5}; do # try 5 times + npm ci && break + if [ $i -eq 5 ]; then + echo "Npm install failed too many times" >&2 + exit 1 + fi + echo "Npm install failed $i, trying again..." + done + env: + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - script: node build/azure-pipelines/distro/mixin-npm.ts + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + + - script: | + set -e + node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt + mkdir -p .build/node_modules_cache + tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive + + - script: node build/azure-pipelines/distro/mixin-quality.ts + displayName: Mixin distro quality + + - template: ../common/install-builtin-extensions.yml@self + + - script: | + set -e + npm run gulp vscode-web-min-ci + ARCHIVE_PATH="$(Build.ArtifactStagingDirectory)/out/web/vscode-web.tar.gz" + mkdir -p $(dirname $ARCHIVE_PATH) + tar --owner=0 --group=0 -czf $ARCHIVE_PATH -C .. vscode-web + echo "##vso[task.setvariable variable=WEB_PATH]$ARCHIVE_PATH" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build + + - task: AzureCLI@2 + displayName: Fetch secrets from Azure + inputs: + azureSubscription: vscode + scriptType: pscore + scriptLocation: inlineScript + addSpnToEnvironment: true + inlineScript: | + Write-Host "##vso[task.setvariable variable=AZURE_TENANT_ID]$env:tenantId" + Write-Host "##vso[task.setvariable variable=AZURE_CLIENT_ID]$env:servicePrincipalId" + Write-Host "##vso[task.setvariable variable=AZURE_ID_TOKEN;issecret=true]$env:idToken" + + - script: | + set -e + AZURE_STORAGE_ACCOUNT="vscodeweb" \ + AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ + AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ + node build/azure-pipelines/upload-cdn.ts + displayName: Upload to CDN + + - script: | + set -e + AZURE_STORAGE_ACCOUNT="vscodeweb" \ + AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ + AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ + node build/azure-pipelines/upload-sourcemaps.ts out-vscode-web-min out-vscode-web-min/vs/workbench/workbench.web.main.internal.js.map + displayName: Upload sourcemaps (Web) + + - script: | + set -e + AZURE_STORAGE_ACCOUNT="vscodeweb" \ + AZURE_TENANT_ID="$(AZURE_TENANT_ID)" \ + AZURE_CLIENT_ID="$(AZURE_CLIENT_ID)" \ + AZURE_ID_TOKEN="$(AZURE_ID_TOKEN)" \ + node build/azure-pipelines/upload-nlsmetadata.ts + displayName: Upload NLS Metadata diff --git a/build/azure-pipelines/win32/cli-build-win32.yml b/build/azure-pipelines/win32/cli-build-win32.yml deleted file mode 100644 index d61f0e722f5ff..0000000000000 --- a/build/azure-pipelines/win32/cli-build-win32.yml +++ /dev/null @@ -1,91 +0,0 @@ -parameters: - - name: VSCODE_BUILD_WIN32 - type: boolean - default: false - - name: VSCODE_BUILD_WIN32_ARM64 - type: boolean - default: false - - name: VSCODE_CHECK_ONLY - type: boolean - default: false - - name: VSCODE_QUALITY - type: string - -steps: - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - template: ../cli/cli-apply-patches.yml@self - - - task: Npm@1 - displayName: Download openssl prebuilt - inputs: - command: custom - customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 - customRegistry: useFeed - customFeed: "Monaco/openssl-prebuilt" - workingDir: $(Build.ArtifactStagingDirectory) - - - powershell: | - mkdir $(Build.ArtifactStagingDirectory)/openssl - tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl - displayName: Extract openssl prebuilt - - - template: ../cli/install-rust-win32.yml@self - parameters: - targets: - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - x86_64-pc-windows-msvc - - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: - - aarch64-pc-windows-msvc - - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_TARGET: x86_64-pc-windows-msvc - VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_x64_cli - VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-windows-static/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/x64-windows-static/include - RUSTFLAGS: "-Ctarget-feature=+crt-static -Clink-args=/guard:cf -Clink-args=/CETCOMPAT" - CFLAGS: "/guard:cf /Qspectre" - - - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: - - template: ../cli/cli-compile.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_CLI_TARGET: aarch64-pc-windows-msvc - VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_arm64_cli - VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} - VSCODE_CLI_ENV: - OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static/lib - OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/arm64-windows-static/include - RUSTFLAGS: "-C target-feature=+crt-static -Clink-args=/guard:cf -Clink-args=/CETCOMPAT:NO" - CFLAGS: "/guard:cf /Qspectre" - - - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: - - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_win32_arm64_cli.zip - artifactName: unsigned_vscode_cli_win32_arm64_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Windows arm64 CLI (unsigned)" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish unsigned_vscode_cli_win32_arm64_cli artifact - - - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_win32_x64_cli.zip - artifactName: unsigned_vscode_cli_win32_x64_cli - sbomBuildDropPath: $(Build.ArtifactStagingDirectory)/cli - sbomPackageName: "VS Code Windows x64 CLI (unsigned)" - sbomPackageVersion: $(Build.SourceVersion) - displayName: Publish unsigned_vscode_cli_win32_x64_cli artifact diff --git a/build/azure-pipelines/win32/codesign.js b/build/azure-pipelines/win32/codesign.js deleted file mode 100644 index 630f9a64ba15f..0000000000000 --- a/build/azure-pipelines/win32/codesign.js +++ /dev/null @@ -1,73 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const zx_1 = require("zx"); -const codesign_1 = require("../common/codesign"); -const publish_1 = require("../common/publish"); -async function main() { - (0, zx_1.usePwsh)(); - const arch = (0, publish_1.e)('VSCODE_ARCH'); - const esrpCliDLLPath = (0, publish_1.e)('EsrpCliDllPath'); - const codeSigningFolderPath = (0, publish_1.e)('CodeSigningFolderPath'); - // Start the code sign processes in parallel - // 1. Codesign executables and shared libraries - // 2. Codesign Powershell scripts - // 3. Codesign context menu appx package (insiders only) - const codesignTask1 = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-windows', codeSigningFolderPath, '*.dll,*.exe,*.node'); - const codesignTask2 = (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-windows-appx', codeSigningFolderPath, '*.ps1'); - const codesignTask3 = process.env['VSCODE_QUALITY'] === 'insider' - ? (0, codesign_1.spawnCodesignProcess)(esrpCliDLLPath, 'sign-windows-appx', codeSigningFolderPath, '*.appx') - : undefined; - // Codesign executables and shared libraries - (0, codesign_1.printBanner)('Codesign executables and shared libraries'); - await (0, codesign_1.streamProcessOutputAndCheckResult)('Codesign executables and shared libraries', codesignTask1); - // Codesign Powershell scripts - (0, codesign_1.printBanner)('Codesign Powershell scripts'); - await (0, codesign_1.streamProcessOutputAndCheckResult)('Codesign Powershell scripts', codesignTask2); - if (codesignTask3) { - // Codesign context menu appx package - (0, codesign_1.printBanner)('Codesign context menu appx package'); - await (0, codesign_1.streamProcessOutputAndCheckResult)('Codesign context menu appx package', codesignTask3); - } - // Create build artifact directory - await (0, zx_1.$) `New-Item -ItemType Directory -Path .build/win32-${arch} -Force`; - // Package client - if (process.env['BUILT_CLIENT']) { - // Product version - const version = await (0, zx_1.$) `node -p "require('../VSCode-win32-${arch}/resources/app/package.json').version"`; - (0, codesign_1.printBanner)('Package client'); - const clientArchivePath = `.build/win32-${arch}/VSCode-win32-${arch}-${version}.zip`; - await (0, zx_1.$) `7z.exe a -tzip ${clientArchivePath} ../VSCode-win32-${arch}/* "-xr!CodeSignSummary*.md"`.pipe(process.stdout); - await (0, zx_1.$) `7z.exe l ${clientArchivePath}`.pipe(process.stdout); - } - // Package server - if (process.env['BUILT_SERVER']) { - (0, codesign_1.printBanner)('Package server'); - const serverArchivePath = `.build/win32-${arch}/vscode-server-win32-${arch}.zip`; - await (0, zx_1.$) `7z.exe a -tzip ${serverArchivePath} ../vscode-server-win32-${arch}`.pipe(process.stdout); - await (0, zx_1.$) `7z.exe l ${serverArchivePath}`.pipe(process.stdout); - } - // Package server (web) - if (process.env['BUILT_WEB']) { - (0, codesign_1.printBanner)('Package server (web)'); - const webArchivePath = `.build/win32-${arch}/vscode-server-win32-${arch}-web.zip`; - await (0, zx_1.$) `7z.exe a -tzip ${webArchivePath} ../vscode-server-win32-${arch}-web`.pipe(process.stdout); - await (0, zx_1.$) `7z.exe l ${webArchivePath}`.pipe(process.stdout); - } - // Sign setup - if (process.env['BUILT_CLIENT']) { - (0, codesign_1.printBanner)('Sign setup packages (system, user)'); - const task = (0, zx_1.$) `npm exec -- npm-run-all -lp "gulp vscode-win32-${arch}-system-setup -- --sign" "gulp vscode-win32-${arch}-user-setup -- --sign"`; - await (0, codesign_1.streamProcessOutputAndCheckResult)('Sign setup packages (system, user)', task); - } -} -main().then(() => { - process.exit(0); -}, err => { - console.error(`ERROR: ${err}`); - process.exit(1); -}); -//# sourceMappingURL=codesign.js.map \ No newline at end of file diff --git a/build/azure-pipelines/win32/codesign.ts b/build/azure-pipelines/win32/codesign.ts index 7e7170709b540..c70d14a7a4f27 100644 --- a/build/azure-pipelines/win32/codesign.ts +++ b/build/azure-pipelines/win32/codesign.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { $, usePwsh } from 'zx'; -import { printBanner, spawnCodesignProcess, streamProcessOutputAndCheckResult } from '../common/codesign'; -import { e } from '../common/publish'; +import { printBanner, spawnCodesignProcess, streamProcessOutputAndCheckResult } from '../common/codesign.ts'; +import { e } from '../common/publish.ts'; async function main() { usePwsh(); @@ -43,11 +43,8 @@ async function main() { // Package client if (process.env['BUILT_CLIENT']) { - // Product version - const version = await $`node -p "require('../VSCode-win32-${arch}/resources/app/package.json').version"`; - printBanner('Package client'); - const clientArchivePath = `.build/win32-${arch}/VSCode-win32-${arch}-${version}.zip`; + const clientArchivePath = `.build/win32-${arch}/VSCode-win32-${arch}.zip`; await $`7z.exe a -tzip ${clientArchivePath} ../VSCode-win32-${arch}/* "-xr!CodeSignSummary*.md"`.pipe(process.stdout); await $`7z.exe l ${clientArchivePath}`.pipe(process.stdout); } diff --git a/build/azure-pipelines/win32/product-build-win32-ci.yml b/build/azure-pipelines/win32/product-build-win32-ci.yml new file mode 100644 index 0000000000000..eefacfdf8e9d6 --- /dev/null +++ b/build/azure-pipelines/win32/product-build-win32-ci.yml @@ -0,0 +1,49 @@ +parameters: + - name: VSCODE_CIBUILD + type: boolean + - name: VSCODE_QUALITY + type: string + - name: VSCODE_TEST_SUITE + type: string + +jobs: + - job: Windows${{ parameters.VSCODE_TEST_SUITE }} + displayName: ${{ parameters.VSCODE_TEST_SUITE }} Tests + timeoutInMinutes: 50 + variables: + VSCODE_ARCH: x64 + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/crashes + artifactName: crash-dump-windows-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) + displayName: Publish Crash Reports + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/node_modules + artifactName: node-modules-windows-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) + displayName: Publish Node Modules + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/logs + artifactName: logs-windows-$(VSCODE_ARCH)-${{ lower(parameters.VSCODE_TEST_SUITE) }}-$(System.JobAttempt) + displayName: Publish Log Files + sbomEnabled: false + isProduction: false + condition: succeededOrFailed() + steps: + - template: ./steps/product-build-win32-compile.yml@self + parameters: + VSCODE_ARCH: x64 + VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Electron') }}: + VSCODE_RUN_ELECTRON_TESTS: true + ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Browser') }}: + VSCODE_RUN_BROWSER_TESTS: true + ${{ if eq(parameters.VSCODE_TEST_SUITE, 'Remote') }}: + VSCODE_RUN_REMOTE_TESTS: true diff --git a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml b/build/azure-pipelines/win32/product-build-win32-cli-sign.yml index c7f4b0a0a1277..fa1328d99e27f 100644 --- a/build/azure-pipelines/win32/product-build-win32-cli-sign.yml +++ b/build/azure-pipelines/win32/product-build-win32-cli-sign.yml @@ -3,61 +3,81 @@ parameters: type: boolean - name: VSCODE_BUILD_WIN32_ARM64 type: boolean - - name: VSCODE_QUALITY - type: string - -steps: - - task: NodeTool@0 - displayName: "Use Node.js" - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - powershell: node build/setup-npm-registry.js $env:NPM_REGISTRY build - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - exec { npm config set registry "$env:NPM_REGISTRY" } - $NpmrcPath = (npm config get userconfig) - echo "##vso[task.setvariable variable=NPMRC_PATH]$NpmrcPath" - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - - powershell: | - . azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { npm ci } - workingDirectory: build - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - retryCountOnTaskFailure: 5 - displayName: Install build dependencies - - - template: ../cli/cli-win32-sign.yml@self - parameters: - VSCODE_CLI_ARTIFACTS: + +jobs: + - job: WindowsCLISign + timeoutInMinutes: 90 + templateContext: + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out + outputs: - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: - - unsigned_vscode_cli_win32_x64_cli + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_win32_x64_cli.zip + artifactName: vscode_cli_win32_x64_cli + displayName: Publish signed artifact with ID vscode_cli_win32_x64_cli + sbomBuildDropPath: $(Build.BinariesDirectory)/sign/unsigned_vscode_cli_win32_x64_cli + sbomPackageName: "VS Code Windows x64 CLI" + sbomPackageVersion: $(Build.SourceVersion) - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: - - unsigned_vscode_cli_win32_arm64_cli + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/vscode_cli_win32_arm64_cli.zip + artifactName: vscode_cli_win32_arm64_cli + displayName: Publish signed artifact with ID vscode_cli_win32_arm64_cli + sbomBuildDropPath: $(Build.BinariesDirectory)/sign/unsigned_vscode_cli_win32_arm64_cli + sbomPackageName: "VS Code Windows arm64 CLI" + sbomPackageVersion: $(Build.SourceVersion) + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + displayName: "Use Node.js" + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - powershell: node build/setup-npm-registry.ts $env:NPM_REGISTRY build + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + exec { npm config set registry "$env:NPM_REGISTRY" } + $NpmrcPath = (npm config get userconfig) + echo "##vso[task.setvariable variable=NPMRC_PATH]$NpmrcPath" + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - powershell: | + . azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npm ci } + workingDirectory: build + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + retryCountOnTaskFailure: 5 + displayName: Install build dependencies + + - template: ./steps/product-build-win32-cli-sign.yml@self + parameters: + VSCODE_CLI_ARTIFACTS: + - ${{ if eq(parameters.VSCODE_BUILD_WIN32, true) }}: + - unsigned_vscode_cli_win32_x64_cli + - ${{ if eq(parameters.VSCODE_BUILD_WIN32_ARM64, true) }}: + - unsigned_vscode_cli_win32_arm64_cli diff --git a/build/azure-pipelines/win32/product-build-win32-cli.yml b/build/azure-pipelines/win32/product-build-win32-cli.yml new file mode 100644 index 0000000000000..5dd69c3b50de3 --- /dev/null +++ b/build/azure-pipelines/win32/product-build-win32-cli.yml @@ -0,0 +1,77 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_CHECK_ONLY + type: boolean + default: false + - name: VSCODE_QUALITY + type: string + +jobs: + - job: WindowsCLI_${{ upper(parameters.VSCODE_ARCH) }} + displayName: Windows (${{ upper(parameters.VSCODE_ARCH) }}) + pool: + name: 1es-windows-2022-x64 + os: windows + timeoutInMinutes: 30 + variables: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + templateContext: + outputs: + - ${{ if not(parameters.VSCODE_CHECK_ONLY) }}: + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli.zip + artifactName: unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli + displayName: Publish unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli artifact + sbomEnabled: false + isProduction: false + + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - template: ../cli/cli-apply-patches.yml@self + + - task: Npm@1 + displayName: Download openssl prebuilt + inputs: + command: custom + customCommand: pack @vscode-internal/openssl-prebuilt@0.0.11 + customRegistry: useFeed + customFeed: "Monaco/openssl-prebuilt" + workingDir: $(Build.ArtifactStagingDirectory) + + - powershell: | + mkdir $(Build.ArtifactStagingDirectory)/openssl + tar -xvzf $(Build.ArtifactStagingDirectory)/vscode-internal-openssl-prebuilt-0.0.11.tgz --strip-components=1 --directory=$(Build.ArtifactStagingDirectory)/openssl + displayName: Extract openssl prebuilt + + - template: ./steps/product-build-win32-install-rust.yml@self + parameters: + targets: + - ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + - x86_64-pc-windows-msvc + - ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + - aarch64-pc-windows-msvc + + - template: ../cli/cli-compile.yml@self + parameters: + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + VSCODE_CLI_TARGET: x86_64-pc-windows-msvc + ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + VSCODE_CLI_TARGET: aarch64-pc-windows-msvc + VSCODE_CLI_ARTIFACT: unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli + VSCODE_CHECK_ONLY: ${{ parameters.VSCODE_CHECK_ONLY }} + VSCODE_CLI_ENV: + OPENSSL_LIB_DIR: $(Build.ArtifactStagingDirectory)/openssl/$(VSCODE_ARCH)-windows-static/lib + OPENSSL_INCLUDE_DIR: $(Build.ArtifactStagingDirectory)/openssl/$(VSCODE_ARCH)-windows-static/include + ${{ if eq(parameters.VSCODE_ARCH, 'x64') }}: + RUSTFLAGS: "-Ctarget-feature=+crt-static -Clink-args=/guard:cf -Clink-args=/CETCOMPAT" + ${{ if eq(parameters.VSCODE_ARCH, 'arm64') }}: + RUSTFLAGS: "-Ctarget-feature=+crt-static -Clink-args=/guard:cf -Clink-args=/CETCOMPAT:NO" + CFLAGS: "/guard:cf /Qspectre" diff --git a/build/azure-pipelines/win32/product-build-win32-node-modules.yml b/build/azure-pipelines/win32/product-build-win32-node-modules.yml new file mode 100644 index 0000000000000..6780073f57af7 --- /dev/null +++ b/build/azure-pipelines/win32/product-build-win32-node-modules.yml @@ -0,0 +1,95 @@ +parameters: + - name: VSCODE_ARCH + type: string + +jobs: + - job: WindowsNodeModules_${{ parameters.VSCODE_ARCH }} + displayName: Windows (${{ upper(parameters.VSCODE_ARCH) }}) + pool: + name: 1es-windows-2022-x64 + os: windows + timeoutInMinutes: 60 + variables: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + steps: + - template: ../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - task: UsePythonVersion@0 + inputs: + versionSpec: "3.x" + addToPath: true + + - template: ../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - powershell: node build/setup-npm-registry.ts $env:NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - pwsh: | + mkdir .build -ea 0 + node build/azure-pipelines/common/computeNodeModulesCacheKey.ts win32 $(VSCODE_ARCH) $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + exec { npm config set registry "$env:NPM_REGISTRY" } + $NpmrcPath = (npm config get userconfig) + echo "##vso[task.setvariable variable=NPMRC_PATH]$NpmrcPath" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npm ci } + env: + npm_config_arch: $(VSCODE_ARCH) + npm_config_foreground_scripts: "true" + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + retryCountOnTaskFailure: 5 + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - powershell: node build/azure-pipelines/distro/mixin-npm.ts + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt } + exec { mkdir -Force .build/node_modules_cache } + exec { 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt } + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive diff --git a/build/azure-pipelines/win32/product-build-win32-test.yml b/build/azure-pipelines/win32/product-build-win32-test.yml deleted file mode 100644 index 73f2b8f977e8b..0000000000000 --- a/build/azure-pipelines/win32/product-build-win32-test.yml +++ /dev/null @@ -1,246 +0,0 @@ -parameters: - - name: VSCODE_QUALITY - type: string - - name: VSCODE_ARCH - type: string - - name: VSCODE_RUN_ELECTRON_TESTS - type: boolean - - name: VSCODE_RUN_BROWSER_TESTS - type: boolean - - name: VSCODE_RUN_REMOTE_TESTS - type: boolean - - name: VSCODE_TEST_ARTIFACT_NAME - type: string - - name: PUBLISH_TASK_NAME - type: string - default: PublishPipelineArtifact@0 - -steps: - # Additional "--" needed to workaround https://github.com/npm/cli/issues/7375 - - powershell: npm exec -- -- npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Download Electron and Playwright - retryCountOnTaskFailure: 3 - - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - powershell: .\scripts\test.bat --tfs "Unit Tests" - displayName: 🧪 Run unit tests (Electron) - timeoutInMinutes: 15 - - powershell: npm run test-node - displayName: 🧪 Run unit tests (node.js) - timeoutInMinutes: 15 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - powershell: node test/unit/browser/index.js --browser chromium --tfs "Browser Unit Tests" - displayName: 🧪 Run unit tests (Browser, Chromium) - timeoutInMinutes: 20 - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - powershell: .\scripts\test.bat --build --tfs "Unit Tests" - displayName: 🧪 Run unit tests (Electron) - timeoutInMinutes: 15 - # Additional "--" needed to workaround https://github.com/npm/cli/issues/7375 - - powershell: npm run test-node -- -- --build - displayName: 🧪 Run unit tests (node.js) - timeoutInMinutes: 15 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - # Additional "--" needed to workaround https://github.com/npm/cli/issues/7375 - - powershell: npm run test-browser-no-install -- -- --build --browser chromium --tfs "Browser Unit Tests" - displayName: 🧪 Run unit tests (Browser, Chromium) - timeoutInMinutes: 20 - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { npm run gulp ` - compile-extension:configuration-editing ` - compile-extension:css-language-features-server ` - compile-extension:emmet ` - compile-extension:git ` - compile-extension:github-authentication ` - compile-extension:html-language-features-server ` - compile-extension:ipynb ` - compile-extension:notebook-renderers ` - compile-extension:json-language-features-server ` - compile-extension:markdown-language-features ` - compile-extension-media ` - compile-extension:microsoft-authentication ` - compile-extension:typescript-language-features ` - compile-extension:vscode-api-tests ` - compile-extension:vscode-colorize-tests ` - compile-extension:vscode-colorize-perf-tests ` - compile-extension:vscode-test-resolver ` - } - displayName: Build integration tests - - - powershell: .\build\azure-pipelines\win32\listprocesses.bat - displayName: Diagnostics before integration test runs - continueOnError: true - condition: succeededOrFailed() - - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - powershell: .\scripts\test-integration.bat --tfs "Integration Tests" - displayName: 🧪 Run integration tests (Electron) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - powershell: .\scripts\test-web-integration.bat --browser chromium - displayName: 🧪 Run integration tests (Browser, Chromium) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: - - powershell: .\scripts\test-remote-integration.bat - displayName: 🧪 Run integration tests (Remote) - timeoutInMinutes: 20 - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: | - # Copy client, server and web builds to a separate test directory, to avoid Access Denied errors in codesign - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $TestDir = "$(agent.builddirectory)\test" - New-Item -ItemType Directory -Path $TestDir -Force - Copy-Item -Path "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" -Destination "$TestDir\VSCode-win32-$(VSCODE_ARCH)" -Recurse -Force - Copy-Item -Path "$(agent.builddirectory)\vscode-server-win32-$(VSCODE_ARCH)" -Destination "$TestDir\vscode-server-win32-$(VSCODE_ARCH)" -Recurse -Force - Copy-Item -Path "$(agent.builddirectory)\vscode-server-win32-$(VSCODE_ARCH)-web" -Destination "$TestDir\vscode-server-win32-$(VSCODE_ARCH)-web" -Recurse -Force - displayName: Copy builds to test directory - - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - - powershell: | - # Figure out the full absolute path of the product we just built - # including the remote server and configure the integration tests - # to run with these builds instead of running out of sources. - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\test\VSCode-win32-$(VSCODE_ARCH)" - $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json - $AppNameShort = $AppProductJson.nameShort - $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH)" - exec { .\scripts\test-integration.bat --build --tfs "Integration Tests" } - displayName: 🧪 Run integration tests (Electron) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH)-web" - exec { .\scripts\test-web-integration.bat --browser firefox } - displayName: 🧪 Run integration tests (Browser, Firefox) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $AppRoot = "$(agent.builddirectory)\test\VSCode-win32-$(VSCODE_ARCH)" - $AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json - $AppNameShort = $AppProductJson.nameShort - $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe" - $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH)" - exec { .\scripts\test-remote-integration.bat } - displayName: 🧪 Run integration tests (Remote) - timeoutInMinutes: 20 - - - powershell: .\build\azure-pipelines\win32\listprocesses.bat - displayName: Diagnostics after integration test runs - continueOnError: true - condition: succeededOrFailed() - - - powershell: .\build\azure-pipelines\win32\listprocesses.bat - displayName: Diagnostics before smoke test run - continueOnError: true - condition: succeededOrFailed() - - # - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - # - powershell: npm run compile - # workingDirectory: test/smoke - # displayName: Compile smoke tests - - # - powershell: npm run gulp compile-extension-media - # displayName: Build extensions for smoke tests - - # - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - # # Additional "--" needed to workaround https://github.com/npm/cli/issues/7375 - # - powershell: npm run smoketest-no-compile -- -- --tracing - # displayName: 🧪 Run smoke tests (Electron) - # timeoutInMinutes: 20 - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: - # Additional "--" needed to workaround https://github.com/npm/cli/issues/7375 - - powershell: npm run smoketest-no-compile -- -- --verbose --tracing --build "$(agent.builddirectory)\test\VSCode-win32-$(VSCODE_ARCH)" - displayName: 🧪 Run smoke tests (Electron) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: - # Additional "--" needed to workaround https://github.com/npm/cli/issues/7375 - - powershell: npm run smoketest-no-compile -- -- --web --tracing --headless - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH)-web - displayName: 🧪 Run smoke tests (Browser, Chromium) - timeoutInMinutes: 20 - - - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: - # Additional "--" needed to workaround https://github.com/npm/cli/issues/7375 - - powershell: npm run smoketest-no-compile -- -- --tracing --remote --build "$(agent.builddirectory)\test\VSCode-win32-$(VSCODE_ARCH)" - env: - VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH) - displayName: 🧪 Run smoke tests (Remote) - timeoutInMinutes: 20 - - - powershell: .\build\azure-pipelines\win32\listprocesses.bat - displayName: Diagnostics after smoke test run - continueOnError: true - condition: succeededOrFailed() - - - task: ${{ parameters.PUBLISH_TASK_NAME }} - inputs: - targetPath: .build\crashes - ${{ if eq(parameters.VSCODE_TEST_ARTIFACT_NAME, '') }}: - artifactName: crash-dump-windows-$(VSCODE_ARCH)-$(System.JobAttempt) - ${{ else }}: - artifactName: crash-dump-windows-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) - sbomEnabled: false - displayName: "Publish Crash Reports" - continueOnError: true - condition: failed() - - # In order to properly symbolify above crash reports - # (if any), we need the compiled native modules too - - task: ${{ parameters.PUBLISH_TASK_NAME }} - inputs: - targetPath: node_modules - ${{ if eq(parameters.VSCODE_TEST_ARTIFACT_NAME, '') }}: - artifactName: node-modules-windows-$(VSCODE_ARCH)-$(System.JobAttempt) - ${{ else }}: - artifactName: node-modules-windows-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) - sbomEnabled: false - displayName: "Publish Node Modules" - continueOnError: true - condition: failed() - - - task: ${{ parameters.PUBLISH_TASK_NAME }} - inputs: - targetPath: .build\logs - ${{ if eq(parameters.VSCODE_TEST_ARTIFACT_NAME, '') }}: - artifactName: logs-windows-$(VSCODE_ARCH)-$(System.JobAttempt) - ${{ else }}: - artifactName: logs-windows-$(VSCODE_ARCH)-${{ parameters.VSCODE_TEST_ARTIFACT_NAME }}-$(System.JobAttempt) - sbomEnabled: false - displayName: "Publish Log Files" - continueOnError: true - condition: succeededOrFailed() - - - task: PublishTestResults@2 - displayName: Publish Tests Results - inputs: - testResultsFiles: "*-results.xml" - searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" - condition: succeededOrFailed() diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index a74935a08af5e..3a91d3cdd97db 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -1,10 +1,10 @@ parameters: - - name: VSCODE_QUALITY - type: string - name: VSCODE_ARCH type: string - name: VSCODE_CIBUILD type: boolean + - name: VSCODE_QUALITY + type: string - name: VSCODE_RUN_ELECTRON_TESTS type: boolean default: false @@ -14,383 +14,82 @@ parameters: - name: VSCODE_RUN_REMOTE_TESTS type: boolean default: false - - name: VSCODE_TEST_ARTIFACT_NAME - type: string - default: "" - -steps: - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - checkout: self - fetchDepth: 1 - retryCountOnTaskFailure: 3 - - - task: NodeTool@0 - inputs: - versionSource: fromFile - versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - - - task: UsePythonVersion@0 - inputs: - versionSpec: "3.x" - addToPath: true - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - template: ../distro/download-distro.yml@self - - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get Secrets" - inputs: - azureSubscription: vscode - KeyVaultName: vscode-build-secrets - SecretsFilter: "github-distro-mixin-password" - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: Compilation - path: $(Build.ArtifactStagingDirectory) - displayName: Download compilation output - - - task: ExtractFiles@1 - displayName: Extract compilation output - inputs: - archiveFilePatterns: "$(Build.ArtifactStagingDirectory)/compilation.tar.gz" - cleanDestinationFolder: false - - - powershell: node build/setup-npm-registry.js $env:NPM_REGISTRY - condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Registry - - - pwsh: | - mkdir .build -ea 0 - node build/azure-pipelines/common/computeNodeModulesCacheKey.js win32 $(VSCODE_ARCH) $(node -p process.arch) > .build/packagelockhash - displayName: Prepare node_modules cache key - - - task: Cache@2 - inputs: - key: '"node_modules" | .build/packagelockhash' - path: .build/node_modules_cache - cacheHitVar: NODE_MODULES_RESTORED - displayName: Restore node_modules cache - - - powershell: 7z.exe x .build/node_modules_cache/cache.7z -aoa - condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Extract node_modules cache - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - # Set the private NPM registry to the global npmrc file - # so that authentication works for subfolders like build/, remote/, extensions/ etc - # which does not have their own .npmrc file - exec { npm config set registry "$env:NPM_REGISTRY" } - $NpmrcPath = (npm config get userconfig) - echo "##vso[task.setvariable variable=NPMRC_PATH]$NpmrcPath" - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM - - - task: npmAuthenticate@0 - inputs: - workingFile: $(NPMRC_PATH) - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) - displayName: Setup NPM Authentication - - # Remove once https://github.com/parcel-bundler/watcher/pull/202 is merged. - - pwsh: | - $includes = @' - { - 'target_defaults': { - 'conditions': [ - ['OS=="win"', { - "msvs_settings": { - "VCCLCompilerTool": { - "AdditionalOptions": [ - "/guard:cf", - "/w34244", - "/w34267", - ] - }, - "VCLinkerTool": { - "AdditionalOptions": [ - "/guard:cf", - ] - } - } - }] - ] - } - } - '@ - - if (!(Test-Path "~/.gyp")) { - mkdir "~/.gyp" - } - echo $includes > "~/.gyp/include.gypi" - displayName: Create include.gypi - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { npm ci } - env: - npm_config_arch: $(VSCODE_ARCH) - npm_config_foreground_scripts: "true" - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - retryCountOnTaskFailure: 5 - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: node build/azure-pipelines/distro/mixin-npm - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Mixin distro node modules - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt } - exec { mkdir -Force .build/node_modules_cache } - exec { 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt } - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - displayName: Create node_modules archive - - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: node build/azure-pipelines/distro/mixin-quality - displayName: Mixin distro quality - - - template: ../common/install-builtin-extensions.yml@self - - - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'oss')) }}: - - powershell: node build\lib\policies win32 - displayName: Generate Group Policy definitions - retryCountOnTaskFailure: 3 - - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - powershell: npm run gulp "transpile-client-esbuild" "transpile-extensions" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Transpile client and extensions - - - ${{ else }}: - - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'exploration')) }}: - - powershell: node build/win32/explorer-dll-fetcher .build/win32/appx - displayName: Download Explorer dll - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { npm run gulp "vscode-win32-$(VSCODE_ARCH)-min-ci" } - exec { npm run gulp "vscode-win32-$(VSCODE_ARCH)-inno-updater" } - echo "##vso[task.setvariable variable=BUILT_CLIENT]true" - echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build client - - # Note: the appx prepare step has to follow Build client step since build step replaces the template - # strings in the raw manifest file at resources/win32/appx/AppxManifest.xml and places it under - # /appx/manifest, we need a separate step to prepare the appx package with the - # final contents. In our case only the manifest file is bundled into the appx package. - - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'exploration')) }}: - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - # Add Windows SDK to path - $sdk = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64" - $env:PATH = "$sdk;$env:PATH" - $AppxName = if ('$(VSCODE_QUALITY)' -eq 'stable') { 'code' } else { 'code_insider' } - makeappx pack /d "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/appx/manifest" /p "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/appx/${AppxName}_$(VSCODE_ARCH).appx" /nv - # Remove the raw manifest folder - Remove-Item -Path "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/appx/manifest" -Recurse -Force - displayName: Prepare appx package - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { npm run gulp "vscode-reh-win32-$(VSCODE_ARCH)-min-ci" } - mv ..\vscode-reh-win32-$(VSCODE_ARCH) ..\vscode-server-win32-$(VSCODE_ARCH) # TODO@joaomoreno - echo "##vso[task.setvariable variable=BUILT_SERVER]true" - echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(CodeSigningFolderPath),$(Agent.BuildDirectory)/vscode-server-win32-$(VSCODE_ARCH)" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build server - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { npm run gulp "vscode-reh-web-win32-$(VSCODE_ARCH)-min-ci" } - mv ..\vscode-reh-web-win32-$(VSCODE_ARCH) ..\vscode-server-win32-$(VSCODE_ARCH)-web # TODO@joaomoreno - echo "##vso[task.setvariable variable=BUILT_WEB]true" - env: - GITHUB_TOKEN: "$(github-distro-mixin-password)" - displayName: Build server (web) - - - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - task: DownloadPipelineArtifact@2 - inputs: - artifact: unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli - patterns: "**" - path: $(Build.ArtifactStagingDirectory)/cli - displayName: Download VS Code CLI - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $ArtifactName = (gci -Path "$(Build.ArtifactStagingDirectory)/cli" | Select-Object -last 1).FullName - Expand-Archive -Path $ArtifactName -DestinationPath "$(Build.ArtifactStagingDirectory)/cli" - $AppProductJson = Get-Content -Raw -Path "$(Agent.BuildDirectory)\VSCode-win32-$(VSCODE_ARCH)\resources\app\product.json" | ConvertFrom-Json - $CliAppName = $AppProductJson.tunnelApplicationName - $AppName = $AppProductJson.applicationName - Move-Item -Path "$(Build.ArtifactStagingDirectory)/cli/$AppName.exe" -Destination "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/bin/$CliAppName.exe" - displayName: Move VS Code CLI - - - task: UseDotNet@2 - inputs: - version: 6.x - - - task: EsrpCodeSigning@5 - inputs: - UseMSIAuthentication: true - ConnectedServiceName: vscode-esrp - AppRegistrationClientId: $(ESRP_CLIENT_ID) - AppRegistrationTenantId: $(ESRP_TENANT_ID) - AuthAKVName: vscode-esrp - AuthSignCertName: esrp-sign - FolderPath: . - Pattern: noop - displayName: 'Install ESRP Tooling' - - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName - $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName - echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version\net6.0\esrpcli.dll" - displayName: Find ESRP CLI - - # Additional "--" needed to workaround https://github.com/npm/cli/issues/7375 - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - mkdir -Force .build/codesign-cpuprofile - exec { npx deemon --detach --wait -- -- npx zx build/azure-pipelines/win32/codesign.js } - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - NODE_DEBUG: "net,child_process" - NODE_OPTIONS: "--report-filename=stdout --report-uncaught-exception --report-on-fatalerror --cpu-prof --cpu-prof-dir=.build/codesign-cpuprofile" - displayName: ✍️ Codesign - - - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: - - template: product-build-win32-test.yml@self - parameters: - VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} - VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} - VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} - VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} - VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} - VSCODE_TEST_ARTIFACT_NAME: ${{ parameters.VSCODE_TEST_ARTIFACT_NAME }} - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - PUBLISH_TASK_NAME: 1ES.PublishPipelineArtifact@1 - - - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - # Additional "--" needed to workaround https://github.com/npm/cli/issues/7375 - - powershell: | - . build/azure-pipelines/win32/exec.ps1 - $ErrorActionPreference = "Stop" - exec { npx deemon --attach -- -- npx zx build/azure-pipelines/win32/codesign.js } - condition: succeededOrFailed() - env: - NODE_DEBUG: "net,child_process" - NODE_OPTIONS: "--report-filename=stdout --report-uncaught-exception --report-on-fatalerror --cpu-prof --cpu-prof-dir=.build/codesign-cpuprofile" - displayName: "✍️ Post-job: Codesign" - - - powershell: | - $ErrorActionPreference = "Stop" - - $PackageJson = Get-Content -Raw -Path ..\VSCode-win32-$(VSCODE_ARCH)\resources\app\package.json | ConvertFrom-Json - $Version = $PackageJson.version - - $ClientArchivePath = ".build\win32-$(VSCODE_ARCH)\VSCode-win32-$(VSCODE_ARCH)-$Version.zip" - $ServerArchivePath = ".build\win32-$(VSCODE_ARCH)\vscode-server-win32-$(VSCODE_ARCH).zip" - $WebArchivePath = ".build\win32-$(VSCODE_ARCH)\vscode-server-win32-$(VSCODE_ARCH)-web.zip" - - $SystemSetupPath = ".build\win32-$(VSCODE_ARCH)\system-setup\VSCodeSetup-$(VSCODE_ARCH)-$Version.exe" - $UserSetupPath = ".build\win32-$(VSCODE_ARCH)\user-setup\VSCodeUserSetup-$(VSCODE_ARCH)-$Version.exe" - - mv .build\win32-$(VSCODE_ARCH)\system-setup\VSCodeSetup.exe $SystemSetupPath - mv .build\win32-$(VSCODE_ARCH)\user-setup\VSCodeSetup.exe $UserSetupPath - - echo "##vso[task.setvariable variable=CLIENT_PATH]$ClientArchivePath" - echo "##vso[task.setvariable variable=SERVER_PATH]$ServerArchivePath" - echo "##vso[task.setvariable variable=WEB_PATH]$WebArchivePath" - - echo "##vso[task.setvariable variable=SYSTEM_SETUP_PATH]$SystemSetupPath" - echo "##vso[task.setvariable variable=USER_SETUP_PATH]$UserSetupPath" - condition: succeededOrFailed() - displayName: Move setup packages - - - powershell: echo "##vso[task.setvariable variable=ARTIFACT_PREFIX]attempt$(System.JobAttempt)_" - condition: and(succeededOrFailed(), notIn(variables['Agent.JobStatus'], 'Succeeded', 'SucceededWithIssues')) - displayName: Generate artifact prefix - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: .build/codesign-cpuprofile - artifactName: node-cpuprofile - sbomEnabled: false - displayName: Publish Codesign cpu profile - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(CLIENT_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_client_win32_$(VSCODE_ARCH)_archive +jobs: + - job: Windows_${{ parameters.VSCODE_ARCH }} + displayName: Windows (${{ upper(parameters.VSCODE_ARCH) }}) + timeoutInMinutes: 90 + variables: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + templateContext: + outputParentDirectory: $(Build.ArtifactStagingDirectory)/out + outputs: + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/crashes + artifactName: crash-dump-windows-$(VSCODE_ARCH)-$(System.JobAttempt) + displayName: Publish Crash Reports + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/node_modules + artifactName: node-modules-windows-$(VSCODE_ARCH)-$(System.JobAttempt) + displayName: Publish Node Modules + sbomEnabled: false + isProduction: false + condition: failed() + - output: pipelineArtifact + targetPath: $(Build.SourcesDirectory)/.build/logs + artifactName: logs-windows-$(VSCODE_ARCH)-$(System.JobAttempt) + displayName: Publish Log Files + sbomEnabled: false + isProduction: false + condition: succeededOrFailed() + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/system-setup/VSCodeSetup-$(VSCODE_ARCH)-$(VSCODE_VERSION).exe + artifactName: vscode_client_win32_$(VSCODE_ARCH)_setup + displayName: Publish system setup + sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH) + sbomPackageName: "VS Code Windows $(VSCODE_ARCH) System Setup" + sbomPackageVersion: $(Build.SourceVersion) + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/user-setup/VSCodeUserSetup-$(VSCODE_ARCH)-$(VSCODE_VERSION).exe + artifactName: vscode_client_win32_$(VSCODE_ARCH)_user-setup + displayName: Publish user setup + sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH) + sbomPackageName: "VS Code Windows $(VSCODE_ARCH) User Setup" + sbomPackageVersion: $(Build.SourceVersion) + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/archive/VSCode-win32-$(VSCODE_ARCH)-$(VSCODE_VERSION).zip + artifactName: vscode_client_win32_$(VSCODE_ARCH)_archive + displayName: Publish archive sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH) sbomPackageName: "VS Code Windows $(VSCODE_ARCH)" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['CLIENT_PATH'], '')) - displayName: Publish archive - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(SERVER_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_server_win32_$(VSCODE_ARCH)_archive + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/server/vscode-server-win32-$(VSCODE_ARCH).zip + artifactName: vscode_server_win32_$(VSCODE_ARCH)_archive + displayName: Publish server archive sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-win32-$(VSCODE_ARCH) sbomPackageName: "VS Code Windows $(VSCODE_ARCH) Server" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['SERVER_PATH'], '')) - displayName: Publish server archive - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(WEB_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_web_win32_$(VSCODE_ARCH)_archive + - output: pipelineArtifact + targetPath: $(Build.ArtifactStagingDirectory)/out/web/vscode-server-win32-$(VSCODE_ARCH)-web.zip + artifactName: vscode_web_win32_$(VSCODE_ARCH)_archive + displayName: Publish web server archive sbomBuildDropPath: $(Agent.BuildDirectory)/vscode-server-win32-$(VSCODE_ARCH)-web sbomPackageName: "VS Code Windows $(VSCODE_ARCH) Web" sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['WEB_PATH'], '')) - displayName: Publish web server archive - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(SYSTEM_SETUP_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_client_win32_$(VSCODE_ARCH)_setup - sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH) - sbomPackageName: "VS Code Windows $(VSCODE_ARCH) System Setup" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['SYSTEM_SETUP_PATH'], '')) - displayName: Publish system setup - - - task: 1ES.PublishPipelineArtifact@1 - inputs: - targetPath: $(USER_SETUP_PATH) - artifactName: $(ARTIFACT_PREFIX)vscode_client_win32_$(VSCODE_ARCH)_user-setup - sbomBuildDropPath: $(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH) - sbomPackageName: "VS Code Windows $(VSCODE_ARCH) User Setup" - sbomPackageVersion: $(Build.SourceVersion) - condition: and(succeededOrFailed(), ne(variables['USER_SETUP_PATH'], '')) - displayName: Publish user setup + sdl: + suppression: + suppressionFile: $(Build.SourcesDirectory)\.config\guardian\.gdnsuppress + steps: + - template: ./steps/product-build-win32-compile.yml@self + parameters: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + VSCODE_CIBUILD: ${{ parameters.VSCODE_CIBUILD }} + VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} + VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} + VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} + VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} diff --git a/build/azure-pipelines/win32/sdl-scan-win32.yml b/build/azure-pipelines/win32/sdl-scan-win32.yml index bf6819a4b479d..e3356effa95a7 100644 --- a/build/azure-pipelines/win32/sdl-scan-win32.yml +++ b/build/azure-pipelines/win32/sdl-scan-win32.yml @@ -5,11 +5,12 @@ parameters: type: string steps: + - template: ../common/checkout.yml@self + - task: NodeTool@0 inputs: versionSource: fromFile versionFilePath: .nvmrc - nodejsMirror: https://github.com/joaomoreno/node-mirror/releases/download - task: UsePythonVersion@0 inputs: @@ -25,7 +26,7 @@ steps: KeyVaultName: vscode-build-secrets SecretsFilter: "github-distro-mixin-password" - - powershell: node build/setup-npm-registry.js $env:NPM_REGISTRY + - powershell: node build/setup-npm-registry.ts $env:NPM_REGISTRY condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Registry @@ -91,10 +92,10 @@ steps: retryCountOnTaskFailure: 5 displayName: Install dependencies - - script: node build/azure-pipelines/distro/mixin-npm + - script: node build/azure-pipelines/distro/mixin-npm.ts displayName: Mixin distro node modules - - script: node build/azure-pipelines/distro/mixin-quality + - script: node build/azure-pipelines/distro/mixin-quality.ts displayName: Mixin distro quality env: VSCODE_QUALITY: ${{ parameters.VSCODE_QUALITY }} @@ -114,6 +115,16 @@ steps: Get-ChildItem '$(Agent.BuildDirectory)\scanbin' -Recurse -Filter "*.pdb" displayName: List files + - task: PublishSymbols@2 + displayName: 'Publish Symbols to Artifacts' + inputs: + SymbolsFolder: '$(Agent.BuildDirectory)\scanbin' + SearchPattern: '**/*.pdb' + IndexSources: false + PublishSymbols: true + SymbolServerType: 'TeamServices' + SymbolsProduct: 'vscode-client' + - task: CopyFiles@2 displayName: 'Collect Symbols for API Scan' inputs: diff --git a/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml b/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml new file mode 100644 index 0000000000000..0caba3d1a2b88 --- /dev/null +++ b/build/azure-pipelines/win32/steps/product-build-win32-cli-sign.yml @@ -0,0 +1,61 @@ +parameters: + - name: VSCODE_CLI_ARTIFACTS + type: object + default: [] + +steps: + - task: UseDotNet@2 + inputs: + version: 6.x + + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName + $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName + echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version\net6.0\esrpcli.dll" + displayName: Find ESRP CLI + + - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: + - task: DownloadPipelineArtifact@2 + displayName: Download artifact + inputs: + artifact: ${{ target }} + path: $(Build.BinariesDirectory)/pkg/${{ target }} + + - task: ExtractFiles@1 + displayName: Extract artifact + inputs: + archiveFilePatterns: $(Build.BinariesDirectory)/pkg/${{ target }}/*.zip + destinationFolder: $(Build.BinariesDirectory)/sign/${{ target }} + + - powershell: node build\azure-pipelines\common\sign.ts $env:EsrpCliDllPath sign-windows $(Build.BinariesDirectory)/sign "*.exe" + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Codesign + + - ${{ each target in parameters.VSCODE_CLI_ARTIFACTS }}: + - powershell: | + $ASSET_ID = "${{ target }}".replace("unsigned_", ""); + echo "##vso[task.setvariable variable=ASSET_ID]$ASSET_ID" + displayName: Set asset id variable + + - task: ArchiveFiles@2 + displayName: Archive signed files + inputs: + rootFolderOrFile: $(Build.BinariesDirectory)/sign/${{ target }} + includeRootFolder: false + archiveType: zip + archiveFile: $(Build.ArtifactStagingDirectory)/out/$(ASSET_ID).zip diff --git a/build/azure-pipelines/win32/steps/product-build-win32-compile.yml b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml new file mode 100644 index 0000000000000..d6412c2342090 --- /dev/null +++ b/build/azure-pipelines/win32/steps/product-build-win32-compile.yml @@ -0,0 +1,274 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_CIBUILD + type: boolean + - name: VSCODE_QUALITY + type: string + - name: VSCODE_RUN_ELECTRON_TESTS + type: boolean + default: false + - name: VSCODE_RUN_BROWSER_TESTS + type: boolean + default: false + - name: VSCODE_RUN_REMOTE_TESTS + type: boolean + default: false + +steps: + - template: ../../common/checkout.yml@self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + + - task: UsePythonVersion@0 + inputs: + versionSpec: "3.x" + addToPath: true + + - template: ../../distro/download-distro.yml@self + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get Secrets" + inputs: + azureSubscription: vscode + KeyVaultName: vscode-build-secrets + SecretsFilter: "github-distro-mixin-password" + + - task: DownloadPipelineArtifact@2 + inputs: + artifact: Compilation + path: $(Build.ArtifactStagingDirectory) + displayName: Download compilation output + + - task: ExtractFiles@1 + displayName: Extract compilation output + inputs: + archiveFilePatterns: "$(Build.ArtifactStagingDirectory)/compilation.tar.gz" + cleanDestinationFolder: false + + - powershell: node build/setup-npm-registry.ts $env:NPM_REGISTRY + condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Registry + + - pwsh: | + mkdir .build -ea 0 + node build/azure-pipelines/common/computeNodeModulesCacheKey.ts win32 $(VSCODE_ARCH) $(node -p process.arch) > .build/packagelockhash + displayName: Prepare node_modules cache key + + - task: Cache@2 + inputs: + key: '"node_modules" | .build/packagelockhash' + path: .build/node_modules_cache + cacheHitVar: NODE_MODULES_RESTORED + displayName: Restore node_modules cache + + - powershell: 7z.exe x .build/node_modules_cache/cache.7z -aoa + condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Extract node_modules cache + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + # Set the private NPM registry to the global npmrc file + # so that authentication works for subfolders like build/, remote/, extensions/ etc + # which does not have their own .npmrc file + exec { npm config set registry "$env:NPM_REGISTRY" } + $NpmrcPath = (npm config get userconfig) + echo "##vso[task.setvariable variable=NPMRC_PATH]$NpmrcPath" + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM + + - task: npmAuthenticate@0 + inputs: + workingFile: $(NPMRC_PATH) + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) + displayName: Setup NPM Authentication + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npm ci } + env: + npm_config_arch: $(VSCODE_ARCH) + npm_config_foreground_scripts: "true" + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + retryCountOnTaskFailure: 5 + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + + - powershell: node build/azure-pipelines/distro/mixin-npm.ts + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Mixin distro node modules + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { node build/azure-pipelines/common/listNodeModules.ts .build/node_modules_list.txt } + exec { mkdir -Force .build/node_modules_cache } + exec { 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt } + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + displayName: Create node_modules archive + + - powershell: node build/azure-pipelines/distro/mixin-quality.ts + displayName: Mixin distro quality + + - template: ../../common/install-builtin-extensions.yml@self + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - powershell: | + npm run copy-policy-dto --prefix build + node build\lib\policies\policyGenerator.ts build\lib\policies\policyData.jsonc win32 + displayName: Generate Group Policy definitions + retryCountOnTaskFailure: 3 + + - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'exploration')) }}: + - powershell: node build/win32/explorer-dll-fetcher.ts .build/win32/appx + displayName: Download Explorer dll + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npm run gulp "vscode-win32-$(VSCODE_ARCH)-min-ci" } + exec { npm run gulp "vscode-win32-$(VSCODE_ARCH)-inno-updater" } + echo "##vso[task.setvariable variable=BUILT_CLIENT]true" + echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build client + + # Note: the appx prepare step has to follow Build client step since build step replaces the template + # strings in the raw manifest file at resources/win32/appx/AppxManifest.xml and places it under + # /appx/manifest, we need a separate step to prepare the appx package with the + # final contents. In our case only the manifest file is bundled into the appx package. + - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'exploration')) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + # Add Windows SDK to path + $sdk = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64" + $env:PATH = "$sdk;$env:PATH" + $AppxName = if ('$(VSCODE_QUALITY)' -eq 'stable') { 'code' } else { 'code_insider' } + makeappx pack /d "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/appx/manifest" /p "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/appx/${AppxName}_$(VSCODE_ARCH).appx" /nv + # Remove the raw manifest folder + Remove-Item -Path "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/appx/manifest" -Recurse -Force + displayName: Prepare appx package + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npm run gulp "vscode-reh-win32-$(VSCODE_ARCH)-min-ci" } + mv ..\vscode-reh-win32-$(VSCODE_ARCH) ..\vscode-server-win32-$(VSCODE_ARCH) # TODO@joaomoreno + echo "##vso[task.setvariable variable=BUILT_SERVER]true" + echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(CodeSigningFolderPath),$(Agent.BuildDirectory)/vscode-server-win32-$(VSCODE_ARCH)" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npm run gulp "vscode-reh-web-win32-$(VSCODE_ARCH)-min-ci" } + mv ..\vscode-reh-web-win32-$(VSCODE_ARCH) ..\vscode-server-win32-$(VSCODE_ARCH)-web # TODO@joaomoreno + echo "##vso[task.setvariable variable=BUILT_WEB]true" + echo "##vso[task.setvariable variable=CodeSigningFolderPath]$(CodeSigningFolderPath),$(Agent.BuildDirectory)/vscode-server-win32-$(VSCODE_ARCH)-web" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Build server (web) + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: unsigned_vscode_cli_win32_$(VSCODE_ARCH)_cli + patterns: "**" + path: $(Build.ArtifactStagingDirectory)/cli + displayName: Download VS Code CLI + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $ArtifactName = (gci -Path "$(Build.ArtifactStagingDirectory)/cli" | Select-Object -last 1).FullName + Expand-Archive -Path $ArtifactName -DestinationPath "$(Build.ArtifactStagingDirectory)/cli" + $ProductJsonPath = (Get-ChildItem -Path "$(Agent.BuildDirectory)\VSCode-win32-$(VSCODE_ARCH)" -Name "product.json" -Recurse | Select-Object -First 1) + $AppProductJson = Get-Content -Raw -Path "$(Agent.BuildDirectory)\VSCode-win32-$(VSCODE_ARCH)\$ProductJsonPath" | ConvertFrom-Json + $CliAppName = $AppProductJson.tunnelApplicationName + $AppName = $AppProductJson.applicationName + Move-Item -Path "$(Build.ArtifactStagingDirectory)/cli/$AppName.exe" -Destination "$(Agent.BuildDirectory)/VSCode-win32-$(VSCODE_ARCH)/bin/$CliAppName.exe" + displayName: Move VS Code CLI + + - task: UseDotNet@2 + inputs: + version: 6.x + + - task: EsrpCodeSigning@5 + inputs: + UseMSIAuthentication: true + ConnectedServiceName: vscode-esrp + AppRegistrationClientId: $(ESRP_CLIENT_ID) + AppRegistrationTenantId: $(ESRP_TENANT_ID) + AuthAKVName: vscode-esrp + AuthSignCertName: esrp-sign + FolderPath: . + Pattern: noop + displayName: 'Install ESRP Tooling' + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $EsrpCodeSigningTool = (gci -directory -filter EsrpCodeSigning_* $(Agent.RootDirectory)\_tasks | Select-Object -last 1).FullName + $Version = (gci -directory $EsrpCodeSigningTool | Select-Object -last 1).FullName + echo "##vso[task.setvariable variable=EsrpCliDllPath]$Version\net6.0\esrpcli.dll" + displayName: Find ESRP CLI + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npx deemon --detach --wait -- npx zx build/azure-pipelines/win32/codesign.ts } + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + displayName: ✍️ Codesign + + - ${{ if or(eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true), eq(parameters.VSCODE_RUN_BROWSER_TESTS, true), eq(parameters.VSCODE_RUN_REMOTE_TESTS, true)) }}: + - template: product-build-win32-test.yml@self + parameters: + VSCODE_ARCH: ${{ parameters.VSCODE_ARCH }} + VSCODE_RUN_ELECTRON_TESTS: ${{ parameters.VSCODE_RUN_ELECTRON_TESTS }} + VSCODE_RUN_BROWSER_TESTS: ${{ parameters.VSCODE_RUN_BROWSER_TESTS }} + VSCODE_RUN_REMOTE_TESTS: ${{ parameters.VSCODE_RUN_REMOTE_TESTS }} + + - ${{ if ne(parameters.VSCODE_CIBUILD, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npx deemon --attach -- npx zx build/azure-pipelines/win32/codesign.ts } + condition: succeededOrFailed() + displayName: "✍️ Post-job: Codesign" + + - powershell: | + $ErrorActionPreference = "Stop" + + $PackageJsonPath = (Get-ChildItem -Path "..\VSCode-win32-$(VSCODE_ARCH)" -Name "package.json" -Recurse | Select-Object -First 1) + $PackageJson = Get-Content -Raw -Path ..\VSCode-win32-$(VSCODE_ARCH)\$PackageJsonPath | ConvertFrom-Json + $Version = $PackageJson.version + + mkdir $(Build.ArtifactStagingDirectory)\out\system-setup -Force + mv .build\win32-$(VSCODE_ARCH)\system-setup\VSCodeSetup.exe $(Build.ArtifactStagingDirectory)\out\system-setup\VSCodeSetup-$(VSCODE_ARCH)-$Version.exe + + mkdir $(Build.ArtifactStagingDirectory)\out\user-setup -Force + mv .build\win32-$(VSCODE_ARCH)\user-setup\VSCodeSetup.exe $(Build.ArtifactStagingDirectory)\out\user-setup\VSCodeUserSetup-$(VSCODE_ARCH)-$Version.exe + + mkdir $(Build.ArtifactStagingDirectory)\out\archive -Force + mv .build\win32-$(VSCODE_ARCH)\VSCode-win32-$(VSCODE_ARCH).zip $(Build.ArtifactStagingDirectory)\out\archive\VSCode-win32-$(VSCODE_ARCH)-$Version.zip + + mkdir $(Build.ArtifactStagingDirectory)\out\server -Force + mv .build\win32-$(VSCODE_ARCH)\vscode-server-win32-$(VSCODE_ARCH).zip $(Build.ArtifactStagingDirectory)\out\server\vscode-server-win32-$(VSCODE_ARCH).zip + + mkdir $(Build.ArtifactStagingDirectory)\out\web -Force + mv .build\win32-$(VSCODE_ARCH)\vscode-server-win32-$(VSCODE_ARCH)-web.zip $(Build.ArtifactStagingDirectory)\out\web\vscode-server-win32-$(VSCODE_ARCH)-web.zip + + echo "##vso[task.setvariable variable=VSCODE_VERSION]$Version" + displayName: Move artifacts to out directory diff --git a/build/azure-pipelines/win32/steps/product-build-win32-install-rust.yml b/build/azure-pipelines/win32/steps/product-build-win32-install-rust.yml new file mode 100644 index 0000000000000..a9c3b7e6432af --- /dev/null +++ b/build/azure-pipelines/win32/steps/product-build-win32-install-rust.yml @@ -0,0 +1,51 @@ +parameters: + - name: channel + type: string + default: 1.88 + - name: targets + default: [] + type: object + +# Todo: use 1ES pipeline once extension is installed in ADO + +steps: + - task: RustInstaller@1 + inputs: + rustVersion: ms-${{ parameters.channel }} + cratesIoFeedOverride: $(CARGO_REGISTRY) + additionalTargets: ${{ join(' ', parameters.targets) }} + toolchainFeed: https://pkgs.dev.azure.com/monacotools/Monaco/_packaging/vscode/nuget/v3/index.json + default: true + addToPath: true + displayName: Install MSFT Rust + condition: and(succeeded(), ne(variables['CARGO_REGISTRY'], 'none')) + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + Invoke-WebRequest -Uri "https://win.rustup.rs" -Outfile $(Build.ArtifactStagingDirectory)/rustup-init.exe + exec { $(Build.ArtifactStagingDirectory)/rustup-init.exe -y --profile minimal --default-toolchain $env:RUSTUP_TOOLCHAIN --default-host x86_64-pc-windows-msvc } + echo "##vso[task.prependpath]$env:USERPROFILE\.cargo\bin" + env: + RUSTUP_TOOLCHAIN: ${{ parameters.channel }} + displayName: Install OSS Rust + condition: and(succeeded(), eq(variables['CARGO_REGISTRY'], 'none')) + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + exec { rustup default $RUSTUP_TOOLCHAIN } + exec { rustup update $RUSTUP_TOOLCHAIN } + env: + RUSTUP_TOOLCHAIN: ${{ parameters.channel }} + displayName: "Set Rust version" + condition: and(succeeded(), eq(variables['CARGO_REGISTRY'], 'none')) + + - ${{ each target in parameters.targets }}: + - script: rustup target add ${{ target }} + displayName: "Adding Rust target '${{ target }}'" + condition: and(succeeded(), eq(variables['CARGO_REGISTRY'], 'none')) + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + exec { rustc --version } + exec { cargo --version } + displayName: "Check Rust versions" diff --git a/build/azure-pipelines/win32/steps/product-build-win32-test.yml b/build/azure-pipelines/win32/steps/product-build-win32-test.yml new file mode 100644 index 0000000000000..89d9bdded5056 --- /dev/null +++ b/build/azure-pipelines/win32/steps/product-build-win32-test.yml @@ -0,0 +1,150 @@ +parameters: + - name: VSCODE_ARCH + type: string + - name: VSCODE_RUN_ELECTRON_TESTS + type: boolean + - name: VSCODE_RUN_BROWSER_TESTS + type: boolean + - name: VSCODE_RUN_REMOTE_TESTS + type: boolean + +steps: + - powershell: npm exec -- npm-run-all -lp "electron $(VSCODE_ARCH)" "playwright-install" + env: + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Download Electron and Playwright + retryCountOnTaskFailure: 3 + + - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: + - powershell: .\scripts\test.bat --build --tfs "Unit Tests" + displayName: 🧪 Run unit tests (Electron) + timeoutInMinutes: 15 + - powershell: npm run test-node -- --build + displayName: 🧪 Run unit tests (node.js) + timeoutInMinutes: 15 + + - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: + - powershell: npm run test-browser-no-install -- --build --browser chromium --tfs "Browser Unit Tests" + displayName: 🧪 Run unit tests (Browser, Chromium) + timeoutInMinutes: 20 + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + exec { npm run gulp ` + compile-extension:configuration-editing ` + compile-extension:css-language-features-server ` + compile-extension:emmet ` + compile-extension:git ` + compile-extension:github-authentication ` + compile-extension:html-language-features-server ` + compile-extension:ipynb ` + compile-extension:notebook-renderers ` + compile-extension:json-language-features-server ` + compile-extension:markdown-language-features ` + compile-extension-media ` + compile-extension:microsoft-authentication ` + compile-extension:typescript-language-features ` + compile-extension:vscode-api-tests ` + compile-extension:vscode-colorize-tests ` + compile-extension:vscode-colorize-perf-tests ` + compile-extension:vscode-test-resolver ` + } + displayName: Build integration tests + + - powershell: .\build\azure-pipelines\win32\listprocesses.bat + displayName: Diagnostics before integration test runs + continueOnError: true + condition: succeededOrFailed() + + - powershell: | + # Copy client, server and web builds to a separate test directory, to avoid Access Denied errors in codesign + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $TestDir = "$(agent.builddirectory)\test" + New-Item -ItemType Directory -Path $TestDir -Force + Copy-Item -Path "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" -Destination "$TestDir\VSCode-win32-$(VSCODE_ARCH)" -Recurse -Force + Copy-Item -Path "$(agent.builddirectory)\vscode-server-win32-$(VSCODE_ARCH)" -Destination "$TestDir\vscode-server-win32-$(VSCODE_ARCH)" -Recurse -Force + Copy-Item -Path "$(agent.builddirectory)\vscode-server-win32-$(VSCODE_ARCH)-web" -Destination "$TestDir\vscode-server-win32-$(VSCODE_ARCH)-web" -Recurse -Force + displayName: Copy builds to test directory + + - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: + - powershell: | + # Figure out the full absolute path of the product we just built + # including the remote server and configure the integration tests + # to run with these builds instead of running out of sources. + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\test\VSCode-win32-$(VSCODE_ARCH)" + $ProductJsonPath = (Get-ChildItem -Path "$AppRoot" -Name "product.json" -Recurse | Select-Object -First 1) + $AppProductJson = Get-Content -Raw -Path "$AppRoot\$ProductJsonPath" | ConvertFrom-Json + $AppNameShort = $AppProductJson.nameShort + $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH)" + exec { .\scripts\test-integration.bat --build --tfs "Integration Tests" } + displayName: 🧪 Run integration tests (Electron) + timeoutInMinutes: 20 + + - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH)-web" + exec { .\scripts\test-web-integration.bat --browser firefox } + displayName: 🧪 Run integration tests (Browser, Firefox) + timeoutInMinutes: 20 + + - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\test\VSCode-win32-$(VSCODE_ARCH)" + $ProductJsonPath = (Get-ChildItem -Path "$AppRoot" -Name "product.json" -Recurse | Select-Object -First 1) + $AppProductJson = Get-Content -Raw -Path "$AppRoot\$ProductJsonPath" | ConvertFrom-Json + $AppNameShort = $AppProductJson.nameShort + $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH)" + exec { .\scripts\test-remote-integration.bat } + displayName: 🧪 Run integration tests (Remote) + timeoutInMinutes: 20 + + - powershell: .\build\azure-pipelines\win32\listprocesses.bat + displayName: Diagnostics after integration test runs + continueOnError: true + condition: succeededOrFailed() + + - powershell: .\build\azure-pipelines\win32\listprocesses.bat + displayName: Diagnostics before smoke test run + continueOnError: true + condition: succeededOrFailed() + + - ${{ if eq(parameters.VSCODE_RUN_ELECTRON_TESTS, true) }}: + - powershell: npm run smoketest-no-compile -- --tracing --build "$(agent.builddirectory)\test\VSCode-win32-$(VSCODE_ARCH)" + displayName: 🧪 Run smoke tests (Electron) + timeoutInMinutes: 20 + + - ${{ if eq(parameters.VSCODE_RUN_BROWSER_TESTS, true) }}: + - powershell: npm run smoketest-no-compile -- --web --tracing --headless + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH)-web + displayName: 🧪 Run smoke tests (Browser, Chromium) + timeoutInMinutes: 20 + + - ${{ if eq(parameters.VSCODE_RUN_REMOTE_TESTS, true) }}: + - powershell: npm run smoketest-no-compile -- --tracing --remote --build "$(agent.builddirectory)\test\VSCode-win32-$(VSCODE_ARCH)" + env: + VSCODE_REMOTE_SERVER_PATH: $(agent.builddirectory)\test\vscode-server-win32-$(VSCODE_ARCH) + displayName: 🧪 Run smoke tests (Remote) + timeoutInMinutes: 20 + + - powershell: .\build\azure-pipelines\win32\listprocesses.bat + displayName: Diagnostics after smoke test run + continueOnError: true + condition: succeededOrFailed() + + - task: PublishTestResults@2 + displayName: Publish Tests Results + inputs: + testResultsFiles: "*-results.xml" + searchFolder: "$(Build.ArtifactStagingDirectory)/test-results" + condition: succeededOrFailed() diff --git a/build/buildfile.js b/build/buildfile.js deleted file mode 100644 index 3acb1218b99e4..0000000000000 --- a/build/buildfile.js +++ /dev/null @@ -1,59 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * @param {string} name - * @returns {import('./lib/bundle').IEntryPoint} - */ -function createModuleDescription(name) { - return { - name - }; -} - -exports.workerEditor = createModuleDescription('vs/editor/common/services/editorWebWorkerMain'); -exports.workerExtensionHost = createModuleDescription('vs/workbench/api/worker/extensionHostWorkerMain'); -exports.workerNotebook = createModuleDescription('vs/workbench/contrib/notebook/common/services/notebookWebWorkerMain'); -exports.workerLanguageDetection = createModuleDescription('vs/workbench/services/languageDetection/browser/languageDetectionWebWorkerMain'); -exports.workerLocalFileSearch = createModuleDescription('vs/workbench/services/search/worker/localFileSearchMain'); -exports.workerProfileAnalysis = createModuleDescription('vs/platform/profiling/electron-browser/profileAnalysisWorkerMain'); -exports.workerOutputLinks = createModuleDescription('vs/workbench/contrib/output/common/outputLinkComputerMain'); -exports.workerBackgroundTokenization = createModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.workerMain'); - -exports.workbenchDesktop = [ - createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp'), - createModuleDescription('vs/platform/files/node/watcher/watcherMain'), - createModuleDescription('vs/platform/terminal/node/ptyHostMain'), - createModuleDescription('vs/workbench/api/node/extensionHostProcess'), - createModuleDescription('vs/workbench/workbench.desktop.main') -]; - -exports.workbenchWeb = createModuleDescription('vs/workbench/workbench.web.main'); - -exports.keyboardMaps = [ - createModuleDescription('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.linux'), - createModuleDescription('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.darwin'), - createModuleDescription('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.win') -]; - -exports.code = [ - // 'vs/code/electron-main/main' is not included here because it comes in via ./src/main.js - // 'vs/code/node/cli' is not included here because it comes in via ./src/cli.js - createModuleDescription('vs/code/node/cliProcessMain'), - createModuleDescription('vs/code/electron-utility/sharedProcess/sharedProcessMain'), - createModuleDescription('vs/code/electron-browser/workbench/workbench'), -]; - -exports.codeWeb = createModuleDescription('vs/code/browser/workbench/workbench'); - -exports.codeServer = [ - // 'vs/server/node/server.main' is not included here because it gets inlined via ./src/server-main.js - // 'vs/server/node/server.cli' is not included here because it gets inlined via ./src/server-cli.js - createModuleDescription('vs/workbench/api/node/extensionHostProcess'), - createModuleDescription('vs/platform/files/node/watcher/watcherMain'), - createModuleDescription('vs/platform/terminal/node/ptyHostMain') -]; - -exports.entrypoint = createModuleDescription; diff --git a/build/buildfile.ts b/build/buildfile.ts new file mode 100644 index 0000000000000..168539f4cae5f --- /dev/null +++ b/build/buildfile.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { IEntryPoint } from './lib/bundle.ts'; + +function createModuleDescription(name: string): IEntryPoint { + return { + name + }; +} + +export const workerEditor = createModuleDescription('vs/editor/common/services/editorWebWorkerMain'); +export const workerExtensionHost = createModuleDescription('vs/workbench/api/worker/extensionHostWorkerMain'); +export const workerNotebook = createModuleDescription('vs/workbench/contrib/notebook/common/services/notebookWebWorkerMain'); +export const workerLanguageDetection = createModuleDescription('vs/workbench/services/languageDetection/browser/languageDetectionWebWorkerMain'); +export const workerLocalFileSearch = createModuleDescription('vs/workbench/services/search/worker/localFileSearchMain'); +export const workerProfileAnalysis = createModuleDescription('vs/platform/profiling/electron-browser/profileAnalysisWorkerMain'); +export const workerOutputLinks = createModuleDescription('vs/workbench/contrib/output/common/outputLinkComputerMain'); +export const workerBackgroundTokenization = createModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.workerMain'); + +export const workbenchDesktop = [ + createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp'), + createModuleDescription('vs/platform/files/node/watcher/watcherMain'), + createModuleDescription('vs/platform/terminal/node/ptyHostMain'), + createModuleDescription('vs/workbench/api/node/extensionHostProcess'), + createModuleDescription('vs/workbench/workbench.desktop.main') +]; + +export const workbenchWeb = createModuleDescription('vs/workbench/workbench.web.main.internal'); + +export const keyboardMaps = [ + createModuleDescription('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.linux'), + createModuleDescription('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.darwin'), + createModuleDescription('vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.win') +]; + +export const code = [ + // 'vs/code/electron-main/main' is not included here because it comes in via ./src/main.js + // 'vs/code/node/cli' is not included here because it comes in via ./src/cli.js + createModuleDescription('vs/code/node/cliProcessMain'), + createModuleDescription('vs/code/electron-utility/sharedProcess/sharedProcessMain'), + createModuleDescription('vs/code/electron-browser/workbench/workbench'), +]; + +export const codeWeb = createModuleDescription('vs/code/browser/workbench/workbench'); + +export const codeServer = [ + // 'vs/server/node/server.main' is not included here because it gets inlined via ./src/server-main.js + // 'vs/server/node/server.cli' is not included here because it gets inlined via ./src/server-cli.js + createModuleDescription('vs/workbench/api/node/extensionHostProcess'), + createModuleDescription('vs/platform/files/node/watcher/watcherMain'), + createModuleDescription('vs/platform/terminal/node/ptyHostMain') +]; + +export const entrypoint = createModuleDescription; + +const buildfile = { + workerEditor, + workerExtensionHost, + workerNotebook, + workerLanguageDetection, + workerLocalFileSearch, + workerProfileAnalysis, + workerOutputLinks, + workerBackgroundTokenization, + workbenchDesktop, + workbenchWeb, + keyboardMaps, + code, + codeWeb, + codeServer, + entrypoint: createModuleDescription +}; + +export default buildfile; diff --git a/build/builtin/.eslintrc b/build/builtin/.eslintrc deleted file mode 100644 index 84e384941f3a4..0000000000000 --- a/build/builtin/.eslintrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "env": { - "node": true, - "es6": true, - "browser": true - }, - "rules": { - "no-console": 0, - "no-cond-assign": 0, - "no-unused-vars": 1, - "no-extra-semi": "warn", - "semi": "warn" - }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaFeatures": { - "experimentalObjectRestSpread": true - } - } -} \ No newline at end of file diff --git a/build/checker/layersChecker.js b/build/checker/layersChecker.js deleted file mode 100644 index b2e319b5ecb33..0000000000000 --- a/build/checker/layersChecker.js +++ /dev/null @@ -1,136 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const typescript_1 = __importDefault(require("typescript")); -const fs_1 = require("fs"); -const path_1 = require("path"); -const minimatch_1 = require("minimatch"); -// -// ############################################################################################# -// -// A custom typescript checker for the specific task of detecting the use of certain types in a -// layer that does not allow such use. -// -// Make changes to below RULES to lift certain files from these checks only if absolutely needed -// -// NOTE: Most layer checks are done via tsconfig..json files. -// -// ############################################################################################# -// -// Types that are defined in a common layer but are known to be only -// available in native environments should not be allowed in browser -const NATIVE_TYPES = [ - 'NativeParsedArgs', - 'INativeEnvironmentService', - 'AbstractNativeEnvironmentService', - 'INativeWindowConfiguration', - 'ICommonNativeHostService', - 'INativeHostService', - 'IMainProcessService', - 'INativeBrowserElementsService', -]; -const RULES = [ - // Tests: skip - { - target: '**/vs/**/test/**', - skip: true // -> skip all test files - }, - // Common: vs/platform services that can access native types - { - target: `**/vs/platform/{${[ - 'environment/common/*.ts', - 'window/common/window.ts', - 'native/common/native.ts', - 'native/common/nativeHostService.ts', - 'browserElements/common/browserElements.ts', - 'browserElements/common/nativeBrowserElementsService.ts' - ].join(',')}}`, - disallowedTypes: [ /* Ignore native types that are defined from here */], - }, - // Common: vs/base/parts/sandbox/electron-browser/preload{,-aux}.ts - { - target: '**/vs/base/parts/sandbox/electron-browser/preload{,-aux}.ts', - disallowedTypes: NATIVE_TYPES, - }, - // Common - { - target: '**/vs/**/common/**', - disallowedTypes: NATIVE_TYPES, - }, - // Common - { - target: '**/vs/**/worker/**', - disallowedTypes: NATIVE_TYPES, - }, - // Browser - { - target: '**/vs/**/browser/**', - disallowedTypes: NATIVE_TYPES, - }, - // Electron (main, utility) - { - target: '**/vs/**/{electron-main,electron-utility}/**', - disallowedTypes: [ - 'ipcMain' // not allowed, use validatedIpcMain instead - ] - } -]; -const TS_CONFIG_PATH = (0, path_1.join)(__dirname, '../../', 'src', 'tsconfig.json'); -let hasErrors = false; -function checkFile(program, sourceFile, rule) { - checkNode(sourceFile); - function checkNode(node) { - if (node.kind !== typescript_1.default.SyntaxKind.Identifier) { - return typescript_1.default.forEachChild(node, checkNode); // recurse down - } - const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(node); - if (!symbol) { - return; - } - let text = symbol.getName(); - let _parentSymbol = symbol; - while (_parentSymbol.parent) { - _parentSymbol = _parentSymbol.parent; - } - const parentSymbol = _parentSymbol; - text = parentSymbol.getName(); - if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { - const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/checker/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1}). Learn more about our source code organization at https://github.com/microsoft/vscode/wiki/Source-Code-Organization.`); - hasErrors = true; - return; - } - } -} -function createProgram(tsconfigPath) { - const tsConfig = typescript_1.default.readConfigFile(tsconfigPath, typescript_1.default.sys.readFile); - const configHostParser = { fileExists: fs_1.existsSync, readDirectory: typescript_1.default.sys.readDirectory, readFile: file => (0, fs_1.readFileSync)(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' }; - const tsConfigParsed = typescript_1.default.parseJsonConfigFileContent(tsConfig.config, configHostParser, (0, path_1.resolve)((0, path_1.dirname)(tsconfigPath)), { noEmit: true }); - const compilerHost = typescript_1.default.createCompilerHost(tsConfigParsed.options, true); - return typescript_1.default.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost); -} -// -// Create program and start checking -// -const program = createProgram(TS_CONFIG_PATH); -for (const sourceFile of program.getSourceFiles()) { - for (const rule of RULES) { - if ((0, minimatch_1.match)([sourceFile.fileName], rule.target).length > 0) { - if (!rule.skip) { - checkFile(program, sourceFile, rule); - } - break; - } - } -} -if (hasErrors) { - process.exit(1); -} -//# sourceMappingURL=layersChecker.js.map \ No newline at end of file diff --git a/build/checker/layersChecker.ts b/build/checker/layersChecker.ts index 68e12e61c40c3..87341dcffd09b 100644 --- a/build/checker/layersChecker.ts +++ b/build/checker/layersChecker.ts @@ -6,7 +6,7 @@ import ts from 'typescript'; import { readFileSync, existsSync } from 'fs'; import { resolve, dirname, join } from 'path'; -import { match } from 'minimatch'; +import minimatch from 'minimatch'; // // ############################################################################################# @@ -88,7 +88,7 @@ const RULES: IRule[] = [ } ]; -const TS_CONFIG_PATH = join(__dirname, '../../', 'src', 'tsconfig.json'); +const TS_CONFIG_PATH = join(import.meta.dirname, '../../', 'src', 'tsconfig.json'); interface IRule { target: string; @@ -151,7 +151,7 @@ const program = createProgram(TS_CONFIG_PATH); for (const sourceFile of program.getSourceFiles()) { for (const rule of RULES) { - if (match([sourceFile.fileName], rule.target).length > 0) { + if (minimatch.match([sourceFile.fileName], rule.target).length > 0) { if (!rule.skip) { checkFile(program, sourceFile, rule); } diff --git a/build/checker/tsconfig.browser.json b/build/checker/tsconfig.browser.json index 67868ef757556..91cb9fe0e70ab 100644 --- a/build/checker/tsconfig.browser.json +++ b/build/checker/tsconfig.browser.json @@ -2,7 +2,7 @@ "extends": "../../src/tsconfig.base.json", "compilerOptions": { "lib": [ - "ES2022", + "ES2024", "DOM", "DOM.Iterable" ], diff --git a/build/checker/tsconfig.node.json b/build/checker/tsconfig.node.json index 2e483136e0892..4fe5c10623d65 100644 --- a/build/checker/tsconfig.node.json +++ b/build/checker/tsconfig.node.json @@ -2,7 +2,7 @@ "extends": "../../src/tsconfig.base.json", "compilerOptions": { "lib": [ - "ES2022" + "ES2024" ], "types": [ "node" diff --git a/build/checker/tsconfig.worker.json b/build/checker/tsconfig.worker.json index ebb919bf0f20c..39d3a58453270 100644 --- a/build/checker/tsconfig.worker.json +++ b/build/checker/tsconfig.worker.json @@ -2,7 +2,7 @@ "extends": "../../src/tsconfig.base.json", "compilerOptions": { "lib": [ - "ES2022", + "ES2024", "WebWorker", "Webworker.Iterable", "WebWorker.AsyncIterable" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index 3f1d7d7f281d7..d5b72a3e55dd9 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,75 +1,75 @@ -de5712504b5788c72fdb9f685e03973cee97376db3fd985db91d254c6d35ca67 *chromedriver-v37.2.3-darwin-arm64.zip -aa088058f7ca524fbf54dff0b32c4d1d3f838270556fc47f6e0e0ab8b4f7afbc *chromedriver-v37.2.3-darwin-x64.zip -c2723fd4abf560a907416d7cbdeba6ea7c7fbf3204381cba3651a21f93161914 *chromedriver-v37.2.3-linux-arm64.zip -90ec08ab96e4d72c75da592989104cebe4bc44ca7a1ab8a25c4d58f55908c84c *chromedriver-v37.2.3-linux-armv7l.zip -7fb75bc68ada0e75f324d392f91b37528b7d4b84ee07f236b29bddd8a658c9b8 *chromedriver-v37.2.3-linux-x64.zip -b0566801e6f1fab3119d284b4ef6521294f1c5385fddfca280f7b0b5d77b48b1 *chromedriver-v37.2.3-mas-arm64.zip -65a178f545e8227b122bcd5061151010cc8b81f7181d9b56167190de7b301e40 *chromedriver-v37.2.3-mas-x64.zip -e26c99c5c2e25268dff892edadb135cb432efff320a63f913dc699cb77d504f1 *chromedriver-v37.2.3-win32-arm64.zip -1bf7c781c8afe9c640f9d87a8599bf8b896e98436d20b2ea18bed2bdd64eb141 *chromedriver-v37.2.3-win32-ia32.zip -7a22e4bba4e47b352cc4d3fe2fb219c81cb571b4ab8e7ebb7fa4b0057dd72651 *chromedriver-v37.2.3-win32-x64.zip -0aaf0c84a9d45af7a5e6139d6491839df9a039fe7f24f14e11cc8fa481b23c2c *electron-api.json -a6e91e67743ab0d8b6f2bb42adcec5db79af02ffee462b7157a0c8463a1cbd5d *electron-v37.2.3-darwin-arm64-dsym-snapshot.zip -ae920fb7fe46bc561ab403a79691c9030d67b805941189d32f2bf490fa5fe179 *electron-v37.2.3-darwin-arm64-dsym.zip -e486968c91b8d43ee65604d9040063dc4ac24faa65c9b2b63757fc95ca984051 *electron-v37.2.3-darwin-arm64-symbols.zip -134e2ec499d5b1e7c63c0b3c8a1389555a4384f6efc7a3749daebff8d4c634ba *electron-v37.2.3-darwin-arm64.zip -97264a03101ff180aac09ecf173d570f3563359240eba98db0794257ab9f65a6 *electron-v37.2.3-darwin-x64-dsym-snapshot.zip -6ffd52a7a66a39b89ce19d0ce6ad77e8fabd172acde283c1b11cff2faad5aaf2 *electron-v37.2.3-darwin-x64-dsym.zip -df9ad784a2de4f47506796f99f427b75f9f698260dea103dc1e244c40c009daf *electron-v37.2.3-darwin-x64-symbols.zip -bed936d54fb1eb31712ed34c22e772392bcaf85f1d41f12653a027822c305383 *electron-v37.2.3-darwin-x64.zip -e9ce595710269f7ec03897aa50b6753d8dc15b3d4316b9260bd496c2d9bfe278 *electron-v37.2.3-linux-arm64-debug.zip -5611a6c2c76cdda7d396575886a3f4b2c0179a3dc208ad073e02f2b401cdff54 *electron-v37.2.3-linux-arm64-symbols.zip -95e5bd64f372598dff73673802f262e6feb4e4a308c3734b9b1e127020e6e36c *electron-v37.2.3-linux-arm64.zip -e9ce595710269f7ec03897aa50b6753d8dc15b3d4316b9260bd496c2d9bfe278 *electron-v37.2.3-linux-armv7l-debug.zip -732185759d1f1a16a5da57b0f71b70a9e29fdaaad2605a6b0ecdf2190318b32d *electron-v37.2.3-linux-armv7l-symbols.zip -50705d920c2feb03e7b57f700384349ed00bed2f9764b06bdbc2b8d2e97d000d *electron-v37.2.3-linux-armv7l.zip -4395be68ece00c54f1f0835103740526ab47ebed0ff4815a7866d6cb3988111e *electron-v37.2.3-linux-x64-debug.zip -8a88fec134b5bbf6b2bc834af996e02f79c1a2945997829a6089cf9bc748448c *electron-v37.2.3-linux-x64-symbols.zip -90aa32126cbd9c843fbbb844613f6bb41a8fad212cf7290e9383bfa1ef541022 *electron-v37.2.3-linux-x64.zip -01df2a4b0cef3d3a4ace426b00573f6f6cd271085e23542be29b58fa9b3011de *electron-v37.2.3-mas-arm64-dsym-snapshot.zip -9e4b33a8ef2f0532f1505bf3461388bcfe71c723cb743281e1ff560d59cf7621 *electron-v37.2.3-mas-arm64-dsym.zip -cec5a4e152cb0a9bb1ba631f8662d046bfe9077139b93a54b8353adc28e90014 *electron-v37.2.3-mas-arm64-symbols.zip -b4e8709061c82179b188c95a9058c938b2e8761131a912e8d58e8af8e60749ea *electron-v37.2.3-mas-arm64.zip -3d151d9d1447b2c4c370483eb870eb0dbfaba1a078ec272a4466387ad9327faa *electron-v37.2.3-mas-x64-dsym-snapshot.zip -180eda5fcbe1fafc0a200c732f810a5842a1903566e1a6c2c46e49ddbf7057d2 *electron-v37.2.3-mas-x64-dsym.zip -9d4ca373b4f9c21ebe767955d9cd887b6966a7bfc08ac31642ad1612184a1e53 *electron-v37.2.3-mas-x64-symbols.zip -ccba94267fb2e7eff125bc05ac82962cddd31774e6eaac71d45deca1a9ee8440 *electron-v37.2.3-mas-x64.zip -5a0c3715b9b4e68d2af24b01d70babaa2d3bef8a91f29f784225f2c29c53305a *electron-v37.2.3-win32-arm64-pdb.zip -0f77c4a013e2ba4db25d07b39f60ed036cef8c244b1d004b379ac66ff7fdcc31 *electron-v37.2.3-win32-arm64-symbols.zip -cb973ca0276f255cc7bfc78a753ea1f2494441d47bfcdbbabd83dcdacc95c72e *electron-v37.2.3-win32-arm64-toolchain-profile.zip -409b6c1b7f66fc837f10f8d3325d8f5f63b6f0c1e614e759883d3008c01e6f4f *electron-v37.2.3-win32-arm64.zip -989e4b559254ca0f086179b01b395aacd9d1662a216a59f6384375031855c5fb *electron-v37.2.3-win32-ia32-pdb.zip -8ef5da1a1e7922a95a6c9569a29926aaa3cfc0bf1ec538ae116e79dd23cb6068 *electron-v37.2.3-win32-ia32-symbols.zip -cb973ca0276f255cc7bfc78a753ea1f2494441d47bfcdbbabd83dcdacc95c72e *electron-v37.2.3-win32-ia32-toolchain-profile.zip -16980a3a4db73d03bc74bfdb8d9b5e63bc8dd55f0db02b8007182ff9e4290700 *electron-v37.2.3-win32-ia32.zip -176062444cf5d233a912f3068048521b26819843db40531890ffb343de01c585 *electron-v37.2.3-win32-x64-pdb.zip -e12c2bffd1f64387487353ba73b9abe342a9075f0e7a42a319dbb81b996a1451 *electron-v37.2.3-win32-x64-symbols.zip -cb973ca0276f255cc7bfc78a753ea1f2494441d47bfcdbbabd83dcdacc95c72e *electron-v37.2.3-win32-x64-toolchain-profile.zip -df2925e059f87316272d8a495602025561a133904f2275387710e15948ed8d8a *electron-v37.2.3-win32-x64.zip -6358a83a03a801d6a85559179e4f12307dee693043ffb26fd40585f369f7db76 *electron.d.ts -eee4934c3079592bad5d9f5249e6f848d75c635d57d4de3dc32deb8c41ddf24b *ffmpeg-v37.2.3-darwin-arm64.zip -34ee1b55530522a6ba14754734c239a2c3278d9d2b2dcb2003bed2468fd34056 *ffmpeg-v37.2.3-darwin-x64.zip -f7ba18d1f9eed9ff5440ab9683c30f9e224d39a7ee4f15b96860f7f997aba3e3 *ffmpeg-v37.2.3-linux-arm64.zip -9f6c3f968f74be387c8b35ff528ab7da02565c9bfc9e4c2055c222bc3dd4d1f4 *ffmpeg-v37.2.3-linux-armv7l.zip -eec1ae0dc0559e68f8df7e8cc48d15439f8cd5c31facf2b8ad6d64283b6cbb87 *ffmpeg-v37.2.3-linux-x64.zip -eee4934c3079592bad5d9f5249e6f848d75c635d57d4de3dc32deb8c41ddf24b *ffmpeg-v37.2.3-mas-arm64.zip -34ee1b55530522a6ba14754734c239a2c3278d9d2b2dcb2003bed2468fd34056 *ffmpeg-v37.2.3-mas-x64.zip -e56d02eedc01fd7c9568fe9a4ecbe639f12b0acfa8cdfa5fa4d5355d80e5f89a *ffmpeg-v37.2.3-win32-arm64.zip -e56d02eedc01fd7c9568fe9a4ecbe639f12b0acfa8cdfa5fa4d5355d80e5f89a *ffmpeg-v37.2.3-win32-ia32.zip -e56d02eedc01fd7c9568fe9a4ecbe639f12b0acfa8cdfa5fa4d5355d80e5f89a *ffmpeg-v37.2.3-win32-x64.zip -6db1dcb596e18554fcd74d1e765f3b3618f0bf0884fa647e007836a82d154b09 *hunspell_dictionaries.zip -4d713e685c862eba624029d73e05d51d3237ddd30e71dbc7c071a82cafd5ea2b *libcxx-objects-v37.2.3-linux-arm64.zip -6a8e546b643e0fb3b17f58d9d9ddbdfbc5ddae392a1e4e788380aa4cd4154acf *libcxx-objects-v37.2.3-linux-armv7l.zip -985ec7b7aedfd03daa0a8bf7984b685760ae9338fa9ad527b7301555677ba688 *libcxx-objects-v37.2.3-linux-x64.zip -3cbad550cb9e6ad542f61c8be23fb74c7c20491b1b89859c55fe9fcf0d2250f7 *libcxx_headers.zip -05d79984eb6648cd389100e7cb935c3244c2bad4c920cbc9b983954a55578b41 *libcxxabi_headers.zip -77536b90f7f0ad36a856f7744d326a140171d580a81c3296de3cfda8a388f95d *mksnapshot-v37.2.3-darwin-arm64.zip -740318b863aa2b172c04401c14e4a01a33ddc3d7a4b7de962d7314a82ea54dd6 *mksnapshot-v37.2.3-darwin-x64.zip -5fc8169bd8dd35dc52db50ec8d2f0e88ac28836b6ba75a58f0845ed38c343187 *mksnapshot-v37.2.3-linux-arm64-x64.zip -2c4a207668e9319eb79687af505688ceaca46f51e4d6ec387f1415456524adcd *mksnapshot-v37.2.3-linux-armv7l-x64.zip -072e0ab9f2bf0011ba4bed2ca304bf0a348f95ef3ee61d88ed284aaa991c8a03 *mksnapshot-v37.2.3-linux-x64.zip -db8449f46b1fd6efdff10b889c795001dd9174b84c27c9cf6146d55a261625eb *mksnapshot-v37.2.3-mas-arm64.zip -3906ef5f2acabaf8efc346a7623c853202fe65a96e0666d28d6d48dfcce4a588 *mksnapshot-v37.2.3-mas-x64.zip -d47b365febeeebb6479c51fb51d06e69d4e72afcf48827fe2371a0b043a3d21b *mksnapshot-v37.2.3-win32-arm64-x64.zip -7dee714ecf1e5900b8f9745f73d41ee420e66115bce8403daf106c6e56cc12a8 *mksnapshot-v37.2.3-win32-ia32.zip -2ac179b35e3648c34b23180153aa1107874f3b1ec2d0e1485cb1fa246aa1cb84 *mksnapshot-v37.2.3-win32-x64.zip +ab4c5ce64b92082b15f11ed2a89766fa5542b33d656872678ca0aee99e51a7c8 *chromedriver-v39.2.7-darwin-arm64.zip +976f03f6e5e1680e5f8449bd04da531aabec0b664ff462a14f0d41fad0b437af *chromedriver-v39.2.7-darwin-x64.zip +28649b04333820f826ea658d18f8111e0a187b3afc498af05b5c59b27ac00155 *chromedriver-v39.2.7-linux-arm64.zip +149033ccf7f909214c7d69788bdef2e4ce164cae1091a2f8220f62e495576f9b *chromedriver-v39.2.7-linux-armv7l.zip +6a071551518eddc688dd348d3e63b0c55f744589a041943e5706bebfd5337f19 *chromedriver-v39.2.7-linux-x64.zip +824ea4699fd6aa6822e453496ebf576174d04e0f0991843b77eb346a842725bc *chromedriver-v39.2.7-mas-arm64.zip +aa991650a765b2bc168f8b742341048fa030ee9e3bd0d0799e1b1d29a4c55d0b *chromedriver-v39.2.7-mas-x64.zip +a8fc4467bf9be10de3e341648ccd6ad6d618b4456a744137e9f19bd5f9d9bd37 *chromedriver-v39.2.7-win32-arm64.zip +01b247563a054617530e454646b086352bc03e02ad4f18e5b65b4e3dfd276a1e *chromedriver-v39.2.7-win32-ia32.zip +a8bc2b9052ac8dadeaf88ea9cd6e46ec0032eee2345a0548741bfed922520579 *chromedriver-v39.2.7-win32-x64.zip +23486b3effffe5b3bc3ca70261fc9abe2396fd5d018652494f73e3f48cfe57cf *electron-api.json +8bee9e905544e60e08468efca91481ec467ab8f108a81846c365782ba0fc737c *electron-v39.2.7-darwin-arm64-dsym-snapshot.zip +3be97c3152cd4a84a6fe4013f7e4712422015f4beeb13eb35f8b4d223307d39a *electron-v39.2.7-darwin-arm64-dsym.zip +6d5551120d0564fc5596a3b724258da2ce632663d12782c8fdf15a2cc461ed95 *electron-v39.2.7-darwin-arm64-symbols.zip +bda657a77c074ee0c6a0e5d5f6de17918d7cf959306b454f6fadb07a08588883 *electron-v39.2.7-darwin-arm64.zip +39f0aab332506455337edff540d007c509e72d8c419cdc57f88a0312848f51c9 *electron-v39.2.7-darwin-x64-dsym-snapshot.zip +1efed54563ede59d7ae9ba3d548b3e93ede1a4e5dfa510ca22036ea2dd8a2956 *electron-v39.2.7-darwin-x64-dsym.zip +3b9bfe84905870c9c36939ffac545d388213ffbb296b969f35ae2a098f6a32b7 *electron-v39.2.7-darwin-x64-symbols.zip +d7535e64ad54efcf0fae84d7fea4c2ee4727eec99c78d2a5acc695285cb0a9f0 *electron-v39.2.7-darwin-x64.zip +59a3bd71f9c1b355dfbc43f233126cd32b82a53439f0d419e6349044d39e8bbf *electron-v39.2.7-linux-arm64-debug.zip +1b326f1a5bea47d9be742554434ddf4f094d7bcdd256f440b808359dc78fcd33 *electron-v39.2.7-linux-arm64-symbols.zip +445465a43bd2ffaec09877f4ed46385065632a4683c2806cc6211cc73c110024 *electron-v39.2.7-linux-arm64.zip +300c8d11d82cd1257b08e5a08c5e315f758133b627c0271a0f249ba3cb4533d2 *electron-v39.2.7-linux-armv7l-debug.zip +034dca3c137c7bfe0736456c1aa0941721e3a9f3a8a72a2786cb817d4edb0f9d *electron-v39.2.7-linux-armv7l-symbols.zip +5de99e9f4de8c9ac2fb93df725e834e3e93194c08c99968def7f7b78594fc97c *electron-v39.2.7-linux-armv7l.zip +64ef2ae24ae0869ebadb34b178fd7e8375d750d7afe39b42cfa28824f0d11445 *electron-v39.2.7-linux-x64-debug.zip +63466c4b6024ae38fdb38ff116abd561b9e36b8d4cd8f8aefbe41289950dba0c *electron-v39.2.7-linux-x64-symbols.zip +2f5285ef563dca154aa247696dddef545d3d895dd9b227ed423ea0d43737c22c *electron-v39.2.7-linux-x64.zip +ef5a108c1d10148aa031300da10c78feee797afe4ca2a2839819fd8434529860 *electron-v39.2.7-mas-arm64-dsym-snapshot.zip +9dd01dc9071b1db9d8fb5e9c81eaa96f551db0a982994881e5750cde2432b0f0 *electron-v39.2.7-mas-arm64-dsym.zip +2cf34289d79906c81b3dfd043fbe19a9604cecedd9ebda6576fa3c6f27edfe23 *electron-v39.2.7-mas-arm64-symbols.zip +5658d58eacb99fb2a22df0d52ca0507d79f03c85515a123d5e9bee5e0749b93d *electron-v39.2.7-mas-arm64.zip +92cd45c3fa64e2889fd1bc6b165c4d12bea40786ce59d6d204cadec6039a8e2a *electron-v39.2.7-mas-x64-dsym-snapshot.zip +21464abc837aeab1609fbfa33aa82793e9d32a597db28ea4da483a9d6b6c668a *electron-v39.2.7-mas-x64-dsym.zip +8d6e7ffee482514b62465e418049bdf717d308118461e5d97480f5a0eb0b9e20 *electron-v39.2.7-mas-x64-symbols.zip +e3b4169ab7bf3bc35cc720ef99032acd3d0eb1521524b5c4667898758dd4e9a3 *electron-v39.2.7-mas-x64.zip +3f1d549214a2430d57e5ab8d3cc9d89363340b16905014e35417c632a94732f6 *electron-v39.2.7-win32-arm64-pdb.zip +984e1d7718bc920e75a38b114ff73fa52647349763f76e91b64458e5d0fde65f *electron-v39.2.7-win32-arm64-symbols.zip +ed66f333ff7b385b2f40845178dc2dc4f25cc887510d766433392733fdd272a3 *electron-v39.2.7-win32-arm64-toolchain-profile.zip +56c6f8d957239b7e8d5a214255f39007d44abc98f701ab61054afa83ad46e80f *electron-v39.2.7-win32-arm64.zip +c885a8af3226f28081106fa89106f4668b907a53ab3997f3b101b487a76d2878 *electron-v39.2.7-win32-ia32-pdb.zip +34edebab8fb5458d97a23461213b39360b5652f8dd6fe8bf7f9c10a17b25a1d2 *electron-v39.2.7-win32-ia32-symbols.zip +ed66f333ff7b385b2f40845178dc2dc4f25cc887510d766433392733fdd272a3 *electron-v39.2.7-win32-ia32-toolchain-profile.zip +85acd7db5dbb39e16d6c798a649342969569caa2c71d6b5bb1f0c8ae96bca32e *electron-v39.2.7-win32-ia32.zip +e6a8e1164106548a1cdf266c615d259feada249e1449df8af1f7e04252575e86 *electron-v39.2.7-win32-x64-pdb.zip +90e1feeff5968265b68d8343e27b9f329b27882747633dd10555740de67d58cc *electron-v39.2.7-win32-x64-symbols.zip +ed66f333ff7b385b2f40845178dc2dc4f25cc887510d766433392733fdd272a3 *electron-v39.2.7-win32-x64-toolchain-profile.zip +3464537fa4be6b7b073f1c9b694ac2eb1f632d6ec36f6eeac9e00d8a279f188c *electron-v39.2.7-win32-x64.zip +40c772eb189d100087b75da6c2ad1aeb044f1d661c90543592546a654b0b6d5b *electron.d.ts +5a904c2edd12542ce2b6685938cdafe21cf90cd552f2f654058353d1a3d8ee43 *ffmpeg-v39.2.7-darwin-arm64.zip +91fc23e9008f43ad3c46f690186d77b291a803451b6d89ac82aadb8ae2dd7995 *ffmpeg-v39.2.7-darwin-x64.zip +a44607619c6742c1f9d729265a687b467a25ba397081ac12bc2c0d9ab4bea37b *ffmpeg-v39.2.7-linux-arm64.zip +8128ec9be261e2c1017f9b8213f948426119306e5d3acdb59392f32b2c2f0204 *ffmpeg-v39.2.7-linux-armv7l.zip +a201a2a64a49ab39def2d38a73e92358ebb57ecae99b0bbc8058353c4be23ea1 *ffmpeg-v39.2.7-linux-x64.zip +5a904c2edd12542ce2b6685938cdafe21cf90cd552f2f654058353d1a3d8ee43 *ffmpeg-v39.2.7-mas-arm64.zip +91fc23e9008f43ad3c46f690186d77b291a803451b6d89ac82aadb8ae2dd7995 *ffmpeg-v39.2.7-mas-x64.zip +6fa4278a41d9c5d733369aa4cce694ba219eb72f7fd181060547c3a4920b5902 *ffmpeg-v39.2.7-win32-arm64.zip +12b9e02c0fd07e8bc233c7c4ebab5c737eca05c41f1c5178867cad313433561b *ffmpeg-v39.2.7-win32-ia32.zip +caedeb04aa648af14b5a20c9ca902c97eb531a456c7965639465f8764b5d95e0 *ffmpeg-v39.2.7-win32-x64.zip +f1320ff95f2cce0f0f7225b45f2b9340aeb38b341b4090f0e58f58dc2da2f3a9 *hunspell_dictionaries.zip +8f4ffd7534f21e40621c515bacd178b809c2e52d1687867c60dfdb97ed17fecb *libcxx-objects-v39.2.7-linux-arm64.zip +0497730c82e1e76b6a4c22b1af4ebb7821ff6ccb838b78503c0cc93d8a8f03ee *libcxx-objects-v39.2.7-linux-armv7l.zip +271e3538eb241f1bc83a103ea7d4c8408ee6bd38322ed50dca781f54d002a590 *libcxx-objects-v39.2.7-linux-x64.zip +9a243728553395448f783591737fb229a327499d6853b51e201c36e4aaa9796f *libcxx_headers.zip +db3018609bce502c307c59074b3d5273080a68fb50ac1e7fc580994a2e80cc25 *libcxxabi_headers.zip +509d0890d1a524efe2c68aae18d2c8fd6537e788b94c9f63fd9f9ca3be98fdb9 *mksnapshot-v39.2.7-darwin-arm64.zip +f0a98b428a6a1f8dc4a4663e876a3984157ac8757922cde7461f19755942c180 *mksnapshot-v39.2.7-darwin-x64.zip +22fda3b708ab14325b2bfba8e875fbf48b6eacea347ecf1ef41cf24b09b4af8f *mksnapshot-v39.2.7-linux-arm64-x64.zip +e7b89dbab3449c0a1862b4d129b3ee384cb5bcd53e149eae05df14744ee55cb5 *mksnapshot-v39.2.7-linux-armv7l-x64.zip +53b3ed9f3a69444915ef1eef688c8f8168d52c3d5232834b8aa249cf210b41b6 *mksnapshot-v39.2.7-linux-x64.zip +181d962eaa93d8d997b1daf99ae016b3d9d8a5ae037c96a8475490396a8d655f *mksnapshot-v39.2.7-mas-arm64.zip +de005b619da1c1afcd8f8b6c70facb1dc388c46a66f8eff3058c8a08323df173 *mksnapshot-v39.2.7-mas-x64.zip +6eea0bee6097cf2cfe3ae42b35f847304697c4a4eec84f5b60d1cbbe324a8490 *mksnapshot-v39.2.7-win32-arm64-x64.zip +3e769269aa0b51ef9664a982235bc9299fc58743dcf7bce585d49a9f4a074abd *mksnapshot-v39.2.7-win32-ia32.zip +51337124892bf76d214f89975d42ec0474199cdfac2f9e08664d86ae8e6ba43e *mksnapshot-v39.2.7-win32-x64.zip diff --git a/build/checksums/explorer-dll.txt b/build/checksums/explorer-dll.txt index fb8ad75684728..4d34e26529729 100644 --- a/build/checksums/explorer-dll.txt +++ b/build/checksums/explorer-dll.txt @@ -1,4 +1,4 @@ -11b36db4f244693381e52316261ce61678286f6bdfe2614c6352f6fecf3f060d code_explorer_command_arm64.dll -bfab3719038ca46bcd8afb9249a00f851dd08aa3cc8d13d01a917111a2a6d7c2 code_explorer_command_x64.dll -b5cd79c1e91390bdeefaf35cc5c62a6022220832e145781e5609913fac706ad9 code_insider_explorer_command_arm64.dll -f04335cc6fbe8425bd5516e6acbfa05ca706fd7566799a1e22fca1344c25351f code_insider_explorer_command_x64.dll +5dbdd08784067e4caf7d119f7bec05b181b155e1e9868dec5a6c5174ce59f8bd code_explorer_command_arm64.dll +c7b8dde71f62397fbcd1693e35f25d9ceab51b66e805b9f39efc78e02c6abf3c code_explorer_command_x64.dll +968a6fe75c7316d2e2176889dffed8b50e41ee3f1834751cf6387094709b00ef code_insider_explorer_command_arm64.dll +da071035467a64fabf8fc3762b52fa8cdb3f216aa2b252df5b25b8bdf96ec594 code_insider_explorer_command_x64.dll diff --git a/build/checksums/nodejs.txt b/build/checksums/nodejs.txt index 23c7c218638e1..43aace217e9a6 100644 --- a/build/checksums/nodejs.txt +++ b/build/checksums/nodejs.txt @@ -1,7 +1,7 @@ -615dda58b5fb41fad2be43940b6398ca56554cbe05800953afadc724729cb09e node-v22.17.0-darwin-arm64.tar.gz -c39c8ec3cdadedfcc75de0cb3305df95ae2aecebc5db8d68a9b67bd74616d2ad node-v22.17.0-darwin-x64.tar.gz -3e99df8b01b27dc8b334a2a30d1cd500442b3b0877d217b308fd61a9ccfc33d4 node-v22.17.0-linux-arm64.tar.gz -ce120efe921de3eaaba2394edaacfab3e61376a56199cb93fc7e9bf0b3f14a16 node-v22.17.0-linux-armv7l.tar.gz -0fa01328a0f3d10800623f7107fbcd654a60ec178fab1ef5b9779e94e0419e1a node-v22.17.0-linux-x64.tar.gz -b06cf160eca7522dc795b437289ebfcbd9fb309d6049a157120c3d1fd5835f55 win-arm64/node.exe -39d45b5933f339d3ebdebd76474893dab5d7da1038920f65cf5bbcf0f20f3636 win-x64/node.exe +c170d6554fba83d41d25a76cdbad85487c077e51fa73519e41ac885aa429d8af node-v22.21.1-darwin-arm64.tar.gz +8e3dc89614debe66c2a6ad2313a1adb06eb37db6cd6c40d7de6f7d987f7d1afd node-v22.21.1-darwin-x64.tar.gz +c86830dedf77f8941faa6c5a9c863bdfdd1927a336a46943decc06a38f80bfb2 node-v22.21.1-linux-arm64.tar.gz +40d3d09aee556abc297dd782864fcc6b9e60acd438ff0660ba9ddcd569c00920 node-v22.21.1-linux-armv7l.tar.gz +219a152ea859861d75adea578bdec3dce8143853c13c5187f40c40e77b0143b2 node-v22.21.1-linux-x64.tar.gz +707bbc8a9e615299ecdbff9040f88f59f20033ff1af923beee749b885cbd565d win-arm64/node.exe +471961cb355311c9a9dd8ba417eca8269ead32a2231653084112554cda52e8b3 win-x64/node.exe diff --git a/build/darwin/create-universal-app.js b/build/darwin/create-universal-app.js deleted file mode 100644 index 7d3f9164d5f10..0000000000000 --- a/build/darwin/create-universal-app.js +++ /dev/null @@ -1,63 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const path_1 = __importDefault(require("path")); -const fs_1 = __importDefault(require("fs")); -const minimatch_1 = __importDefault(require("minimatch")); -const vscode_universal_bundler_1 = require("vscode-universal-bundler"); -const root = path_1.default.dirname(path_1.default.dirname(__dirname)); -async function main(buildDir) { - const arch = process.env['VSCODE_ARCH']; - if (!buildDir) { - throw new Error('Build dir not provided'); - } - const product = JSON.parse(fs_1.default.readFileSync(path_1.default.join(root, 'product.json'), 'utf8')); - const appName = product.nameLong + '.app'; - const x64AppPath = path_1.default.join(buildDir, 'VSCode-darwin-x64', appName); - const arm64AppPath = path_1.default.join(buildDir, 'VSCode-darwin-arm64', appName); - const asarRelativePath = path_1.default.join('Contents', 'Resources', 'app', 'node_modules.asar'); - const outAppPath = path_1.default.join(buildDir, `VSCode-darwin-${arch}`, appName); - const productJsonPath = path_1.default.resolve(outAppPath, 'Contents', 'Resources', 'app', 'product.json'); - const filesToSkip = [ - '**/CodeResources', - '**/Credits.rtf', - '**/policies/{*.mobileconfig,**/*.plist}', - // TODO: Should we consider expanding this to other files in this area? - '**/node_modules/@parcel/node-addon-api/nothing.target.mk' - ]; - await (0, vscode_universal_bundler_1.makeUniversalApp)({ - x64AppPath, - arm64AppPath, - asarPath: asarRelativePath, - outAppPath, - force: true, - mergeASARs: true, - x64ArchFiles: '*/kerberos.node', - filesToSkipComparison: (file) => { - for (const expected of filesToSkip) { - if ((0, minimatch_1.default)(file, expected)) { - return true; - } - } - return false; - } - }); - const productJson = JSON.parse(fs_1.default.readFileSync(productJsonPath, 'utf8')); - Object.assign(productJson, { - darwinUniversalAssetId: 'darwin-universal' - }); - fs_1.default.writeFileSync(productJsonPath, JSON.stringify(productJson, null, '\t')); -} -if (require.main === module) { - main(process.argv[2]).catch(err => { - console.error(err); - process.exit(1); - }); -} -//# sourceMappingURL=create-universal-app.js.map \ No newline at end of file diff --git a/build/darwin/create-universal-app.ts b/build/darwin/create-universal-app.ts index 7872eccc63e34..6bda47add7129 100644 --- a/build/darwin/create-universal-app.ts +++ b/build/darwin/create-universal-app.ts @@ -8,7 +8,7 @@ import fs from 'fs'; import minimatch from 'minimatch'; import { makeUniversalApp } from 'vscode-universal-bundler'; -const root = path.dirname(path.dirname(__dirname)); +const root = path.dirname(path.dirname(import.meta.dirname)); async function main(buildDir?: string) { const arch = process.env['VSCODE_ARCH']; @@ -30,7 +30,7 @@ async function main(buildDir?: string) { '**/Credits.rtf', '**/policies/{*.mobileconfig,**/*.plist}', // TODO: Should we consider expanding this to other files in this area? - '**/node_modules/@parcel/node-addon-api/nothing.target.mk' + '**/node_modules/@vscode/node-addon-api/nothing.target.mk', ]; await makeUniversalApp({ @@ -40,7 +40,7 @@ async function main(buildDir?: string) { outAppPath, force: true, mergeASARs: true, - x64ArchFiles: '*/kerberos.node', + x64ArchFiles: '{*/kerberos.node,**/extensions/microsoft-authentication/dist/libmsalruntime.dylib,**/extensions/microsoft-authentication/dist/msal-node-runtime.node}', filesToSkipComparison: (file: string) => { for (const expected of filesToSkip) { if (minimatch(file, expected)) { @@ -58,7 +58,7 @@ async function main(buildDir?: string) { fs.writeFileSync(productJsonPath, JSON.stringify(productJson, null, '\t')); } -if (require.main === module) { +if (import.meta.main) { main(process.argv[2]).catch(err => { console.error(err); process.exit(1); diff --git a/build/darwin/sign.js b/build/darwin/sign.js deleted file mode 100644 index fb55b8aa03d54..0000000000000 --- a/build/darwin/sign.js +++ /dev/null @@ -1,105 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const osx_sign_1 = require("@electron/osx-sign"); -const cross_spawn_promise_1 = require("@malept/cross-spawn-promise"); -const root = path_1.default.dirname(path_1.default.dirname(__dirname)); -const baseDir = path_1.default.dirname(__dirname); -const product = JSON.parse(fs_1.default.readFileSync(path_1.default.join(root, 'product.json'), 'utf8')); -const helperAppBaseName = product.nameShort; -const gpuHelperAppName = helperAppBaseName + ' Helper (GPU).app'; -const rendererHelperAppName = helperAppBaseName + ' Helper (Renderer).app'; -const pluginHelperAppName = helperAppBaseName + ' Helper (Plugin).app'; -function getElectronVersion() { - const npmrc = fs_1.default.readFileSync(path_1.default.join(root, '.npmrc'), 'utf8'); - const target = /^target="(.*)"$/m.exec(npmrc)[1]; - return target; -} -function getEntitlementsForFile(filePath) { - if (filePath.includes(gpuHelperAppName)) { - return path_1.default.join(baseDir, 'azure-pipelines', 'darwin', 'helper-gpu-entitlements.plist'); - } - else if (filePath.includes(rendererHelperAppName)) { - return path_1.default.join(baseDir, 'azure-pipelines', 'darwin', 'helper-renderer-entitlements.plist'); - } - else if (filePath.includes(pluginHelperAppName)) { - return path_1.default.join(baseDir, 'azure-pipelines', 'darwin', 'helper-plugin-entitlements.plist'); - } - return path_1.default.join(baseDir, 'azure-pipelines', 'darwin', 'app-entitlements.plist'); -} -async function main(buildDir) { - const tempDir = process.env['AGENT_TEMPDIRECTORY']; - const arch = process.env['VSCODE_ARCH']; - const identity = process.env['CODESIGN_IDENTITY']; - if (!buildDir) { - throw new Error('$AGENT_BUILDDIRECTORY not set'); - } - if (!tempDir) { - throw new Error('$AGENT_TEMPDIRECTORY not set'); - } - const appRoot = path_1.default.join(buildDir, `VSCode-darwin-${arch}`); - const appName = product.nameLong + '.app'; - const infoPlistPath = path_1.default.resolve(appRoot, appName, 'Contents', 'Info.plist'); - const appOpts = { - app: path_1.default.join(appRoot, appName), - platform: 'darwin', - optionsForFile: (filePath) => ({ - entitlements: getEntitlementsForFile(filePath), - hardenedRuntime: true, - }), - preAutoEntitlements: false, - preEmbedProvisioningProfile: false, - keychain: path_1.default.join(tempDir, 'buildagent.keychain'), - version: getElectronVersion(), - identity, - }; - // Only overwrite plist entries for x64 and arm64 builds, - // universal will get its copy from the x64 build. - if (arch !== 'universal') { - await (0, cross_spawn_promise_1.spawn)('plutil', [ - '-insert', - 'NSAppleEventsUsageDescription', - '-string', - 'An application in Visual Studio Code wants to use AppleScript.', - `${infoPlistPath}` - ]); - await (0, cross_spawn_promise_1.spawn)('plutil', [ - '-replace', - 'NSMicrophoneUsageDescription', - '-string', - 'An application in Visual Studio Code wants to use the Microphone.', - `${infoPlistPath}` - ]); - await (0, cross_spawn_promise_1.spawn)('plutil', [ - '-replace', - 'NSCameraUsageDescription', - '-string', - 'An application in Visual Studio Code wants to use the Camera.', - `${infoPlistPath}` - ]); - } - await (0, osx_sign_1.sign)(appOpts); -} -if (require.main === module) { - main(process.argv[2]).catch(async (err) => { - console.error(err); - const tempDir = process.env['AGENT_TEMPDIRECTORY']; - if (tempDir) { - const keychain = path_1.default.join(tempDir, 'buildagent.keychain'); - const identities = await (0, cross_spawn_promise_1.spawn)('security', ['find-identity', '-p', 'codesigning', '-v', keychain]); - console.error(`Available identities:\n${identities}`); - const dump = await (0, cross_spawn_promise_1.spawn)('security', ['dump-keychain', keychain]); - console.error(`Keychain dump:\n${dump}`); - } - process.exit(1); - }); -} -//# sourceMappingURL=sign.js.map \ No newline at end of file diff --git a/build/darwin/sign.ts b/build/darwin/sign.ts index 83f18c6a5a7e1..fcdcb2b2d4560 100644 --- a/build/darwin/sign.ts +++ b/build/darwin/sign.ts @@ -5,11 +5,11 @@ import fs from 'fs'; import path from 'path'; -import { sign, SignOptions } from '@electron/osx-sign'; +import { sign, type SignOptions } from '@electron/osx-sign'; import { spawn } from '@malept/cross-spawn-promise'; -const root = path.dirname(path.dirname(__dirname)); -const baseDir = path.dirname(__dirname); +const root = path.dirname(path.dirname(import.meta.dirname)); +const baseDir = path.dirname(import.meta.dirname); const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); const helperAppBaseName = product.nameShort; const gpuHelperAppName = helperAppBaseName + ' Helper (GPU).app'; @@ -33,6 +33,35 @@ function getEntitlementsForFile(filePath: string): string { return path.join(baseDir, 'azure-pipelines', 'darwin', 'app-entitlements.plist'); } +async function retrySignOnKeychainError(fn: () => Promise, maxRetries: number = 3): Promise { + let lastError: Error | undefined; + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await fn(); + } catch (error) { + lastError = error as Error; + + // Check if this is the specific keychain error we want to retry + const errorMessage = error instanceof Error ? error.message : String(error); + const isKeychainError = errorMessage.includes('The specified item could not be found in the keychain.'); + + if (!isKeychainError || attempt === maxRetries) { + throw error; + } + + console.log(`Signing attempt ${attempt} failed with keychain error, retrying...`); + console.log(`Error: ${errorMessage}`); + + const delay = 1000 * Math.pow(2, attempt - 1); + console.log(`Waiting ${Math.round(delay)}ms before retry ${attempt}/${maxRetries}...`); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + + throw lastError; +} + async function main(buildDir?: string): Promise { const tempDir = process.env['AGENT_TEMPDIRECTORY']; const arch = process.env['VSCODE_ARCH']; @@ -90,10 +119,10 @@ async function main(buildDir?: string): Promise { ]); } - await sign(appOpts); + await retrySignOnKeychainError(() => sign(appOpts)); } -if (require.main === module) { +if (import.meta.main) { main(process.argv[2]).catch(async err => { console.error(err); const tempDir = process.env['AGENT_TEMPDIRECTORY']; diff --git a/build/darwin/verify-macho.js b/build/darwin/verify-macho.js deleted file mode 100644 index e7a4eb28d705c..0000000000000 --- a/build/darwin/verify-macho.js +++ /dev/null @@ -1,126 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const assert_1 = __importDefault(require("assert")); -const path_1 = __importDefault(require("path")); -const promises_1 = require("fs/promises"); -const cross_spawn_promise_1 = require("@malept/cross-spawn-promise"); -const MACHO_PREFIX = 'Mach-O '; -const MACHO_64_MAGIC_LE = 0xfeedfacf; -const MACHO_UNIVERSAL_MAGIC_LE = 0xbebafeca; -const MACHO_ARM64_CPU_TYPE = new Set([ - 0x0c000001, - 0x0100000c, -]); -const MACHO_X86_64_CPU_TYPE = new Set([ - 0x07000001, - 0x01000007, -]); -async function read(file, buf, offset, length, position) { - let filehandle; - try { - filehandle = await (0, promises_1.open)(file); - await filehandle.read(buf, offset, length, position); - } - finally { - await filehandle?.close(); - } -} -async function checkMachOFiles(appPath, arch) { - const visited = new Set(); - const invalidFiles = []; - const header = Buffer.alloc(8); - const file_header_entry_size = 20; - const checkx86_64Arch = (arch === 'x64'); - const checkArm64Arch = (arch === 'arm64'); - const checkUniversalArch = (arch === 'universal'); - const traverse = async (p) => { - p = await (0, promises_1.realpath)(p); - if (visited.has(p)) { - return; - } - visited.add(p); - const info = await (0, promises_1.stat)(p); - if (info.isSymbolicLink()) { - return; - } - if (info.isFile()) { - let fileOutput = ''; - try { - fileOutput = await (0, cross_spawn_promise_1.spawn)('file', ['--brief', '--no-pad', p]); - } - catch (e) { - if (e instanceof cross_spawn_promise_1.ExitCodeError) { - /* silently accept error codes from "file" */ - } - else { - throw e; - } - } - if (fileOutput.startsWith(MACHO_PREFIX)) { - console.log(`Verifying architecture of ${p}`); - read(p, header, 0, 8, 0).then(_ => { - const header_magic = header.readUInt32LE(); - if (header_magic === MACHO_64_MAGIC_LE) { - const cpu_type = header.readUInt32LE(4); - if (checkUniversalArch) { - invalidFiles.push(p); - } - else if (checkArm64Arch && !MACHO_ARM64_CPU_TYPE.has(cpu_type)) { - invalidFiles.push(p); - } - else if (checkx86_64Arch && !MACHO_X86_64_CPU_TYPE.has(cpu_type)) { - invalidFiles.push(p); - } - } - else if (header_magic === MACHO_UNIVERSAL_MAGIC_LE) { - const num_binaries = header.readUInt32BE(4); - assert_1.default.equal(num_binaries, 2); - const file_entries_size = file_header_entry_size * num_binaries; - const file_entries = Buffer.alloc(file_entries_size); - read(p, file_entries, 0, file_entries_size, 8).then(_ => { - for (let i = 0; i < num_binaries; i++) { - const cpu_type = file_entries.readUInt32LE(file_header_entry_size * i); - if (!MACHO_ARM64_CPU_TYPE.has(cpu_type) && !MACHO_X86_64_CPU_TYPE.has(cpu_type)) { - invalidFiles.push(p); - } - } - }); - } - }); - } - } - if (info.isDirectory()) { - for (const child of await (0, promises_1.readdir)(p)) { - await traverse(path_1.default.resolve(p, child)); - } - } - }; - await traverse(appPath); - return invalidFiles; -} -const archToCheck = process.argv[2]; -(0, assert_1.default)(process.env['APP_PATH'], 'APP_PATH not set'); -(0, assert_1.default)(archToCheck === 'x64' || archToCheck === 'arm64' || archToCheck === 'universal', `Invalid architecture ${archToCheck} to check`); -checkMachOFiles(process.env['APP_PATH'], archToCheck).then(invalidFiles => { - if (invalidFiles.length > 0) { - console.error('\x1b[31mThe following files are built for the wrong architecture:\x1b[0m'); - for (const file of invalidFiles) { - console.error(`\x1b[31m${file}\x1b[0m`); - } - process.exit(1); - } - else { - console.log('\x1b[32mAll files are valid\x1b[0m'); - } -}).catch(err => { - console.error(err); - process.exit(1); -}); -//# sourceMappingURL=verify-macho.js.map \ No newline at end of file diff --git a/build/darwin/verify-macho.ts b/build/darwin/verify-macho.ts index 61ebc71aba240..7770b9c36cd1f 100644 --- a/build/darwin/verify-macho.ts +++ b/build/darwin/verify-macho.ts @@ -7,6 +7,7 @@ import assert from 'assert'; import path from 'path'; import { open, stat, readdir, realpath } from 'fs/promises'; import { spawn, ExitCodeError } from '@malept/cross-spawn-promise'; +import minimatch from 'minimatch'; const MACHO_PREFIX = 'Mach-O '; const MACHO_64_MAGIC_LE = 0xfeedfacf; @@ -20,6 +21,17 @@ const MACHO_X86_64_CPU_TYPE = new Set([ 0x01000007, ]); +// Files to skip during architecture validation +const FILES_TO_SKIP = [ + // MSAL runtime files are only present in ARM64 builds + '**/extensions/microsoft-authentication/dist/libmsalruntime.dylib', + '**/extensions/microsoft-authentication/dist/msal-node-runtime.node', +]; + +function isFileSkipped(file: string): boolean { + return FILES_TO_SKIP.some(pattern => minimatch(file, pattern)); +} + async function read(file: string, buf: Buffer, offset: number, length: number, position: number) { let filehandle; try { @@ -105,11 +117,11 @@ const archToCheck = process.argv[2]; assert(process.env['APP_PATH'], 'APP_PATH not set'); assert(archToCheck === 'x64' || archToCheck === 'arm64' || archToCheck === 'universal', `Invalid architecture ${archToCheck} to check`); checkMachOFiles(process.env['APP_PATH'], archToCheck).then(invalidFiles => { - if (invalidFiles.length > 0) { - console.error('\x1b[31mThe following files are built for the wrong architecture:\x1b[0m'); - for (const file of invalidFiles) { - console.error(`\x1b[31m${file}\x1b[0m`); - } + // Filter out files that should be skipped + const actualInvalidFiles = invalidFiles.filter(file => !isFileSkipped(file)); + if (actualInvalidFiles.length > 0) { + console.error('\x1b[31mThese files are built for the wrong architecture:\x1b[0m'); + actualInvalidFiles.forEach(file => console.error(`\x1b[31m${file}\x1b[0m`)); process.exit(1); } else { console.log('\x1b[32mAll files are valid\x1b[0m'); diff --git a/build/eslint.js b/build/eslint.js deleted file mode 100644 index 55e11b277d93c..0000000000000 --- a/build/eslint.js +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const es = require('event-stream'); -const vfs = require('vinyl-fs'); -const { eslintFilter } = require('./filters'); - -function eslint() { - const eslint = require('./gulp-eslint'); - return vfs - .src(eslintFilter, { base: '.', follow: true, allowEmpty: true }) - .pipe( - eslint((results) => { - if (results.warningCount > 0 || results.errorCount > 0) { - throw new Error('eslint failed with warnings and/or errors'); - } - }) - ).pipe(es.through(function () { /* noop, important for the stream to end */ })); -} - -if (require.main === module) { - eslint().on('error', (err) => { - console.error(); - console.error(err); - process.exit(1); - }); -} diff --git a/build/eslint.ts b/build/eslint.ts new file mode 100644 index 0000000000000..a2ef396a16c7c --- /dev/null +++ b/build/eslint.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import eventStream from 'event-stream'; +import vfs from 'vinyl-fs'; +import { eslintFilter } from './filters.ts'; +import gulpEslint from './gulp-eslint.ts'; + +function eslint(): NodeJS.ReadWriteStream { + return vfs + .src(Array.from(eslintFilter), { base: '.', follow: true, allowEmpty: true }) + .pipe( + gulpEslint((results) => { + if (results.warningCount > 0 || results.errorCount > 0) { + throw new Error(`eslint failed with ${results.warningCount + results.errorCount} warnings and/or errors`); + } + }) + ).pipe(eventStream.through(function () { /* noop, important for the stream to end */ })); +} + +if (import.meta.main) { + eslint().on('error', (err) => { + console.error(); + console.error(err); + process.exit(1); + }); +} diff --git a/build/filters.js b/build/filters.js deleted file mode 100644 index 035bd3ddb911e..0000000000000 --- a/build/filters.js +++ /dev/null @@ -1,220 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/** - * Hygiene works by creating cascading subsets of all our files and - * passing them through a sequence of checks. Here are the current subsets, - * named according to the checks performed on them. Each subset contains - * the following one, as described in mathematical notation: - * - * all ⊃ eol ⊇ indentation ⊃ copyright ⊃ typescript - */ - -const { readFileSync } = require('fs'); -const { join } = require('path'); - -module.exports.all = [ - '*', - 'build/**/*', - 'extensions/**/*', - 'scripts/**/*', - 'src/**/*', - 'test/**/*', - '!cli/**/*', - '!out*/**', - '!test/**/out/**', - '!**/node_modules/**', -]; - -module.exports.unicodeFilter = [ - '**', - - '!**/ThirdPartyNotices.txt', - '!**/ThirdPartyNotices.cli.txt', - '!**/LICENSE.{txt,rtf}', - '!LICENSES.chromium.html', - '!**/LICENSE', - - '!**/*.{dll,exe,png,bmp,jpg,scpt,cur,ttf,woff,eot,template,ico,icns,opus,wasm}', - '!**/test/**', - '!**/*.test.ts', - '!**/*.{d.ts,json,md}', - '!**/*.mp3', - - '!build/win32/**', - '!extensions/markdown-language-features/notebook-out/*.js', - '!extensions/markdown-math/notebook-out/**', - '!extensions/ipynb/notebook-out/**', - '!extensions/notebook-renderers/renderer-out/**', - '!extensions/php-language-features/src/features/phpGlobalFunctions.ts', - '!extensions/terminal-suggest/src/completions/upstream/**', - '!extensions/typescript-language-features/test-workspace/**', - '!extensions/vscode-api-tests/testWorkspace/**', - '!extensions/vscode-api-tests/testWorkspace2/**', - '!extensions/**/dist/**', - '!extensions/**/out/**', - '!extensions/**/snippets/**', - '!extensions/**/colorize-fixtures/**', - '!extensions/terminal-suggest/src/shell/fishBuiltinsCache.ts', - - '!src/vs/base/browser/dompurify/**', - '!src/vs/workbench/services/keybinding/browser/keyboardLayouts/**', -]; - -module.exports.indentationFilter = [ - '**', - - // except specific files - '!**/ThirdPartyNotices.txt', - '!**/ThirdPartyNotices.cli.txt', - '!**/LICENSE.{txt,rtf}', - '!LICENSES.chromium.html', - '!**/LICENSE', - '!**/*.mp3', - '!src/vs/loader.js', - '!src/vs/base/browser/dompurify/*', - '!src/vs/base/common/marked/marked.js', - '!src/vs/base/common/semver/semver.js', - '!src/vs/base/node/terminateProcess.sh', - '!src/vs/base/node/cpuUsage.sh', - '!src/vs/editor/common/languages/highlights/*.scm', - '!src/vs/editor/common/languages/injections/*.scm', - '!test/unit/assert.js', - '!resources/linux/snap/electron-launch', - '!build/ext.js', - '!build/npm/gyp/patches/gyp_spectre_mitigation_support.patch', - '!product.overrides.json', - - // except specific folders - '!test/automation/out/**', - '!test/monaco/out/**', - '!test/smoke/out/**', - '!extensions/terminal-suggest/src/shell/zshBuiltinsCache.ts', - '!extensions/terminal-suggest/src/shell/fishBuiltinsCache.ts', - '!extensions/terminal-suggest/src/completions/upstream/**', - '!extensions/typescript-language-features/test-workspace/**', - '!extensions/typescript-language-features/resources/walkthroughs/**', - '!extensions/typescript-language-features/package-manager/node-maintainer/**', - '!extensions/markdown-math/notebook-out/**', - '!extensions/ipynb/notebook-out/**', - '!extensions/vscode-api-tests/testWorkspace/**', - '!extensions/vscode-api-tests/testWorkspace2/**', - '!build/monaco/**', - '!build/win32/**', - '!build/checker/**', - - // except multiple specific files - '!**/package.json', - '!**/package-lock.json', - - // except multiple specific folders - '!**/codicon/**', - '!**/fixtures/**', - '!**/lib/**', - '!extensions/**/dist/**', - '!extensions/**/out/**', - '!extensions/**/snippets/**', - '!extensions/**/syntaxes/**', - '!extensions/**/themes/**', - '!extensions/**/colorize-fixtures/**', - - // except specific file types - '!src/vs/*/**/*.d.ts', - '!src/typings/**/*.d.ts', - '!extensions/**/*.d.ts', - '!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,psm1,template,yaml,yml,d.ts.recipe,ico,icns,plist,opus,admx,adml,wasm}', - '!build/{lib,download,linux,darwin}/**/*.js', - '!build/**/*.sh', - '!build/azure-pipelines/**/*.js', - '!build/azure-pipelines/**/*.config', - '!**/Dockerfile', - '!**/Dockerfile.*', - '!**/*.Dockerfile', - '!**/*.dockerfile', - - // except for built files - '!extensions/markdown-language-features/media/*.js', - '!extensions/markdown-language-features/notebook-out/*.js', - '!extensions/markdown-math/notebook-out/*.js', - '!extensions/ipynb/notebook-out/**', - '!extensions/notebook-renderers/renderer-out/*.js', - '!extensions/simple-browser/media/*.js', -]; - -module.exports.copyrightFilter = [ - '**', - '!**/*.desktop', - '!**/*.json', - '!**/*.html', - '!**/*.template', - '!**/*.md', - '!**/*.bat', - '!**/*.cmd', - '!**/*.ico', - '!**/*.opus', - '!**/*.mp3', - '!**/*.icns', - '!**/*.xml', - '!**/*.sh', - '!**/*.zsh', - '!**/*.fish', - '!**/*.txt', - '!**/*.xpm', - '!**/*.opts', - '!**/*.disabled', - '!**/*.code-workspace', - '!**/*.js.map', - '!**/*.wasm', - '!build/**/*.init', - '!build/linux/libcxx-fetcher.*', - '!resources/linux/snap/snapcraft.yaml', - '!resources/win32/bin/code.js', - '!resources/completions/**', - '!extensions/configuration-editing/build/inline-allOf.ts', - '!extensions/markdown-language-features/media/highlight.css', - '!extensions/markdown-math/notebook-out/**', - '!extensions/ipynb/notebook-out/**', - '!extensions/simple-browser/media/codicon.css', - '!extensions/terminal-suggest/src/completions/upstream/**', - '!extensions/typescript-language-features/node-maintainer/**', - '!extensions/html-language-features/server/src/modes/typescript/*', - '!extensions/*/server/bin/*', -]; - -module.exports.tsFormattingFilter = [ - 'src/**/*.ts', - 'test/**/*.ts', - 'extensions/**/*.ts', - '!src/vs/*/**/*.d.ts', - '!src/typings/**/*.d.ts', - '!extensions/**/*.d.ts', - '!**/fixtures/**', - '!**/typings/**', - '!**/node_modules/**', - '!extensions/**/colorize-fixtures/**', - '!extensions/vscode-api-tests/testWorkspace/**', - '!extensions/vscode-api-tests/testWorkspace2/**', - '!extensions/**/*.test.ts', - '!extensions/html-language-features/server/lib/jquery.d.ts', - '!extensions/terminal-suggest/src/shell/zshBuiltinsCache.ts', - '!extensions/terminal-suggest/src/shell/fishBuiltinsCache.ts', -]; - -module.exports.eslintFilter = [ - '**/*.js', - '**/*.cjs', - '**/*.mjs', - '**/*.ts', - '.eslint-plugin-local/**/*.ts', - ...readFileSync(join(__dirname, '..', '.eslint-ignore')) - .toString() - .split(/\r\n|\n/) - .filter(line => line && !line.startsWith('#')) - .map(line => line.startsWith('!') ? line.slice(1) : `!${line}`) -]; - -module.exports.stylelintFilter = [ - 'src/**/*.css' -]; diff --git a/build/filters.ts b/build/filters.ts new file mode 100644 index 0000000000000..04c72e27cbc57 --- /dev/null +++ b/build/filters.ts @@ -0,0 +1,229 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { readFileSync } from 'fs'; +import { join } from 'path'; + +/** + * Hygiene works by creating cascading subsets of all our files and + * passing them through a sequence of checks. Here are the current subsets, + * named according to the checks performed on them. Each subset contains + * the following one, as described in mathematical notation: + * + * all ⊃ eol ⊇ indentation ⊃ copyright ⊃ typescript + */ + +export const all = Object.freeze([ + '*', + 'build/**/*', + 'extensions/**/*', + 'scripts/**/*', + 'src/**/*', + 'test/**/*', + '!cli/**/*', + '!out*/**', + '!extensions/**/out*/**', + '!test/**/out/**', + '!**/node_modules/**', + '!**/*.js.map', +]); + +export const unicodeFilter = Object.freeze([ + '**', + + '!**/ThirdPartyNotices.txt', + '!**/ThirdPartyNotices.cli.txt', + '!**/LICENSE.{txt,rtf}', + '!LICENSES.chromium.html', + '!**/LICENSE', + + '!**/*.{dll,exe,png,bmp,jpg,scpt,cur,ttf,woff,eot,template,ico,icns,opus,wasm}', + '!**/test/**', + '!**/*.test.ts', + '!**/*.{d.ts,json,md}', + '!**/*.mp3', + + '!build/win32/**', + '!extensions/markdown-language-features/notebook-out/*.js', + '!extensions/markdown-math/notebook-out/**', + '!extensions/mermaid-chat-features/chat-webview-out/**', + '!extensions/ipynb/notebook-out/**', + '!extensions/notebook-renderers/renderer-out/**', + '!extensions/php-language-features/src/features/phpGlobalFunctions.ts', + '!extensions/terminal-suggest/src/completions/upstream/**', + '!extensions/typescript-language-features/test-workspace/**', + '!extensions/vscode-api-tests/testWorkspace/**', + '!extensions/vscode-api-tests/testWorkspace2/**', + '!extensions/**/dist/**', + '!extensions/**/out/**', + '!extensions/**/snippets/**', + '!extensions/**/colorize-fixtures/**', + '!extensions/terminal-suggest/src/shell/fishBuiltinsCache.ts', + + '!src/vs/base/browser/dompurify/**', + '!src/vs/workbench/services/keybinding/browser/keyboardLayouts/**', + '!src/vs/workbench/contrib/terminal/common/scripts/psreadline/**', +]); + +export const indentationFilter = Object.freeze([ + '**', + + // except specific files + '!**/ThirdPartyNotices.txt', + '!**/ThirdPartyNotices.cli.txt', + '!**/LICENSE.{txt,rtf}', + '!LICENSES.chromium.html', + '!**/LICENSE', + '!**/*.mp3', + '!src/vs/loader.js', + '!src/vs/base/browser/dompurify/*', + '!src/vs/base/common/marked/marked.js', + '!src/vs/base/common/semver/semver.js', + '!src/vs/base/node/terminateProcess.sh', + '!src/vs/base/node/cpuUsage.sh', + '!src/vs/editor/common/languages/highlights/*.scm', + '!src/vs/editor/common/languages/injections/*.scm', + '!test/unit/assert.js', + '!resources/linux/snap/electron-launch', + '!build/ext.js', + '!build/npm/gyp/patches/gyp_spectre_mitigation_support.patch', + '!product.overrides.json', + + // except specific folders + '!test/automation/out/**', + '!test/monaco/out/**', + '!test/smoke/out/**', + '!extensions/terminal-suggest/src/shell/zshBuiltinsCache.ts', + '!extensions/terminal-suggest/src/shell/fishBuiltinsCache.ts', + '!extensions/terminal-suggest/src/completions/upstream/**', + '!extensions/typescript-language-features/test-workspace/**', + '!extensions/typescript-language-features/resources/walkthroughs/**', + '!extensions/typescript-language-features/package-manager/node-maintainer/**', + '!extensions/markdown-math/notebook-out/**', + '!extensions/ipynb/notebook-out/**', + '!extensions/vscode-api-tests/testWorkspace/**', + '!extensions/vscode-api-tests/testWorkspace2/**', + '!build/monaco/**', + '!build/win32/**', + '!build/checker/**', + '!src/vs/workbench/contrib/terminal/common/scripts/psreadline/**', + + // except multiple specific files + '!**/package.json', + '!**/package-lock.json', + + // except multiple specific folders + '!**/codicon/**', + '!**/fixtures/**', + '!**/lib/**', + '!extensions/**/dist/**', + '!extensions/**/out/**', + '!extensions/**/snippets/**', + '!extensions/**/syntaxes/**', + '!extensions/**/themes/**', + '!extensions/**/colorize-fixtures/**', + + // except specific file types + '!src/vs/*/**/*.d.ts', + '!src/typings/**/*.d.ts', + '!extensions/**/*.d.ts', + '!**/*.{svg,exe,png,bmp,jpg,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,psm1,template,yaml,yml,d.ts.recipe,ico,icns,plist,opus,admx,adml,wasm}', + '!build/{lib,download,linux,darwin}/**/*.js', + '!build/**/*.sh', + '!build/azure-pipelines/**/*.js', + '!build/azure-pipelines/**/*.config', + '!build/npm/gyp/custom-headers/*.patch', + '!**/Dockerfile', + '!**/Dockerfile.*', + '!**/*.Dockerfile', + '!**/*.dockerfile', + + // except for built files + '!extensions/mermaid-chat-features/chat-webview-out/*.js', + '!extensions/markdown-language-features/media/*.js', + '!extensions/markdown-language-features/notebook-out/*.js', + '!extensions/markdown-math/notebook-out/*.js', + '!extensions/ipynb/notebook-out/**', + '!extensions/notebook-renderers/renderer-out/*.js', + '!extensions/simple-browser/media/*.js', +]); + +export const copyrightFilter = Object.freeze([ + '**', + '!**/*.desktop', + '!**/*.json', + '!**/*.html', + '!**/*.template', + '!**/*.md', + '!**/*.bat', + '!**/*.cmd', + '!**/*.ico', + '!**/*.opus', + '!**/*.mp3', + '!**/*.icns', + '!**/*.xml', + '!**/*.sh', + '!**/*.zsh', + '!**/*.fish', + '!**/*.txt', + '!**/*.xpm', + '!**/*.opts', + '!**/*.disabled', + '!**/*.code-workspace', + '!**/*.js.map', + '!**/*.wasm', + '!build/**/*.init', + '!build/linux/libcxx-fetcher.*', + '!build/npm/gyp/custom-headers/*.patch', + '!resources/linux/snap/snapcraft.yaml', + '!resources/win32/bin/code.js', + '!resources/completions/**', + '!extensions/configuration-editing/build/inline-allOf.ts', + '!extensions/markdown-language-features/media/highlight.css', + '!extensions/markdown-math/notebook-out/**', + '!extensions/ipynb/notebook-out/**', + '!extensions/simple-browser/media/codicon.css', + '!extensions/terminal-suggest/src/completions/upstream/**', + '!extensions/typescript-language-features/node-maintainer/**', + '!extensions/html-language-features/server/src/modes/typescript/*', + '!extensions/*/server/bin/*', + '!src/vs/workbench/contrib/terminal/common/scripts/psreadline/**', +]); + +export const tsFormattingFilter = Object.freeze([ + 'src/**/*.ts', + 'test/**/*.ts', + 'extensions/**/*.ts', + '!src/vs/*/**/*.d.ts', + '!src/typings/**/*.d.ts', + '!extensions/**/*.d.ts', + '!**/fixtures/**', + '!**/typings/**', + '!**/node_modules/**', + '!extensions/**/colorize-fixtures/**', + '!extensions/vscode-api-tests/testWorkspace/**', + '!extensions/vscode-api-tests/testWorkspace2/**', + '!extensions/**/*.test.ts', + '!extensions/html-language-features/server/lib/jquery.d.ts', + '!extensions/terminal-suggest/src/shell/zshBuiltinsCache.ts', + '!extensions/terminal-suggest/src/shell/fishBuiltinsCache.ts', +]); + +export const eslintFilter = Object.freeze([ + '**/*.js', + '**/*.cjs', + '**/*.mjs', + '**/*.ts', + '.eslint-plugin-local/**/*.ts', + ...readFileSync(join(import.meta.dirname, '..', '.eslint-ignore')) + .toString() + .split(/\r\n|\n/) + .filter(line => line && !line.startsWith('#')) + .map(line => line.startsWith('!') ? line.slice(1) : `!${line}`) +]); + +export const stylelintFilter = Object.freeze([ + 'src/**/*.css' +]); diff --git a/build/gulp-eslint.js b/build/gulp-eslint.js deleted file mode 100644 index 902c7b47003ed..0000000000000 --- a/build/gulp-eslint.js +++ /dev/null @@ -1,78 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -const { ESLint } = require('eslint'); -const { Transform } = require('stream'); -const { relative } = require('path'); -const fancyLog = require('fancy-log'); - -/** - * @param {Function} action - A function to handle all ESLint results - * @returns {stream} gulp file stream - */ -function eslint(action) { - const linter = new ESLint({}); - const formatter = linter.loadFormatter('compact'); - - const results = []; - results.errorCount = 0; - results.warningCount = 0; - - return transform( - async (file, enc, cb) => { - const filePath = relative(process.cwd(), file.path); - - if (file.isNull()) { - cb(null, file); - return; - } - - if (file.isStream()) { - cb(new Error('vinyl files with Stream contents are not supported')); - return; - } - - try { - // TODO: Should this be checked? - if (await linter.isPathIgnored(filePath)) { - cb(null, file); - return; - } - - const result = (await linter.lintText(file.contents.toString(), { filePath }))[0]; - results.push(result); - results.errorCount += result.errorCount; - results.warningCount += result.warningCount; - - const message = (await formatter).format([result]); - if (message) { - fancyLog(message); - } - cb(null, file); - } catch (error) { - cb(error); - } - }, - (done) => { - try { - action(results); - done(); - } catch (error) { - done(error); - } - }); -} - -function transform(transform, flush) { - return new Transform({ - objectMode: true, - transform, - flush - }); -} - -module.exports = eslint; diff --git a/build/gulp-eslint.ts b/build/gulp-eslint.ts new file mode 100644 index 0000000000000..1e953cdba7b7f --- /dev/null +++ b/build/gulp-eslint.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ESLint } from 'eslint'; +import fancyLog from 'fancy-log'; +import { relative } from 'path'; +import { Transform, type TransformOptions } from 'stream'; + +interface ESLintResults extends Array { + errorCount: number; + warningCount: number; +} + +interface EslintAction { + (results: ESLintResults): void; +} + +export default function eslint(action: EslintAction) { + const linter = new ESLint({}); + const formatter = linter.loadFormatter('compact'); + + const results: ESLintResults = Object.assign([], { errorCount: 0, warningCount: 0 }); + + return createTransform( + async (file, _enc, cb) => { + const filePath = relative(process.cwd(), file.path); + + if (file.isNull()) { + cb(null, file); + return; + } + + if (file.isStream()) { + cb(new Error('vinyl files with Stream contents are not supported')); + return; + } + + try { + // TODO: Should this be checked? + if (await linter.isPathIgnored(filePath)) { + cb(null, file); + return; + } + + const result = (await linter.lintText(file.contents.toString(), { filePath }))[0]; + results.push(result); + results.errorCount += result.errorCount; + results.warningCount += result.warningCount; + + const message = (await formatter).format([result]); + if (message) { + fancyLog(message); + } + cb(null, file); + } catch (error) { + cb(error); + } + }, + (done) => { + try { + action(results); + done(); + } catch (error) { + done(error); + } + }); +} + +function createTransform( + transform: TransformOptions['transform'], + flush: TransformOptions['flush'] +): Transform { + return new Transform({ + objectMode: true, + transform, + flush + }); +} diff --git a/build/gulpfile.cli.js b/build/gulpfile.cli.js deleted file mode 100644 index 592fc74516c5e..0000000000000 --- a/build/gulpfile.cli.js +++ /dev/null @@ -1,184 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -const es = require('event-stream'); -const gulp = require('gulp'); -const path = require('path'); -const fancyLog = require('fancy-log'); -const ansiColors = require('ansi-colors'); -const cp = require('child_process'); -const { tmpdir } = require('os'); -const { promises: fs, existsSync, mkdirSync, rmSync } = require('fs'); - -const task = require('./lib/task'); -const watcher = require('./lib/watch'); -const { debounce } = require('./lib/util'); -const createReporter = require('./lib/reporter').createReporter; - -const root = 'cli'; -const rootAbs = path.resolve(__dirname, '..', root); -const src = `${root}/src`; - -const platformOpensslDirName = - process.platform === 'win32' ? ( - process.arch === 'arm64' - ? 'arm64-windows-static-md' - : 'x64-windows-static-md') - : process.platform === 'darwin' ? ( - process.arch === 'arm64' - ? 'arm64-osx' - : 'x64-osx') - : (process.arch === 'arm64' - ? 'arm64-linux' - : process.arch === 'arm' - ? 'arm-linux' - : 'x64-linux'); -const platformOpensslDir = path.join(rootAbs, 'openssl', 'package', 'out', platformOpensslDirName); - -const hasLocalRust = (() => { - /** @type boolean | undefined */ - let result = undefined; - return () => { - if (result !== undefined) { - return result; - } - - try { - const r = cp.spawnSync('cargo', ['--version']); - result = r.status === 0; - } catch (e) { - result = false; - } - - return result; - }; -})(); - -const debounceEsStream = (fn, duration = 100) => { - let handle = undefined; - let pending = []; - const sendAll = (pending) => (event, ...args) => { - for (const stream of pending) { - pending.emit(event, ...args); - } - }; - - return es.map(function (_, callback) { - console.log('defer'); - if (handle !== undefined) { - clearTimeout(handle); - } - - handle = setTimeout(() => { - handle = undefined; - - const previous = pending; - pending = []; - fn() - .on('error', sendAll('error')) - .on('data', sendAll('data')) - .on('end', sendAll('end')); - }, duration); - - pending.push(this); - }); -}; - -const compileFromSources = (callback) => { - const proc = cp.spawn('cargo', ['--color', 'always', 'build'], { - cwd: root, - stdio: ['ignore', 'pipe', 'pipe'], - env: existsSync(platformOpensslDir) ? { OPENSSL_DIR: platformOpensslDir, ...process.env } : process.env - }); - - /** @type Buffer[] */ - const stdoutErr = []; - proc.stdout.on('data', d => stdoutErr.push(d)); - proc.stderr.on('data', d => stdoutErr.push(d)); - proc.on('error', callback); - proc.on('exit', code => { - if (code !== 0) { - callback(Buffer.concat(stdoutErr).toString()); - } else { - callback(); - } - }); -}; - -const acquireBuiltOpenSSL = (callback) => { - const untar = require('gulp-untar'); - const gunzip = require('gulp-gunzip'); - const dir = path.join(tmpdir(), 'vscode-openssl-download'); - mkdirSync(dir, { recursive: true }); - - cp.spawnSync( - process.platform === 'win32' ? 'npm.cmd' : 'npm', - ['pack', '@vscode/openssl-prebuilt'], - { stdio: ['ignore', 'ignore', 'inherit'], cwd: dir } - ); - - gulp.src('*.tgz', { cwd: dir }) - .pipe(gunzip()) - .pipe(untar()) - .pipe(gulp.dest(`${root}/openssl`)) - .on('error', callback) - .on('end', () => { - rmSync(dir, { recursive: true, force: true }); - callback(); - }); -}; - -const compileWithOpenSSLCheck = (/** @type import('./lib/reporter').IReporter */ reporter) => es.map((_, callback) => { - compileFromSources(err => { - if (!err) { - // no-op - } else if (err.toString().includes('Could not find directory of OpenSSL installation') && !existsSync(platformOpensslDir)) { - fancyLog(ansiColors.yellow(`[cli]`), 'OpenSSL libraries not found, acquiring prebuilt bits...'); - acquireBuiltOpenSSL(err => { - if (err) { - callback(err); - } else { - compileFromSources(err => { - if (err) { - reporter(err.toString()); - } - callback(null, ''); - }); - } - }); - } else { - reporter(err.toString()); - } - - callback(null, ''); - }); -}); - -const warnIfRustNotInstalled = () => { - if (!hasLocalRust()) { - fancyLog(ansiColors.yellow(`[cli]`), 'No local Rust install detected, compilation may fail.'); - fancyLog(ansiColors.yellow(`[cli]`), 'Get rust from: https://rustup.rs/'); - } -}; - -const compileCliTask = task.define('compile-cli', () => { - warnIfRustNotInstalled(); - const reporter = createReporter('cli'); - return gulp.src(`${root}/Cargo.toml`) - .pipe(compileWithOpenSSLCheck(reporter)) - .pipe(reporter.end(true)); -}); - - -const watchCliTask = task.define('watch-cli', () => { - warnIfRustNotInstalled(); - return watcher(`${src}/**`, { read: false }) - .pipe(debounce(compileCliTask)); -}); - -gulp.task(compileCliTask); -gulp.task(watchCliTask); diff --git a/build/gulpfile.cli.ts b/build/gulpfile.cli.ts new file mode 100644 index 0000000000000..e746a00e2bb2d --- /dev/null +++ b/build/gulpfile.cli.ts @@ -0,0 +1,148 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import es from 'event-stream'; +import gulp from 'gulp'; +import * as path from 'path'; +import fancyLog from 'fancy-log'; +import ansiColors from 'ansi-colors'; +import * as cp from 'child_process'; +import { tmpdir } from 'os'; +import { existsSync, mkdirSync, rmSync } from 'fs'; +import * as task from './lib/task.ts'; +import watcher from './lib/watch/index.ts'; +import { debounce } from './lib/util.ts'; +import { createReporter } from './lib/reporter.ts'; +import untar from 'gulp-untar'; +import gunzip from 'gulp-gunzip'; + +const root = 'cli'; +const rootAbs = path.resolve(import.meta.dirname, '..', root); +const src = `${root}/src`; + +const platformOpensslDirName = + process.platform === 'win32' ? ( + process.arch === 'arm64' + ? 'arm64-windows-static-md' + : 'x64-windows-static-md') + : process.platform === 'darwin' ? ( + process.arch === 'arm64' + ? 'arm64-osx' + : 'x64-osx') + : (process.arch === 'arm64' + ? 'arm64-linux' + : process.arch === 'arm' + ? 'arm-linux' + : 'x64-linux'); +const platformOpensslDir = path.join(rootAbs, 'openssl', 'package', 'out', platformOpensslDirName); + +const hasLocalRust = (() => { + let result: boolean | undefined = undefined; + return () => { + if (result !== undefined) { + return result; + } + + try { + const r = cp.spawnSync('cargo', ['--version']); + result = r.status === 0; + } catch (e) { + result = false; + } + + return result; + }; +})(); + +const compileFromSources = (callback: (err?: string) => void) => { + const proc = cp.spawn('cargo', ['--color', 'always', 'build'], { + cwd: root, + stdio: ['ignore', 'pipe', 'pipe'], + env: existsSync(platformOpensslDir) ? { OPENSSL_DIR: platformOpensslDir, ...process.env } : process.env + }); + + const stdoutErr: Buffer[] = []; + proc.stdout.on('data', d => stdoutErr.push(d)); + proc.stderr.on('data', d => stdoutErr.push(d)); + proc.on('error', callback); + proc.on('exit', code => { + if (code !== 0) { + callback(Buffer.concat(stdoutErr).toString()); + } else { + callback(); + } + }); +}; + +const acquireBuiltOpenSSL = (callback: (err?: unknown) => void) => { + const dir = path.join(tmpdir(), 'vscode-openssl-download'); + mkdirSync(dir, { recursive: true }); + + cp.spawnSync( + process.platform === 'win32' ? 'npm.cmd' : 'npm', + ['pack', '@vscode/openssl-prebuilt'], + { stdio: ['ignore', 'ignore', 'inherit'], cwd: dir } + ); + + gulp.src('*.tgz', { cwd: dir }) + .pipe(gunzip()) + .pipe(untar()) + .pipe(gulp.dest(`${root}/openssl`)) + .on('error', callback) + .on('end', () => { + rmSync(dir, { recursive: true, force: true }); + callback(); + }); +}; + +const compileWithOpenSSLCheck = (reporter: import('./lib/reporter.ts').IReporter) => es.map((_, callback) => { + compileFromSources(err => { + if (!err) { + // no-op + } else if (err.toString().includes('Could not find directory of OpenSSL installation') && !existsSync(platformOpensslDir)) { + fancyLog(ansiColors.yellow(`[cli]`), 'OpenSSL libraries not found, acquiring prebuilt bits...'); + acquireBuiltOpenSSL(err => { + if (err) { + callback(err as Error); + } else { + compileFromSources(err => { + if (err) { + reporter(err.toString()); + } + callback(undefined, ''); + }); + } + }); + } else { + reporter(err.toString()); + } + callback(undefined, ''); + }); +}); + +const warnIfRustNotInstalled = () => { + if (!hasLocalRust()) { + fancyLog(ansiColors.yellow(`[cli]`), 'No local Rust install detected, compilation may fail.'); + fancyLog(ansiColors.yellow(`[cli]`), 'Get rust from: https://rustup.rs/'); + } +}; + +const compileCliTask = task.define('compile-cli', () => { + warnIfRustNotInstalled(); + const reporter = createReporter('cli'); + return gulp.src(`${root}/Cargo.toml`) + .pipe(compileWithOpenSSLCheck(reporter)) + .pipe(reporter.end(true)); +}); + + +const watchCliTask = task.define('watch-cli', () => { + warnIfRustNotInstalled(); + return watcher(`${src}/**`, { read: false }) + .pipe(debounce(compileCliTask as task.StreamTask)); +}); + +gulp.task(compileCliTask); +gulp.task(watchCliTask); diff --git a/build/gulpfile.compile.js b/build/gulpfile.compile.js deleted file mode 100644 index 0c0a024c8fc09..0000000000000 --- a/build/gulpfile.compile.js +++ /dev/null @@ -1,35 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check -'use strict'; - -const gulp = require('gulp'); -const util = require('./lib/util'); -const date = require('./lib/date'); -const task = require('./lib/task'); -const compilation = require('./lib/compilation'); - -/** - * @param {boolean} disableMangle - */ -function makeCompileBuildTask(disableMangle) { - return task.series( - util.rimraf('out-build'), - date.writeISODate('out-build'), - compilation.compileApiProposalNamesTask, - compilation.compileTask('src', 'out-build', true, { disableMangle }) - ); -} - -// Local/PR compile, including nls and inline sources in sourcemaps, minification, no mangling -const compileBuildWithoutManglingTask = task.define('compile-build-without-mangling', makeCompileBuildTask(true)); -gulp.task(compileBuildWithoutManglingTask); -exports.compileBuildWithoutManglingTask = compileBuildWithoutManglingTask; - -// CI compile, including nls and inline sources in sourcemaps, mangling, minification, for build -const compileBuildWithManglingTask = task.define('compile-build-with-mangling', makeCompileBuildTask(false)); -gulp.task(compileBuildWithManglingTask); -exports.compileBuildWithManglingTask = compileBuildWithManglingTask; diff --git a/build/gulpfile.compile.ts b/build/gulpfile.compile.ts new file mode 100644 index 0000000000000..fcfdf2dca57e0 --- /dev/null +++ b/build/gulpfile.compile.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import gulp from 'gulp'; +import * as util from './lib/util.ts'; +import * as date from './lib/date.ts'; +import * as task from './lib/task.ts'; +import * as compilation from './lib/compilation.ts'; + +function makeCompileBuildTask(disableMangle: boolean) { + return task.series( + util.rimraf('out-build'), + date.writeISODate('out-build'), + compilation.compileApiProposalNamesTask, + compilation.compileTask('src', 'out-build', true, { disableMangle }) + ); +} + +// Local/PR compile, including nls and inline sources in sourcemaps, minification, no mangling +export const compileBuildWithoutManglingTask = task.define('compile-build-without-mangling', makeCompileBuildTask(true)); +gulp.task(compileBuildWithoutManglingTask); + +// CI compile, including nls and inline sources in sourcemaps, mangling, minification, for build +export const compileBuildWithManglingTask = task.define('compile-build-with-mangling', makeCompileBuildTask(false)); +gulp.task(compileBuildWithManglingTask); diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js deleted file mode 100644 index ece30e13152bc..0000000000000 --- a/build/gulpfile.editor.js +++ /dev/null @@ -1,264 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -const gulp = require('gulp'); -const path = require('path'); -const util = require('./lib/util'); -const { getVersion } = require('./lib/getVersion'); -const task = require('./lib/task'); -const es = require('event-stream'); -const File = require('vinyl'); -const i18n = require('./lib/i18n'); -const standalone = require('./lib/standalone'); -const cp = require('child_process'); -const compilation = require('./lib/compilation'); -const monacoapi = require('./lib/monaco-api'); -const fs = require('fs'); -const filter = require('gulp-filter'); - -const root = path.dirname(__dirname); -const sha1 = getVersion(root); -const semver = require('./monaco/package.json').version; -const headerVersion = semver + '(' + sha1 + ')'; - -const BUNDLED_FILE_HEADER = [ - '/*!-----------------------------------------------------------', - ' * Copyright (c) Microsoft Corporation. All rights reserved.', - ' * Version: ' + headerVersion, - ' * Released under the MIT license', - ' * https://github.com/microsoft/vscode/blob/main/LICENSE.txt', - ' *-----------------------------------------------------------*/', - '' -].join('\n'); - -const extractEditorSrcTask = task.define('extract-editor-src', () => { - const apiusages = monacoapi.execute().usageContent; - const extrausages = fs.readFileSync(path.join(root, 'build', 'monaco', 'monaco.usage.recipe')).toString(); - standalone.extractEditor({ - sourcesRoot: path.join(root, 'src'), - entryPoints: [ - 'vs/editor/editor.main', - 'vs/editor/editor.worker.start', - 'vs/editor/common/services/editorWebWorkerMain', - ], - inlineEntryPoints: [ - apiusages, - extrausages - ], - typings: [], - shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers - importIgnorePattern: /\.css$/, - destRoot: path.join(root, 'out-editor-src'), - tsOutDir: '../out-monaco-editor-core/esm/vs', - redirects: { - '@vscode/tree-sitter-wasm': '../node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter', - } - }); -}); - -const compileEditorESMTask = task.define('compile-editor-esm', () => { - - const src = 'out-editor-src'; - const out = 'out-monaco-editor-core/esm'; - - const compile = compilation.createCompile(src, { build: true, emitError: true, transpileOnly: false, preserveEnglish: true }); - const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); - - return ( - srcPipe - .pipe(compile()) - .pipe(i18n.processNlsFiles({ - out, - fileHeader: BUNDLED_FILE_HEADER, - languages: i18n.defaultLanguages, - })) - .pipe(filter(['**', '!**/inlineEntryPoint*', '!**/tsconfig.json', '!**/loader.js'])) - .pipe(gulp.dest(out)) - ); -}); - -/** - * @param {string} contents - */ -function toExternalDTS(contents) { - const lines = contents.split(/\r\n|\r|\n/); - let killNextCloseCurlyBrace = false; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - - if (killNextCloseCurlyBrace) { - if ('}' === line) { - lines[i] = ''; - killNextCloseCurlyBrace = false; - continue; - } - - if (line.indexOf(' ') === 0) { - lines[i] = line.substr(4); - } else if (line.charAt(0) === '\t') { - lines[i] = line.substr(1); - } - - continue; - } - - if ('declare namespace monaco {' === line) { - lines[i] = ''; - killNextCloseCurlyBrace = true; - continue; - } - - if (line.indexOf('declare namespace monaco.') === 0) { - lines[i] = line.replace('declare namespace monaco.', 'export namespace '); - } - - if (line.indexOf('declare var MonacoEnvironment') === 0) { - lines[i] = `declare global {\n var MonacoEnvironment: Environment | undefined;\n}`; - } - } - return lines.join('\n').replace(/\n\n\n+/g, '\n\n'); -} - -const finalEditorResourcesTask = task.define('final-editor-resources', () => { - return es.merge( - // other assets - es.merge( - gulp.src('build/monaco/LICENSE'), - gulp.src('build/monaco/ThirdPartyNotices.txt'), - gulp.src('src/vs/monaco.d.ts') - ).pipe(gulp.dest('out-monaco-editor-core')), - - // place the .d.ts in the esm folder - gulp.src('src/vs/monaco.d.ts') - .pipe(es.through(function (data) { - this.emit('data', new File({ - path: data.path.replace(/monaco\.d\.ts/, 'editor.api.d.ts'), - base: data.base, - contents: Buffer.from(toExternalDTS(data.contents.toString())) - })); - })) - .pipe(gulp.dest('out-monaco-editor-core/esm/vs/editor')), - - // package.json - gulp.src('build/monaco/package.json') - .pipe(es.through(function (data) { - const json = JSON.parse(data.contents.toString()); - json.private = false; - data.contents = Buffer.from(JSON.stringify(json, null, ' ')); - this.emit('data', data); - })) - .pipe(gulp.dest('out-monaco-editor-core')), - - // version.txt - gulp.src('build/monaco/version.txt') - .pipe(es.through(function (data) { - data.contents = Buffer.from(`monaco-editor-core: https://github.com/microsoft/vscode/tree/${sha1}`); - this.emit('data', data); - })) - .pipe(gulp.dest('out-monaco-editor-core')), - - // README.md - gulp.src('build/monaco/README-npm.md') - .pipe(es.through(function (data) { - this.emit('data', new File({ - path: data.path.replace(/README-npm\.md/, 'README.md'), - base: data.base, - contents: data.contents - })); - })) - .pipe(gulp.dest('out-monaco-editor-core')), - ); -}); - -gulp.task('extract-editor-src', - task.series( - util.rimraf('out-editor-src'), - extractEditorSrcTask - ) -); - -gulp.task('editor-distro', - task.series( - task.parallel( - util.rimraf('out-editor-src'), - util.rimraf('out-monaco-editor-core'), - ), - extractEditorSrcTask, - compileEditorESMTask, - finalEditorResourcesTask - ) -); - -gulp.task('monacodts', task.define('monacodts', () => { - const result = monacoapi.execute(); - fs.writeFileSync(result.filePath, result.content); - fs.writeFileSync(path.join(root, 'src/vs/editor/common/standalone/standaloneEnums.ts'), result.enums); - return Promise.resolve(true); -})); - -//#region monaco type checking - -/** - * @param {boolean} watch - */ -function createTscCompileTask(watch) { - return () => { - const createReporter = require('./lib/reporter').createReporter; - - return new Promise((resolve, reject) => { - const args = ['./node_modules/.bin/tsc', '-p', './src/tsconfig.monaco.json', '--noEmit']; - if (watch) { - args.push('-w'); - } - const child = cp.spawn(`node`, args, { - cwd: path.join(__dirname, '..'), - // stdio: [null, 'pipe', 'inherit'] - }); - const errors = []; - const reporter = createReporter('monaco'); - - /** @type {NodeJS.ReadWriteStream | undefined} */ - let report; - const magic = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; // https://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings - - child.stdout.on('data', data => { - let str = String(data); - str = str.replace(magic, '').trim(); - if (str.indexOf('Starting compilation') >= 0 || str.indexOf('File change detected') >= 0) { - errors.length = 0; - report = reporter.end(false); - - } else if (str.indexOf('Compilation complete') >= 0) { - // @ts-ignore - report.end(); - - } else if (str) { - const match = /(.*\(\d+,\d+\): )(.*: )(.*)/.exec(str); - if (match) { - // trying to massage the message so that it matches the gulp-tsb error messages - // e.g. src/vs/base/common/strings.ts(663,5): error TS2322: Type '1234' is not assignable to type 'string'. - const fullpath = path.join(root, match[1]); - const message = match[3]; - reporter(fullpath + message); - } else { - reporter(str); - } - } - }); - child.on('exit', resolve); - child.on('error', reject); - }); - }; -} - -const monacoTypecheckWatchTask = task.define('monaco-typecheck-watch', createTscCompileTask(true)); -exports.monacoTypecheckWatchTask = monacoTypecheckWatchTask; - -const monacoTypecheckTask = task.define('monaco-typecheck', createTscCompileTask(false)); -exports.monacoTypecheckTask = monacoTypecheckTask; - -//#endregion diff --git a/build/gulpfile.editor.ts b/build/gulpfile.editor.ts new file mode 100644 index 0000000000000..447b76fa16c25 --- /dev/null +++ b/build/gulpfile.editor.ts @@ -0,0 +1,291 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import gulp from 'gulp'; +import path from 'path'; +import * as util from './lib/util.ts'; +import { getVersion } from './lib/getVersion.ts'; +import * as task from './lib/task.ts'; +import es from 'event-stream'; +import File from 'vinyl'; +import * as i18n from './lib/i18n.ts'; +import * as standalone from './lib/standalone.ts'; +import * as cp from 'child_process'; +import * as compilation from './lib/compilation.ts'; +import * as monacoapi from './lib/monaco-api.ts'; +import * as fs from 'fs'; +import filter from 'gulp-filter'; +import { createReporter } from './lib/reporter.ts'; +import monacoPackage from './monaco/package.json' with { type: 'json' }; + +const root = path.dirname(import.meta.dirname); +const sha1 = getVersion(root); +const semver = monacoPackage.version; +const headerVersion = semver + '(' + sha1 + ')'; + +const BUNDLED_FILE_HEADER = [ + '/*!-----------------------------------------------------------', + ' * Copyright (c) Microsoft Corporation. All rights reserved.', + ' * Version: ' + headerVersion, + ' * Released under the MIT license', + ' * https://github.com/microsoft/vscode/blob/main/LICENSE.txt', + ' *-----------------------------------------------------------*/', + '' +].join('\n'); + +const extractEditorSrcTask = task.define('extract-editor-src', () => { + const apiusages = monacoapi.execute().usageContent; + const extrausages = fs.readFileSync(path.join(root, 'build', 'monaco', 'monaco.usage.recipe')).toString(); + standalone.extractEditor({ + sourcesRoot: path.join(root, 'src'), + entryPoints: [ + 'vs/editor/editor.main.ts', + 'vs/editor/editor.worker.start.ts', + 'vs/editor/common/services/editorWebWorkerMain.ts', + ], + inlineEntryPoints: [ + apiusages, + extrausages + ], + typings: [], + additionalFilesToCopyOut: [ + 'vs/base/browser/dompurify/dompurify.js', + 'vs/base/common/marked/marked.js', + ], + shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers + importIgnorePattern: /\.css$/, + destRoot: path.join(root, 'out-editor-src'), + tsOutDir: '../out-monaco-editor-core/esm/vs', + }); +}); + +const compileEditorESMTask = task.define('compile-editor-esm', () => { + + const src = 'out-editor-src'; + const out = 'out-monaco-editor-core/esm'; + + const compile = compilation.createCompile(src, { build: true, emitError: true, transpileOnly: false, preserveEnglish: true }); + const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); + + return ( + srcPipe + .pipe(compile()) + .pipe(i18n.processNlsFiles({ + out, + fileHeader: BUNDLED_FILE_HEADER, + languages: [...i18n.defaultLanguages, ...i18n.extraLanguages], + })) + .pipe(filter(['**', '!**/inlineEntryPoint*', '!**/tsconfig.json', '!**/loader.js'])) + .pipe(gulp.dest(out)) + ); +}); + +function toExternalDTS(contents: string) { + const lines = contents.split(/\r\n|\r|\n/); + let killNextCloseCurlyBrace = false; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + + if (killNextCloseCurlyBrace) { + if ('}' === line) { + lines[i] = ''; + killNextCloseCurlyBrace = false; + continue; + } + + if (line.indexOf(' ') === 0) { + lines[i] = line.substr(4); + } else if (line.charAt(0) === '\t') { + lines[i] = line.substr(1); + } + + continue; + } + + if ('declare namespace monaco {' === line) { + lines[i] = ''; + killNextCloseCurlyBrace = true; + continue; + } + + if (line.indexOf('declare namespace monaco.') === 0) { + lines[i] = line.replace('declare namespace monaco.', 'export namespace '); + } + + if (line.indexOf('declare var MonacoEnvironment') === 0) { + lines[i] = `declare global {\n var MonacoEnvironment: Environment | undefined;\n}`; + } + } + return lines.join('\n').replace(/\n\n\n+/g, '\n\n'); +} + +const finalEditorResourcesTask = task.define('final-editor-resources', () => { + return es.merge( + // other assets + es.merge( + gulp.src('build/monaco/LICENSE'), + gulp.src('build/monaco/ThirdPartyNotices.txt'), + gulp.src('src/vs/monaco.d.ts') + ).pipe(gulp.dest('out-monaco-editor-core')), + + // place the .d.ts in the esm folder + gulp.src('src/vs/monaco.d.ts') + .pipe(es.through(function (data) { + this.emit('data', new File({ + path: data.path.replace(/monaco\.d\.ts/, 'editor.api.d.ts'), + base: data.base, + contents: Buffer.from(toExternalDTS(data.contents.toString())) + })); + })) + .pipe(gulp.dest('out-monaco-editor-core/esm/vs/editor')), + + // package.json + gulp.src('build/monaco/package.json') + .pipe(es.through(function (data) { + const json = JSON.parse(data.contents.toString()); + json.private = false; + + let markedVersion; + let dompurifyVersion; + try { + const markedManifestPath = path.join(root, 'src/vs/base/common/marked/cgmanifest.json'); + const dompurifyManifestPath = path.join(root, 'src/vs/base/browser/dompurify/cgmanifest.json'); + + const markedManifest = JSON.parse(fs.readFileSync(markedManifestPath, 'utf8')); + const dompurifyManifest = JSON.parse(fs.readFileSync(dompurifyManifestPath, 'utf8')); + + markedVersion = markedManifest.registrations[0].version; + dompurifyVersion = dompurifyManifest.registrations[0].version; + + if (!markedVersion || !dompurifyVersion) { + throw new Error('Unable to read versions from cgmanifest.json files'); + } + } catch (error) { + throw new Error(`Failed to read cgmanifest.json files for monaco-editor-core dependencies: ${error.message}`); + } + + setUnsetField(json, 'dependencies', { + 'marked': markedVersion, + 'dompurify': dompurifyVersion + }); + + data.contents = Buffer.from(JSON.stringify(json, null, ' ')); + this.emit('data', data); + })) + .pipe(gulp.dest('out-monaco-editor-core')), + + // version.txt + gulp.src('build/monaco/version.txt') + .pipe(es.through(function (data) { + data.contents = Buffer.from(`monaco-editor-core: https://github.com/microsoft/vscode/tree/${sha1}`); + this.emit('data', data); + })) + .pipe(gulp.dest('out-monaco-editor-core')), + + // README.md + gulp.src('build/monaco/README-npm.md') + .pipe(es.through(function (data) { + this.emit('data', new File({ + path: data.path.replace(/README-npm\.md/, 'README.md'), + base: data.base, + contents: data.contents + })); + })) + .pipe(gulp.dest('out-monaco-editor-core')), + ); +}); + +gulp.task('extract-editor-src', + task.series( + util.rimraf('out-editor-src'), + extractEditorSrcTask + ) +); + +gulp.task('editor-distro', + task.series( + task.parallel( + util.rimraf('out-editor-src'), + util.rimraf('out-monaco-editor-core'), + ), + extractEditorSrcTask, + compileEditorESMTask, + finalEditorResourcesTask + ) +); + +gulp.task('monacodts', task.define('monacodts', () => { + const result = monacoapi.execute(); + fs.writeFileSync(result.filePath, result.content); + fs.writeFileSync(path.join(root, 'src/vs/editor/common/standalone/standaloneEnums.ts'), result.enums); + return Promise.resolve(true); +})); + +//#region monaco type checking + +function createTscCompileTask(watch: boolean) { + return () => { + return new Promise((resolve, reject) => { + const args = ['./node_modules/.bin/tsc', '-p', './src/tsconfig.monaco.json', '--noEmit']; + if (watch) { + args.push('-w'); + } + const child = cp.spawn(`node`, args, { + cwd: path.join(import.meta.dirname, '..'), + // stdio: [null, 'pipe', 'inherit'] + }); + const errors: string[] = []; + const reporter = createReporter('monaco'); + + let report: NodeJS.ReadWriteStream | undefined; + const magic = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; // https://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings + + child.stdout.on('data', data => { + let str = String(data); + str = str.replace(magic, '').trim(); + if (str.indexOf('Starting compilation') >= 0 || str.indexOf('File change detected') >= 0) { + errors.length = 0; + report = reporter.end(false); + + } else if (str.indexOf('Compilation complete') >= 0) { + // @ts-ignore + report.end(); + + } else if (str) { + const match = /(.*\(\d+,\d+\): )(.*: )(.*)/.exec(str); + if (match) { + // trying to massage the message so that it matches the gulp-tsb error messages + // e.g. src/vs/base/common/strings.ts(663,5): error TS2322: Type '1234' is not assignable to type 'string'. + const fullpath = path.join(root, match[1]); + const message = match[3]; + reporter(fullpath + message); + } else { + reporter(str); + } + } + }); + child.on('exit', resolve); + child.on('error', reject); + }); + }; +} + +export const monacoTypecheckWatchTask = task.define('monaco-typecheck-watch', createTscCompileTask(true)); + +export const monacoTypecheckTask = task.define('monaco-typecheck', createTscCompileTask(false)); + +//#endregion +/** + * Sets a field on an object only if it's not already set, otherwise throws an error + * @param obj The object to modify + * @param field The field name to set + * @param value The value to set + */ +function setUnsetField(obj: Record, field: string, value: unknown) { + if (obj[field] !== undefined) { + throw new Error(`Field "${field}" is already set (but was expected to not be).`); + } + obj[field] = value; +} diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js deleted file mode 100644 index 73c227e29ae72..0000000000000 --- a/build/gulpfile.extensions.js +++ /dev/null @@ -1,310 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// Increase max listeners for event emitters -require('events').EventEmitter.defaultMaxListeners = 100; - -const gulp = require('gulp'); -const path = require('path'); -const nodeUtil = require('util'); -const es = require('event-stream'); -const filter = require('gulp-filter'); -const util = require('./lib/util'); -const { getVersion } = require('./lib/getVersion'); -const task = require('./lib/task'); -const watcher = require('./lib/watch'); -const createReporter = require('./lib/reporter').createReporter; -const glob = require('glob'); -const root = path.dirname(__dirname); -const commit = getVersion(root); -const plumber = require('gulp-plumber'); -const ext = require('./lib/extensions'); - -// To save 250ms for each gulp startup, we are caching the result here -// const compilations = glob.sync('**/tsconfig.json', { -// cwd: extensionsPath, -// ignore: ['**/out/**', '**/node_modules/**'] -// }); -const compilations = [ - 'extensions/configuration-editing/tsconfig.json', - 'extensions/css-language-features/client/tsconfig.json', - 'extensions/css-language-features/server/tsconfig.json', - 'extensions/debug-auto-launch/tsconfig.json', - 'extensions/debug-server-ready/tsconfig.json', - 'extensions/emmet/tsconfig.json', - 'extensions/extension-editing/tsconfig.json', - 'extensions/git/tsconfig.json', - 'extensions/git-base/tsconfig.json', - 'extensions/github/tsconfig.json', - 'extensions/github-authentication/tsconfig.json', - 'extensions/grunt/tsconfig.json', - 'extensions/gulp/tsconfig.json', - 'extensions/html-language-features/client/tsconfig.json', - 'extensions/html-language-features/server/tsconfig.json', - 'extensions/ipynb/tsconfig.json', - 'extensions/jake/tsconfig.json', - 'extensions/json-language-features/client/tsconfig.json', - 'extensions/json-language-features/server/tsconfig.json', - 'extensions/markdown-language-features/preview-src/tsconfig.json', - 'extensions/markdown-language-features/tsconfig.json', - 'extensions/markdown-math/tsconfig.json', - 'extensions/media-preview/tsconfig.json', - 'extensions/merge-conflict/tsconfig.json', - 'extensions/terminal-suggest/tsconfig.json', - 'extensions/microsoft-authentication/tsconfig.json', - 'extensions/notebook-renderers/tsconfig.json', - 'extensions/npm/tsconfig.json', - 'extensions/php-language-features/tsconfig.json', - 'extensions/references-view/tsconfig.json', - 'extensions/search-result/tsconfig.json', - 'extensions/simple-browser/tsconfig.json', - 'extensions/tunnel-forwarding/tsconfig.json', - 'extensions/typescript-language-features/test-workspace/tsconfig.json', - 'extensions/typescript-language-features/web/tsconfig.json', - 'extensions/typescript-language-features/tsconfig.json', - 'extensions/vscode-api-tests/tsconfig.json', - 'extensions/vscode-colorize-tests/tsconfig.json', - 'extensions/vscode-colorize-perf-tests/tsconfig.json', - 'extensions/vscode-test-resolver/tsconfig.json', - - '.vscode/extensions/vscode-selfhost-test-provider/tsconfig.json', - '.vscode/extensions/vscode-selfhost-import-aid/tsconfig.json', -]; - -const getBaseUrl = out => `https://main.vscode-cdn.net/sourcemaps/${commit}/${out}`; - -const tasks = compilations.map(function (tsconfigFile) { - const absolutePath = path.join(root, tsconfigFile); - const relativeDirname = path.dirname(tsconfigFile.replace(/^(.*\/)?extensions\//i, '')); - - const overrideOptions = {}; - overrideOptions.sourceMap = true; - - const name = relativeDirname.replace(/\//g, '-'); - - const srcRoot = path.dirname(tsconfigFile); - const srcBase = path.join(srcRoot, 'src'); - const src = path.join(srcBase, '**'); - const srcOpts = { cwd: root, base: srcBase, dot: true }; - - const out = path.join(srcRoot, 'out'); - const baseUrl = getBaseUrl(out); - - let headerId, headerOut; - const index = relativeDirname.indexOf('/'); - if (index < 0) { - headerId = 'vscode.' + relativeDirname; - headerOut = 'out'; - } else { - headerId = 'vscode.' + relativeDirname.substr(0, index); - headerOut = relativeDirname.substr(index + 1) + '/out'; - } - - function createPipeline(build, emitError, transpileOnly) { - const tsb = require('./lib/tsb'); - const sourcemaps = require('gulp-sourcemaps'); - - const reporter = createReporter('extensions'); - - overrideOptions.inlineSources = Boolean(build); - overrideOptions.base = path.dirname(absolutePath); - - const compilation = tsb.create(absolutePath, overrideOptions, { verbose: false, transpileOnly, transpileOnlyIncludesDts: transpileOnly, transpileWithEsbuild: true }, err => reporter(err.toString())); - - const pipeline = function () { - const input = es.through(); - const tsFilter = filter(['**/*.ts', '!**/lib/lib*.d.ts', '!**/node_modules/**'], { restore: true, dot: true }); - const output = input - .pipe(plumber({ - errorHandler: function (err) { - if (err && !err.__reporter__) { - reporter(err); - } - } - })) - .pipe(tsFilter) - .pipe(util.loadSourcemaps()) - .pipe(compilation()) - .pipe(build ? util.stripSourceMappingURL() : es.through()) - .pipe(sourcemaps.write('.', { - sourceMappingURL: !build ? null : f => `${baseUrl}/${f.relative}.map`, - addComment: !!build, - includeContent: !!build, - // note: trailing slash is important, else the source URLs in V8's file coverage are incorrect - sourceRoot: '../src/', - })) - .pipe(tsFilter.restore) - .pipe(reporter.end(emitError)); - - return es.duplex(input, output); - }; - - // add src-stream for project files - pipeline.tsProjectSrc = () => { - return compilation.src(srcOpts); - }; - return pipeline; - } - - const cleanTask = task.define(`clean-extension-${name}`, util.rimraf(out)); - - const transpileTask = task.define(`transpile-extension:${name}`, task.series(cleanTask, () => { - const pipeline = createPipeline(false, true, true); - const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); - const input = es.merge(nonts, pipeline.tsProjectSrc()); - - return input - .pipe(pipeline()) - .pipe(gulp.dest(out)); - })); - - const compileTask = task.define(`compile-extension:${name}`, task.series(cleanTask, () => { - const pipeline = createPipeline(false, true); - const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); - const input = es.merge(nonts, pipeline.tsProjectSrc()); - - return input - .pipe(pipeline()) - .pipe(gulp.dest(out)); - })); - - const watchTask = task.define(`watch-extension:${name}`, task.series(cleanTask, () => { - const pipeline = createPipeline(false); - const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); - const input = es.merge(nonts, pipeline.tsProjectSrc()); - const watchInput = watcher(src, { ...srcOpts, ...{ readDelay: 200 } }); - - return watchInput - .pipe(util.incremental(pipeline, input)) - .pipe(gulp.dest(out)); - })); - - const compileBuildTask = task.define(`compile-build-extension-${name}`, task.series(cleanTask, () => { - const pipeline = createPipeline(true, true); - const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); - const input = es.merge(nonts, pipeline.tsProjectSrc()); - - return input - .pipe(pipeline()) - .pipe(gulp.dest(out)); - })); - - // Tasks - gulp.task(transpileTask); - gulp.task(compileTask); - gulp.task(watchTask); - - return { transpileTask, compileTask, watchTask, compileBuildTask }; -}); - -const transpileExtensionsTask = task.define('transpile-extensions', task.parallel(...tasks.map(t => t.transpileTask))); -gulp.task(transpileExtensionsTask); - -const compileExtensionsTask = task.define('compile-extensions', task.parallel(...tasks.map(t => t.compileTask))); -gulp.task(compileExtensionsTask); -exports.compileExtensionsTask = compileExtensionsTask; - -const watchExtensionsTask = task.define('watch-extensions', task.parallel(...tasks.map(t => t.watchTask))); -gulp.task(watchExtensionsTask); -exports.watchExtensionsTask = watchExtensionsTask; - -const compileExtensionsBuildLegacyTask = task.define('compile-extensions-build-legacy', task.parallel(...tasks.map(t => t.compileBuildTask))); -gulp.task(compileExtensionsBuildLegacyTask); - -//#region Extension media - -const compileExtensionMediaTask = task.define('compile-extension-media', () => ext.buildExtensionMedia(false)); -gulp.task(compileExtensionMediaTask); -exports.compileExtensionMediaTask = compileExtensionMediaTask; - -const watchExtensionMedia = task.define('watch-extension-media', () => ext.buildExtensionMedia(true)); -gulp.task(watchExtensionMedia); -exports.watchExtensionMedia = watchExtensionMedia; - -const compileExtensionMediaBuildTask = task.define('compile-extension-media-build', () => ext.buildExtensionMedia(false, '.build/extensions')); -gulp.task(compileExtensionMediaBuildTask); -exports.compileExtensionMediaBuildTask = compileExtensionMediaBuildTask; - -//#endregion - -//#region Azure Pipelines - -/** - * Cleans the build directory for extensions - */ -const cleanExtensionsBuildTask = task.define('clean-extensions-build', util.rimraf('.build/extensions')); -exports.cleanExtensionsBuildTask = cleanExtensionsBuildTask; - -/** - * brings in the marketplace extensions for the build - */ -const bundleMarketplaceExtensionsBuildTask = task.define('bundle-marketplace-extensions-build', () => ext.packageMarketplaceExtensionsStream(false).pipe(gulp.dest('.build'))); - -/** - * Compiles the non-native extensions for the build - * @note this does not clean the directory ahead of it. See {@link cleanExtensionsBuildTask} for that. - */ -const compileNonNativeExtensionsBuildTask = task.define('compile-non-native-extensions-build', task.series( - bundleMarketplaceExtensionsBuildTask, - task.define('bundle-non-native-extensions-build', () => ext.packageNonNativeLocalExtensionsStream().pipe(gulp.dest('.build'))) -)); -gulp.task(compileNonNativeExtensionsBuildTask); -exports.compileNonNativeExtensionsBuildTask = compileNonNativeExtensionsBuildTask; - -/** - * Compiles the native extensions for the build - * @note this does not clean the directory ahead of it. See {@link cleanExtensionsBuildTask} for that. - */ -const compileNativeExtensionsBuildTask = task.define('compile-native-extensions-build', () => ext.packageNativeLocalExtensionsStream().pipe(gulp.dest('.build'))); -gulp.task(compileNativeExtensionsBuildTask); -exports.compileNativeExtensionsBuildTask = compileNativeExtensionsBuildTask; - -/** - * Compiles the extensions for the build. - * This is essentially a helper task that combines {@link cleanExtensionsBuildTask}, {@link compileNonNativeExtensionsBuildTask} and {@link compileNativeExtensionsBuildTask} - */ -const compileAllExtensionsBuildTask = task.define('compile-extensions-build', task.series( - cleanExtensionsBuildTask, - bundleMarketplaceExtensionsBuildTask, - task.define('bundle-extensions-build', () => ext.packageAllLocalExtensionsStream(false, false).pipe(gulp.dest('.build'))), -)); -gulp.task(compileAllExtensionsBuildTask); -exports.compileAllExtensionsBuildTask = compileAllExtensionsBuildTask; - -// This task is run in the compilation stage of the CI pipeline. We only compile the non-native extensions since those can be fully built regardless of platform. -// This defers the native extensions to the platform specific stage of the CI pipeline. -gulp.task(task.define('extensions-ci', task.series(compileNonNativeExtensionsBuildTask, compileExtensionMediaBuildTask))); - -const compileExtensionsBuildPullRequestTask = task.define('compile-extensions-build-pr', task.series( - cleanExtensionsBuildTask, - bundleMarketplaceExtensionsBuildTask, - task.define('bundle-extensions-build-pr', () => ext.packageAllLocalExtensionsStream(false, true).pipe(gulp.dest('.build'))), -)); -gulp.task(compileExtensionsBuildPullRequestTask); - -// This task is run in the compilation stage of the PR pipeline. We compile all extensions in it to verify compilation. -gulp.task(task.define('extensions-ci-pr', task.series(compileExtensionsBuildPullRequestTask, compileExtensionMediaBuildTask))); - -//#endregion - -const compileWebExtensionsTask = task.define('compile-web', () => buildWebExtensions(false)); -gulp.task(compileWebExtensionsTask); -exports.compileWebExtensionsTask = compileWebExtensionsTask; - -const watchWebExtensionsTask = task.define('watch-web', () => buildWebExtensions(true)); -gulp.task(watchWebExtensionsTask); -exports.watchWebExtensionsTask = watchWebExtensionsTask; - -/** - * @param {boolean} isWatch - */ -async function buildWebExtensions(isWatch) { - const extensionsPath = path.join(root, 'extensions'); - const webpackConfigLocations = await nodeUtil.promisify(glob)( - path.join(extensionsPath, '**', 'extension-browser.webpack.config.js'), - { ignore: ['**/node_modules'] } - ); - return ext.webpackExtensions('packaging web extension', isWatch, webpackConfigLocations.map(configPath => ({ configPath }))); -} diff --git a/build/gulpfile.extensions.ts b/build/gulpfile.extensions.ts new file mode 100644 index 0000000000000..6f5cf0d25d81b --- /dev/null +++ b/build/gulpfile.extensions.ts @@ -0,0 +1,273 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Increase max listeners for event emitters +import { EventEmitter } from 'events'; +EventEmitter.defaultMaxListeners = 100; + +import gulp from 'gulp'; +import * as path from 'path'; +import * as nodeUtil from 'util'; +import es from 'event-stream'; +import filter from 'gulp-filter'; +import * as util from './lib/util.ts'; +import { getVersion } from './lib/getVersion.ts'; +import * as task from './lib/task.ts'; +import watcher from './lib/watch/index.ts'; +import { createReporter } from './lib/reporter.ts'; +import glob from 'glob'; +import plumber from 'gulp-plumber'; +import * as ext from './lib/extensions.ts'; +import * as tsb from './lib/tsb/index.ts'; +import sourcemaps from 'gulp-sourcemaps'; + +const root = path.dirname(import.meta.dirname); +const commit = getVersion(root); + +// To save 250ms for each gulp startup, we are caching the result here +// const compilations = glob.sync('**/tsconfig.json', { +// cwd: extensionsPath, +// ignore: ['**/out/**', '**/node_modules/**'] +// }); +const compilations = [ + 'extensions/configuration-editing/tsconfig.json', + 'extensions/css-language-features/client/tsconfig.json', + 'extensions/css-language-features/server/tsconfig.json', + 'extensions/debug-auto-launch/tsconfig.json', + 'extensions/debug-server-ready/tsconfig.json', + 'extensions/emmet/tsconfig.json', + 'extensions/extension-editing/tsconfig.json', + 'extensions/git/tsconfig.json', + 'extensions/git-base/tsconfig.json', + 'extensions/github/tsconfig.json', + 'extensions/github-authentication/tsconfig.json', + 'extensions/grunt/tsconfig.json', + 'extensions/gulp/tsconfig.json', + 'extensions/html-language-features/client/tsconfig.json', + 'extensions/html-language-features/server/tsconfig.json', + 'extensions/ipynb/tsconfig.json', + 'extensions/jake/tsconfig.json', + 'extensions/json-language-features/client/tsconfig.json', + 'extensions/json-language-features/server/tsconfig.json', + 'extensions/markdown-language-features/tsconfig.json', + 'extensions/markdown-math/tsconfig.json', + 'extensions/media-preview/tsconfig.json', + 'extensions/merge-conflict/tsconfig.json', + 'extensions/mermaid-chat-features/tsconfig.json', + 'extensions/terminal-suggest/tsconfig.json', + 'extensions/microsoft-authentication/tsconfig.json', + 'extensions/notebook-renderers/tsconfig.json', + 'extensions/npm/tsconfig.json', + 'extensions/php-language-features/tsconfig.json', + 'extensions/references-view/tsconfig.json', + 'extensions/search-result/tsconfig.json', + 'extensions/simple-browser/tsconfig.json', + 'extensions/tunnel-forwarding/tsconfig.json', + 'extensions/typescript-language-features/web/tsconfig.json', + 'extensions/typescript-language-features/tsconfig.json', + 'extensions/vscode-api-tests/tsconfig.json', + 'extensions/vscode-colorize-tests/tsconfig.json', + 'extensions/vscode-colorize-perf-tests/tsconfig.json', + 'extensions/vscode-test-resolver/tsconfig.json', + + '.vscode/extensions/vscode-selfhost-test-provider/tsconfig.json', + '.vscode/extensions/vscode-selfhost-import-aid/tsconfig.json', +]; + +const getBaseUrl = (out: string) => `https://main.vscode-cdn.net/sourcemaps/${commit}/${out}`; + +const tasks = compilations.map(function (tsconfigFile) { + const absolutePath = path.join(root, tsconfigFile); + const relativeDirname = path.dirname(tsconfigFile.replace(/^(.*\/)?extensions\//i, '')); + + const overrideOptions: { sourceMap?: boolean; inlineSources?: boolean; base?: string } = {}; + overrideOptions.sourceMap = true; + + const name = relativeDirname.replace(/\//g, '-'); + + const srcRoot = path.dirname(tsconfigFile); + const srcBase = path.join(srcRoot, 'src'); + const src = path.join(srcBase, '**'); + const srcOpts = { cwd: root, base: srcBase, dot: true }; + + const out = path.join(srcRoot, 'out'); + const baseUrl = getBaseUrl(out); + + function createPipeline(build: boolean, emitError?: boolean, transpileOnly?: boolean) { + const reporter = createReporter('extensions'); + + overrideOptions.inlineSources = Boolean(build); + overrideOptions.base = path.dirname(absolutePath); + + const compilation = tsb.create(absolutePath, overrideOptions, { verbose: false, transpileOnly, transpileOnlyIncludesDts: transpileOnly, transpileWithEsbuild: true }, err => reporter(err.toString())); + + const pipeline = function () { + const input = es.through(); + const tsFilter = filter(['**/*.ts', '!**/lib/lib*.d.ts', '!**/node_modules/**'], { restore: true, dot: true }); + const output = input + .pipe(plumber({ + errorHandler: function (err) { + if (err && !err.__reporter__) { + reporter(err); + } + } + })) + .pipe(tsFilter) + .pipe(util.loadSourcemaps()) + .pipe(compilation()) + .pipe(build ? util.stripSourceMappingURL() : es.through()) + .pipe(sourcemaps.write('.', { + sourceMappingURL: !build ? undefined : f => `${baseUrl}/${f.relative}.map`, + addComment: !!build, + includeContent: !!build, + // note: trailing slash is important, else the source URLs in V8's file coverage are incorrect + sourceRoot: '../src/', + })) + .pipe(tsFilter.restore) + .pipe(reporter.end(!!emitError)); + + return es.duplex(input, output); + }; + + // add src-stream for project files + pipeline.tsProjectSrc = () => { + return compilation.src(srcOpts); + }; + return pipeline; + } + + const cleanTask = task.define(`clean-extension-${name}`, util.rimraf(out)); + + const transpileTask = task.define(`transpile-extension:${name}`, task.series(cleanTask, () => { + const pipeline = createPipeline(false, true, true); + const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); + const input = es.merge(nonts, pipeline.tsProjectSrc()); + + return input + .pipe(pipeline()) + .pipe(gulp.dest(out)); + })); + + const compileTask = task.define(`compile-extension:${name}`, task.series(cleanTask, () => { + const pipeline = createPipeline(false, true); + const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); + const input = es.merge(nonts, pipeline.tsProjectSrc()); + + return input + .pipe(pipeline()) + .pipe(gulp.dest(out)); + })); + + const watchTask = task.define(`watch-extension:${name}`, task.series(cleanTask, () => { + const pipeline = createPipeline(false); + const nonts = gulp.src(src, srcOpts).pipe(filter(['**', '!**/*.ts'])); + const input = es.merge(nonts, pipeline.tsProjectSrc()); + const watchInput = watcher(src, { ...srcOpts, ...{ readDelay: 200 } }); + + return watchInput + .pipe(util.incremental(pipeline, input)) + .pipe(gulp.dest(out)); + })); + + // Tasks + gulp.task(transpileTask); + gulp.task(compileTask); + gulp.task(watchTask); + + return { transpileTask, compileTask, watchTask }; +}); + +const transpileExtensionsTask = task.define('transpile-extensions', task.parallel(...tasks.map(t => t.transpileTask))); +gulp.task(transpileExtensionsTask); + +export const compileExtensionsTask = task.define('compile-extensions', task.parallel(...tasks.map(t => t.compileTask))); +gulp.task(compileExtensionsTask); + +export const watchExtensionsTask = task.define('watch-extensions', task.parallel(...tasks.map(t => t.watchTask))); +gulp.task(watchExtensionsTask); + +//#region Extension media + +export const compileExtensionMediaTask = task.define('compile-extension-media', () => ext.buildExtensionMedia(false)); +gulp.task(compileExtensionMediaTask); + +export const watchExtensionMedia = task.define('watch-extension-media', () => ext.buildExtensionMedia(true)); +gulp.task(watchExtensionMedia); + +export const compileExtensionMediaBuildTask = task.define('compile-extension-media-build', () => ext.buildExtensionMedia(false, '.build/extensions')); +gulp.task(compileExtensionMediaBuildTask); + +//#endregion + +//#region Azure Pipelines + +/** + * Cleans the build directory for extensions + */ +export const cleanExtensionsBuildTask = task.define('clean-extensions-build', util.rimraf('.build/extensions')); + +/** + * brings in the marketplace extensions for the build + */ +const bundleMarketplaceExtensionsBuildTask = task.define('bundle-marketplace-extensions-build', () => ext.packageMarketplaceExtensionsStream(false).pipe(gulp.dest('.build'))); + +/** + * Compiles the non-native extensions for the build + * @note this does not clean the directory ahead of it. See {@link cleanExtensionsBuildTask} for that. + */ +export const compileNonNativeExtensionsBuildTask = task.define('compile-non-native-extensions-build', task.series( + bundleMarketplaceExtensionsBuildTask, + task.define('bundle-non-native-extensions-build', () => ext.packageNonNativeLocalExtensionsStream(false, false).pipe(gulp.dest('.build'))) +)); +gulp.task(compileNonNativeExtensionsBuildTask); + +/** + * Compiles the native extensions for the build + * @note this does not clean the directory ahead of it. See {@link cleanExtensionsBuildTask} for that. + */ +export const compileNativeExtensionsBuildTask = task.define('compile-native-extensions-build', () => ext.packageNativeLocalExtensionsStream(false, false).pipe(gulp.dest('.build'))); +gulp.task(compileNativeExtensionsBuildTask); + +/** + * Compiles the extensions for the build. + * This is essentially a helper task that combines {@link cleanExtensionsBuildTask}, {@link compileNonNativeExtensionsBuildTask} and {@link compileNativeExtensionsBuildTask} + */ +export const compileAllExtensionsBuildTask = task.define('compile-extensions-build', task.series( + cleanExtensionsBuildTask, + bundleMarketplaceExtensionsBuildTask, + task.define('bundle-extensions-build', () => ext.packageAllLocalExtensionsStream(false, false).pipe(gulp.dest('.build'))), +)); +gulp.task(compileAllExtensionsBuildTask); + +// This task is run in the compilation stage of the CI pipeline. We only compile the non-native extensions since those can be fully built regardless of platform. +// This defers the native extensions to the platform specific stage of the CI pipeline. +gulp.task(task.define('extensions-ci', task.series(compileNonNativeExtensionsBuildTask, compileExtensionMediaBuildTask))); + +const compileExtensionsBuildPullRequestTask = task.define('compile-extensions-build-pr', task.series( + cleanExtensionsBuildTask, + bundleMarketplaceExtensionsBuildTask, + task.define('bundle-extensions-build-pr', () => ext.packageAllLocalExtensionsStream(false, true).pipe(gulp.dest('.build'))), +)); +gulp.task(compileExtensionsBuildPullRequestTask); + +// This task is run in the compilation stage of the PR pipeline. We compile all extensions in it to verify compilation. +gulp.task(task.define('extensions-ci-pr', task.series(compileExtensionsBuildPullRequestTask, compileExtensionMediaBuildTask))); + +//#endregion + +export const compileWebExtensionsTask = task.define('compile-web', () => buildWebExtensions(false)); +gulp.task(compileWebExtensionsTask); + +export const watchWebExtensionsTask = task.define('watch-web', () => buildWebExtensions(true)); +gulp.task(watchWebExtensionsTask); + +async function buildWebExtensions(isWatch: boolean) { + const extensionsPath = path.join(root, 'extensions'); + const webpackConfigLocations = await nodeUtil.promisify(glob)( + path.join(extensionsPath, '**', 'extension-browser.webpack.config.js'), + { ignore: ['**/node_modules'] } + ); + return ext.webpackExtensions('packaging web extension', isWatch, webpackConfigLocations.map(configPath => ({ configPath }))); +} diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js deleted file mode 100644 index c76fab7abc6c5..0000000000000 --- a/build/gulpfile.hygiene.js +++ /dev/null @@ -1,51 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const gulp = require('gulp'); -const es = require('event-stream'); -const path = require('path'); -const task = require('./lib/task'); -const { hygiene } = require('./hygiene'); - -/** - * @param {string} actualPath - */ -function checkPackageJSON(actualPath) { - const actual = require(path.join(__dirname, '..', actualPath)); - const rootPackageJSON = require('../package.json'); - const checkIncluded = (set1, set2) => { - for (const depName in set1) { - const depVersion = set1[depName]; - const rootDepVersion = set2[depName]; - if (!rootDepVersion) { - // missing in root is allowed - continue; - } - if (depVersion !== rootDepVersion) { - this.emit( - 'error', - `The dependency ${depName} in '${actualPath}' (${depVersion}) is different than in the root package.json (${rootDepVersion})` - ); - } - } - }; - - checkIncluded(actual.dependencies, rootPackageJSON.dependencies); - checkIncluded(actual.devDependencies, rootPackageJSON.devDependencies); -} - -const checkPackageJSONTask = task.define('check-package-json', () => { - return gulp.src('package.json').pipe( - es.through(function () { - checkPackageJSON.call(this, 'remote/package.json'); - checkPackageJSON.call(this, 'remote/web/package.json'); - checkPackageJSON.call(this, 'build/package.json'); - }) - ); -}); -gulp.task(checkPackageJSONTask); - -const hygieneTask = task.define('hygiene', task.series(checkPackageJSONTask, () => hygiene(undefined, false))); -gulp.task(hygieneTask); diff --git a/build/gulpfile.hygiene.ts b/build/gulpfile.hygiene.ts new file mode 100644 index 0000000000000..24595643c86cd --- /dev/null +++ b/build/gulpfile.hygiene.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import gulp from 'gulp'; +import es from 'event-stream'; +import path from 'path'; +import fs from 'fs'; +import * as task from './lib/task.ts'; +import { hygiene } from './hygiene.ts'; + +const dirName = path.dirname(new URL(import.meta.url).pathname); + +function checkPackageJSON(this: NodeJS.ReadWriteStream, actualPath: string) { + const actual = JSON.parse(fs.readFileSync(path.join(dirName, '..', actualPath), 'utf8')); + const rootPackageJSON = JSON.parse(fs.readFileSync(path.join(dirName, '..', 'package.json'), 'utf8')); + const checkIncluded = (set1: Record, set2: Record) => { + for (const depName in set1) { + const depVersion = set1[depName]; + const rootDepVersion = set2[depName]; + if (!rootDepVersion) { + // missing in root is allowed + continue; + } + if (depVersion !== rootDepVersion) { + this.emit( + 'error', + `The dependency ${depName} in '${actualPath}' (${depVersion}) is different than in the root package.json (${rootDepVersion})` + ); + } + } + }; + + checkIncluded(actual.dependencies, rootPackageJSON.dependencies); + checkIncluded(actual.devDependencies, rootPackageJSON.devDependencies); +} + +const checkPackageJSONTask = task.define('check-package-json', () => { + return gulp.src('package.json').pipe( + es.through(function () { + checkPackageJSON.call(this, 'remote/package.json'); + checkPackageJSON.call(this, 'remote/web/package.json'); + checkPackageJSON.call(this, 'build/package.json'); + }) + ); +}); +gulp.task(checkPackageJSONTask); + +const hygieneTask = task.define('hygiene', task.series(checkPackageJSONTask, () => hygiene(undefined, false))); +gulp.task(hygieneTask); diff --git a/build/gulpfile.js b/build/gulpfile.js deleted file mode 100644 index 7894398c2ea94..0000000000000 --- a/build/gulpfile.js +++ /dev/null @@ -1,53 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -// Increase max listeners for event emitters -require('events').EventEmitter.defaultMaxListeners = 100; - -const gulp = require('gulp'); -const util = require('./lib/util'); -const task = require('./lib/task'); -const { transpileClientSWC, transpileTask, compileTask, watchTask, compileApiProposalNamesTask, watchApiProposalNamesTask } = require('./lib/compilation'); -const { monacoTypecheckTask/* , monacoTypecheckWatchTask */ } = require('./gulpfile.editor'); -const { compileExtensionsTask, watchExtensionsTask, compileExtensionMediaTask } = require('./gulpfile.extensions'); - -// API proposal names -gulp.task(compileApiProposalNamesTask); -gulp.task(watchApiProposalNamesTask); - -// SWC Client Transpile -const transpileClientSWCTask = task.define('transpile-client-esbuild', task.series(util.rimraf('out'), transpileTask('src', 'out', true))); -gulp.task(transpileClientSWCTask); - -// Transpile only -const transpileClientTask = task.define('transpile-client', task.series(util.rimraf('out'), transpileTask('src', 'out'))); -gulp.task(transpileClientTask); - -// Fast compile for development time -const compileClientTask = task.define('compile-client', task.series(util.rimraf('out'), compileApiProposalNamesTask, compileTask('src', 'out', false))); -gulp.task(compileClientTask); - -const watchClientTask = task.define('watch-client', task.series(util.rimraf('out'), task.parallel(watchTask('out', false), watchApiProposalNamesTask))); -gulp.task(watchClientTask); - -// All -const _compileTask = task.define('compile', task.parallel(monacoTypecheckTask, compileClientTask, compileExtensionsTask, compileExtensionMediaTask)); -gulp.task(_compileTask); - -gulp.task(task.define('watch', task.parallel(/* monacoTypecheckWatchTask, */ watchClientTask, watchExtensionsTask))); - -// Default -gulp.task('default', _compileTask); - -process.on('unhandledRejection', (reason, p) => { - console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); - process.exit(1); -}); - -// Load all the gulpfiles only if running tasks other than the editor tasks -require('glob').sync('gulpfile.*.js', { cwd: __dirname }) - .forEach(f => require(`./${f}`)); diff --git a/build/gulpfile.reh.js b/build/gulpfile.reh.js deleted file mode 100644 index 10b7b44b5ecc0..0000000000000 --- a/build/gulpfile.reh.js +++ /dev/null @@ -1,487 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -const gulp = require('gulp'); -const path = require('path'); -const es = require('event-stream'); -const util = require('./lib/util'); -const { getVersion } = require('./lib/getVersion'); -const task = require('./lib/task'); -const optimize = require('./lib/optimize'); -const { inlineMeta } = require('./lib/inlineMeta'); -const product = require('../product.json'); -const rename = require('gulp-rename'); -const replace = require('gulp-replace'); -const filter = require('gulp-filter'); -const { getProductionDependencies } = require('./lib/dependencies'); -const { readISODate } = require('./lib/date'); -const vfs = require('vinyl-fs'); -const packageJson = require('../package.json'); -const flatmap = require('gulp-flatmap'); -const gunzip = require('gulp-gunzip'); -const File = require('vinyl'); -const fs = require('fs'); -const glob = require('glob'); -const { compileBuildWithManglingTask } = require('./gulpfile.compile'); -const { cleanExtensionsBuildTask, compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions'); -const { vscodeWebResourceIncludes, createVSCodeWebFileContentMapper } = require('./gulpfile.vscode.web'); -const cp = require('child_process'); -const log = require('fancy-log'); -const buildfile = require('./buildfile'); - -const REPO_ROOT = path.dirname(__dirname); -const commit = getVersion(REPO_ROOT); -const BUILD_ROOT = path.dirname(REPO_ROOT); -const REMOTE_FOLDER = path.join(REPO_ROOT, 'remote'); - -// Targets - -const BUILD_TARGETS = [ - { platform: 'win32', arch: 'x64' }, - { platform: 'win32', arch: 'arm64' }, - { platform: 'darwin', arch: 'x64' }, - { platform: 'darwin', arch: 'arm64' }, - { platform: 'linux', arch: 'x64' }, - { platform: 'linux', arch: 'armhf' }, - { platform: 'linux', arch: 'arm64' }, - { platform: 'alpine', arch: 'arm64' }, - // legacy: we use to ship only one alpine so it was put in the arch, but now we ship - // multiple alpine images and moved to a better model (alpine as the platform) - { platform: 'linux', arch: 'alpine' }, -]; - -const serverResourceIncludes = [ - - // NLS - 'out-build/nls.messages.json', - 'out-build/nls.keys.json', - - // Process monitor - 'out-build/vs/base/node/cpuUsage.sh', - 'out-build/vs/base/node/ps.sh', - - // External Terminal - 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', - - // Terminal shell integration - 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1', - 'out-build/vs/workbench/contrib/terminal/common/scripts/CodeTabExpansion.psm1', - 'out-build/vs/workbench/contrib/terminal/common/scripts/GitTabExpansion.psm1', - 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh', - 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-env.zsh', - 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-profile.zsh', - 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh', - 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-login.zsh', - 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish', - -]; - -const serverResourceExcludes = [ - '!out-build/vs/**/{electron-browser,electron-main,electron-utility}/**', - '!out-build/vs/editor/standalone/**', - '!out-build/vs/workbench/**/*-tb.png', - '!**/test/**' -]; - -const serverResources = [ - ...serverResourceIncludes, - ...serverResourceExcludes -]; - -const serverWithWebResourceIncludes = [ - ...serverResourceIncludes, - 'out-build/vs/code/browser/workbench/*.html', - ...vscodeWebResourceIncludes -]; - -const serverWithWebResourceExcludes = [ - ...serverResourceExcludes, - '!out-build/vs/code/**/*-dev.html' -]; - -const serverWithWebResources = [ - ...serverWithWebResourceIncludes, - ...serverWithWebResourceExcludes -]; -const serverEntryPoints = buildfile.codeServer; - -const webEntryPoints = [ - buildfile.workerEditor, - buildfile.workerExtensionHost, - buildfile.workerNotebook, - buildfile.workerLanguageDetection, - buildfile.workerLocalFileSearch, - buildfile.workerOutputLinks, - buildfile.workerBackgroundTokenization, - buildfile.keyboardMaps, - buildfile.codeWeb -].flat(); - -const serverWithWebEntryPoints = [ - - // Include all of server - ...serverEntryPoints, - - // Include all of web - ...webEntryPoints, -].flat(); - -const bootstrapEntryPoints = [ - 'out-build/server-main.js', - 'out-build/server-cli.js', - 'out-build/bootstrap-fork.js' -]; - -function getNodeVersion() { - const npmrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.npmrc'), 'utf8'); - const nodeVersion = /^target="(.*)"$/m.exec(npmrc)[1]; - const internalNodeVersion = /^ms_build_id="(.*)"$/m.exec(npmrc)[1]; - return { nodeVersion, internalNodeVersion }; -} - -function getNodeChecksum(expectedName) { - const nodeJsChecksums = fs.readFileSync(path.join(REPO_ROOT, 'build', 'checksums', 'nodejs.txt'), 'utf8'); - for (const line of nodeJsChecksums.split('\n')) { - const [checksum, name] = line.split(/\s+/); - if (name === expectedName) { - return checksum; - } - } - return undefined; -} - -function extractAlpinefromDocker(nodeVersion, platform, arch) { - const imageName = arch === 'arm64' ? 'arm64v8/node' : 'node'; - log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from docker image ${imageName}`); - const contents = cp.execSync(`docker run --rm ${imageName}:${nodeVersion}-alpine /bin/sh -c 'cat \`which node\`'`, { maxBuffer: 100 * 1024 * 1024, encoding: 'buffer' }); - return es.readArray([new File({ path: 'node', contents, stat: { mode: parseInt('755', 8) } })]); -} - -const { nodeVersion, internalNodeVersion } = getNodeVersion(); - -BUILD_TARGETS.forEach(({ platform, arch }) => { - gulp.task(task.define(`node-${platform}-${arch}`, () => { - const nodePath = path.join('.build', 'node', `v${nodeVersion}`, `${platform}-${arch}`); - - if (!fs.existsSync(nodePath)) { - util.rimraf(nodePath); - - return nodejs(platform, arch) - .pipe(vfs.dest(nodePath)); - } - - return Promise.resolve(null); - })); -}); - -const defaultNodeTask = gulp.task(`node-${process.platform}-${process.arch}`); - -if (defaultNodeTask) { - gulp.task(task.define('node', defaultNodeTask)); -} - -function nodejs(platform, arch) { - const { fetchUrls, fetchGithub } = require('./lib/fetch'); - const untar = require('gulp-untar'); - - if (arch === 'armhf') { - arch = 'armv7l'; - } else if (arch === 'alpine') { - platform = 'alpine'; - arch = 'x64'; - } - - log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from ${product.nodejsRepository}...`); - - const glibcPrefix = process.env['VSCODE_NODE_GLIBC'] ?? ''; - let expectedName; - switch (platform) { - case 'win32': - expectedName = product.nodejsRepository !== 'https://nodejs.org' ? - `win-${arch}-node.exe` : `win-${arch}/node.exe`; - break; - - case 'darwin': - expectedName = `node-v${nodeVersion}-${platform}-${arch}.tar.gz`; - break; - case 'linux': - expectedName = `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`; - break; - case 'alpine': - expectedName = `node-v${nodeVersion}-linux-${arch}-musl.tar.gz`; - break; - } - const checksumSha256 = getNodeChecksum(expectedName); - - if (checksumSha256) { - log(`Using SHA256 checksum for checking integrity: ${checksumSha256}`); - } else { - log.warn(`Unable to verify integrity of downloaded node.js binary because no SHA256 checksum was found!`); - } - - switch (platform) { - case 'win32': - return (product.nodejsRepository !== 'https://nodejs.org' ? - fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName, checksumSha256 }) : - fetchUrls(`/dist/v${nodeVersion}/win-${arch}/node.exe`, { base: 'https://nodejs.org', checksumSha256 })) - .pipe(rename('node.exe')); - case 'darwin': - case 'linux': - return (product.nodejsRepository !== 'https://nodejs.org' ? - fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName, checksumSha256 }) : - fetchUrls(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', checksumSha256 }) - ).pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) - .pipe(filter('**/node')) - .pipe(util.setExecutableBit('**')) - .pipe(rename('node')); - case 'alpine': - return product.nodejsRepository !== 'https://nodejs.org' ? - fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName, checksumSha256 }) - .pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) - .pipe(filter('**/node')) - .pipe(util.setExecutableBit('**')) - .pipe(rename('node')) - : extractAlpinefromDocker(nodeVersion, platform, arch); - } -} - -function packageTask(type, platform, arch, sourceFolderName, destinationFolderName) { - const destination = path.join(BUILD_ROOT, destinationFolderName); - - return () => { - const json = require('gulp-json-editor'); - - const src = gulp.src(sourceFolderName + '/**', { base: '.' }) - .pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); })) - .pipe(util.setExecutableBit(['**/*.sh'])) - .pipe(filter(['**', '!**/*.{js,css}.map'])); - - const workspaceExtensionPoints = ['debuggers', 'jsonValidation']; - const isUIExtension = (manifest) => { - switch (manifest.extensionKind) { - case 'ui': return true; - case 'workspace': return false; - default: { - if (manifest.main) { - return false; - } - if (manifest.contributes && Object.keys(manifest.contributes).some(key => workspaceExtensionPoints.indexOf(key) !== -1)) { - return false; - } - // Default is UI Extension - return true; - } - } - }; - const localWorkspaceExtensions = glob.sync('extensions/*/package.json') - .filter((extensionPath) => { - if (type === 'reh-web') { - return true; // web: ship all extensions for now - } - - // Skip shipping UI extensions because the client side will have them anyways - // and they'd just increase the download without being used - const manifest = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, extensionPath)).toString()); - return !isUIExtension(manifest); - }).map((extensionPath) => path.basename(path.dirname(extensionPath))) - .filter(name => name !== 'vscode-api-tests' && name !== 'vscode-test-resolver'); // Do not ship the test extensions - const marketplaceExtensions = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'product.json'), 'utf8')).builtInExtensions - .filter(entry => !entry.platforms || new Set(entry.platforms).has(platform)) - .filter(entry => !entry.clientOnly) - .map(entry => entry.name); - const extensionPaths = [...localWorkspaceExtensions, ...marketplaceExtensions] - .map(name => `.build/extensions/${name}/**`); - - const extensions = gulp.src(extensionPaths, { base: '.build', dot: true }); - const extensionsCommonDependencies = gulp.src('.build/extensions/node_modules/**', { base: '.build', dot: true }); - const sources = es.merge(src, extensions, extensionsCommonDependencies) - .pipe(filter(['**', '!**/*.{js,css}.map'], { dot: true })); - - let version = packageJson.version; - const quality = product.quality; - - if (quality && quality !== 'stable') { - version += '-' + quality; - } - - const name = product.nameShort; - - let packageJsonContents; - const packageJsonStream = gulp.src(['remote/package.json'], { base: 'remote' }) - .pipe(json({ name, version, dependencies: undefined, optionalDependencies: undefined, type: 'module' })) - .pipe(es.through(function (file) { - packageJsonContents = file.contents.toString(); - this.emit('data', file); - })); - - let productJsonContents; - const productJsonStream = gulp.src(['product.json'], { base: '.' }) - .pipe(json({ commit, date: readISODate('out-build'), version })) - .pipe(es.through(function (file) { - productJsonContents = file.contents.toString(); - this.emit('data', file); - })); - - const license = gulp.src(['remote/LICENSE'], { base: 'remote', allowEmpty: true }); - - const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path)); - - const productionDependencies = getProductionDependencies(REMOTE_FOLDER); - const dependenciesSrc = productionDependencies.map(d => path.relative(REPO_ROOT, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`]).flat(); - const deps = gulp.src(dependenciesSrc, { base: 'remote', dot: true }) - // filter out unnecessary files, no source maps in server build - .pipe(filter(['**', '!**/package-lock.json', '!**/*.{js,css}.map'])) - .pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore'))) - .pipe(util.cleanNodeModules(path.join(__dirname, `.moduleignore.${process.platform}`))) - .pipe(jsFilter) - .pipe(util.stripSourceMappingURL()) - .pipe(jsFilter.restore); - - const nodePath = `.build/node/v${nodeVersion}/${platform}-${arch}`; - const node = gulp.src(`${nodePath}/**`, { base: nodePath, dot: true }); - - let web = []; - if (type === 'reh-web') { - web = [ - 'resources/server/favicon.ico', - 'resources/server/code-192.png', - 'resources/server/code-512.png', - 'resources/server/manifest.json' - ].map(resource => gulp.src(resource, { base: '.' }).pipe(rename(resource))); - } - - const all = es.merge( - packageJsonStream, - productJsonStream, - license, - sources, - deps, - node, - ...web - ); - - let result = all - .pipe(util.skipDirectories()) - .pipe(util.fixWin32DirectoryPermissions()); - - if (platform === 'win32') { - result = es.merge(result, - gulp.src('resources/server/bin/remote-cli/code.cmd', { base: '.' }) - .pipe(replace('@@VERSION@@', version)) - .pipe(replace('@@COMMIT@@', commit)) - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename(`bin/remote-cli/${product.applicationName}.cmd`)), - gulp.src('resources/server/bin/helpers/browser.cmd', { base: '.' }) - .pipe(replace('@@VERSION@@', version)) - .pipe(replace('@@COMMIT@@', commit)) - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename(`bin/helpers/browser.cmd`)), - gulp.src('resources/server/bin/code-server.cmd', { base: '.' }) - .pipe(rename(`bin/${product.serverApplicationName}.cmd`)), - ); - } else if (platform === 'linux' || platform === 'alpine' || platform === 'darwin') { - result = es.merge(result, - gulp.src(`resources/server/bin/remote-cli/${platform === 'darwin' ? 'code-darwin.sh' : 'code-linux.sh'}`, { base: '.' }) - .pipe(replace('@@VERSION@@', version)) - .pipe(replace('@@COMMIT@@', commit)) - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename(`bin/remote-cli/${product.applicationName}`)) - .pipe(util.setExecutableBit()), - gulp.src(`resources/server/bin/helpers/${platform === 'darwin' ? 'browser-darwin.sh' : 'browser-linux.sh'}`, { base: '.' }) - .pipe(replace('@@VERSION@@', version)) - .pipe(replace('@@COMMIT@@', commit)) - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename(`bin/helpers/browser.sh`)) - .pipe(util.setExecutableBit()), - gulp.src(`resources/server/bin/${platform === 'darwin' ? 'code-server-darwin.sh' : 'code-server-linux.sh'}`, { base: '.' }) - .pipe(rename(`bin/${product.serverApplicationName}`)) - .pipe(util.setExecutableBit()) - ); - } - - if (platform === 'linux' || platform === 'alpine') { - result = es.merge(result, - gulp.src(`resources/server/bin/helpers/check-requirements-linux.sh`, { base: '.' }) - .pipe(rename(`bin/helpers/check-requirements.sh`)) - .pipe(util.setExecutableBit()) - ); - } - - result = inlineMeta(result, { - targetPaths: bootstrapEntryPoints, - packageJsonFn: () => packageJsonContents, - productJsonFn: () => productJsonContents - }); - - return result.pipe(vfs.dest(destination)); - }; -} - -/** - * @param {object} product The parsed product.json file contents - */ -function tweakProductForServerWeb(product) { - const result = { ...product }; - delete result.webEndpointUrlTemplate; - return result; -} - -['reh', 'reh-web'].forEach(type => { - const bundleTask = task.define(`bundle-vscode-${type}`, task.series( - util.rimraf(`out-vscode-${type}`), - optimize.bundleTask( - { - out: `out-vscode-${type}`, - esm: { - src: 'out-build', - entryPoints: [ - ...(type === 'reh' ? serverEntryPoints : serverWithWebEntryPoints), - ...bootstrapEntryPoints - ], - resources: type === 'reh' ? serverResources : serverWithWebResources, - fileContentMapper: createVSCodeWebFileContentMapper('.build/extensions', type === 'reh-web' ? tweakProductForServerWeb(product) : product) - } - } - ) - )); - - const minifyTask = task.define(`minify-vscode-${type}`, task.series( - bundleTask, - util.rimraf(`out-vscode-${type}-min`), - optimize.minifyTask(`out-vscode-${type}`, `https://main.vscode-cdn.net/sourcemaps/${commit}/core`) - )); - gulp.task(minifyTask); - - BUILD_TARGETS.forEach(buildTarget => { - const dashed = (str) => (str ? `-${str}` : ``); - const platform = buildTarget.platform; - const arch = buildTarget.arch; - - ['', 'min'].forEach(minified => { - const sourceFolderName = `out-vscode-${type}${dashed(minified)}`; - const destinationFolderName = `vscode-${type}${dashed(platform)}${dashed(arch)}`; - - const serverTaskCI = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series( - compileNativeExtensionsBuildTask, - gulp.task(`node-${platform}-${arch}`), - util.rimraf(path.join(BUILD_ROOT, destinationFolderName)), - packageTask(type, platform, arch, sourceFolderName, destinationFolderName) - )); - gulp.task(serverTaskCI); - - const serverTask = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( - compileBuildWithManglingTask, - cleanExtensionsBuildTask, - compileNonNativeExtensionsBuildTask, - compileExtensionMediaBuildTask, - minified ? minifyTask : bundleTask, - serverTaskCI - )); - gulp.task(serverTask); - }); - }); -}); diff --git a/build/gulpfile.reh.ts b/build/gulpfile.reh.ts new file mode 100644 index 0000000000000..cb1a0a5fd6964 --- /dev/null +++ b/build/gulpfile.reh.ts @@ -0,0 +1,488 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import gulp from 'gulp'; +import * as path from 'path'; +import es from 'event-stream'; +import * as util from './lib/util.ts'; +import { getVersion } from './lib/getVersion.ts'; +import * as task from './lib/task.ts'; +import * as optimize from './lib/optimize.ts'; +import { inlineMeta } from './lib/inlineMeta.ts'; +import product from '../product.json' with { type: 'json' }; +import rename from 'gulp-rename'; +import replace from 'gulp-replace'; +import filter from 'gulp-filter'; +import { getProductionDependencies } from './lib/dependencies.ts'; +import { readISODate } from './lib/date.ts'; +import vfs from 'vinyl-fs'; +import packageJson from '../package.json' with { type: 'json' }; +import flatmap from 'gulp-flatmap'; +import gunzip from 'gulp-gunzip'; +import untar from 'gulp-untar'; +import File from 'vinyl'; +import * as fs from 'fs'; +import glob from 'glob'; +import { compileBuildWithManglingTask } from './gulpfile.compile.ts'; +import { cleanExtensionsBuildTask, compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileExtensionMediaBuildTask } from './gulpfile.extensions.ts'; +import { vscodeWebResourceIncludes, createVSCodeWebFileContentMapper } from './gulpfile.vscode.web.ts'; +import * as cp from 'child_process'; +import log from 'fancy-log'; +import buildfile from './buildfile.ts'; +import { fetchUrls, fetchGithub } from './lib/fetch.ts'; +import jsonEditor from 'gulp-json-editor'; + + +const REPO_ROOT = path.dirname(import.meta.dirname); +const commit = getVersion(REPO_ROOT); +const BUILD_ROOT = path.dirname(REPO_ROOT); +const REMOTE_FOLDER = path.join(REPO_ROOT, 'remote'); + +// Targets + +const BUILD_TARGETS = [ + { platform: 'win32', arch: 'x64' }, + { platform: 'win32', arch: 'arm64' }, + { platform: 'darwin', arch: 'x64' }, + { platform: 'darwin', arch: 'arm64' }, + { platform: 'linux', arch: 'x64' }, + { platform: 'linux', arch: 'armhf' }, + { platform: 'linux', arch: 'arm64' }, + { platform: 'alpine', arch: 'arm64' }, + // legacy: we use to ship only one alpine so it was put in the arch, but now we ship + // multiple alpine images and moved to a better model (alpine as the platform) + { platform: 'linux', arch: 'alpine' }, +]; + +const serverResourceIncludes = [ + + // NLS + 'out-build/nls.messages.json', + 'out-build/nls.keys.json', + + // Process monitor + 'out-build/vs/base/node/cpuUsage.sh', + 'out-build/vs/base/node/ps.sh', + + // External Terminal + 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', + + // Terminal shell integration + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1', + 'out-build/vs/workbench/contrib/terminal/common/scripts/CodeTabExpansion.psm1', + 'out-build/vs/workbench/contrib/terminal/common/scripts/GitTabExpansion.psm1', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-env.zsh', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-profile.zsh', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-login.zsh', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish', + +]; + +const serverResourceExcludes = [ + '!out-build/vs/**/{electron-browser,electron-main,electron-utility}/**', + '!out-build/vs/editor/standalone/**', + '!out-build/vs/workbench/**/*-tb.png', + '!**/test/**' +]; + +const serverResources = [ + ...serverResourceIncludes, + ...serverResourceExcludes +]; + +const serverWithWebResourceIncludes = [ + ...serverResourceIncludes, + 'out-build/vs/code/browser/workbench/*.html', + ...vscodeWebResourceIncludes +]; + +const serverWithWebResourceExcludes = [ + ...serverResourceExcludes, + '!out-build/vs/code/**/*-dev.html' +]; + +const serverWithWebResources = [ + ...serverWithWebResourceIncludes, + ...serverWithWebResourceExcludes +]; +const serverEntryPoints = buildfile.codeServer; + +const webEntryPoints = [ + buildfile.workerEditor, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.workerOutputLinks, + buildfile.workerBackgroundTokenization, + buildfile.keyboardMaps, + buildfile.codeWeb +].flat(); + +const serverWithWebEntryPoints = [ + + // Include all of server + ...serverEntryPoints, + + // Include all of web + ...webEntryPoints, +].flat(); + +const bootstrapEntryPoints = [ + 'out-build/server-main.js', + 'out-build/server-cli.js', + 'out-build/bootstrap-fork.js' +]; + +function getNodeVersion() { + const npmrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.npmrc'), 'utf8'); + const nodeVersion = /^target="(.*)"$/m.exec(npmrc)![1]; + const internalNodeVersion = /^ms_build_id="(.*)"$/m.exec(npmrc)![1]; + return { nodeVersion, internalNodeVersion }; +} + +function getNodeChecksum(expectedName: string): string | undefined { + const nodeJsChecksums = fs.readFileSync(path.join(REPO_ROOT, 'build', 'checksums', 'nodejs.txt'), 'utf8'); + for (const line of nodeJsChecksums.split('\n')) { + const [checksum, name] = line.split(/\s+/); + if (name === expectedName) { + return checksum; + } + } + return undefined; +} + +function extractAlpinefromDocker(nodeVersion: string, platform: string, arch: string) { + const imageName = arch === 'arm64' ? 'arm64v8/node' : 'node'; + log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from docker image ${imageName}`); + const contents = cp.execSync(`docker run --rm ${imageName}:${nodeVersion}-alpine /bin/sh -c 'cat \`which node\`'`, { maxBuffer: 100 * 1024 * 1024, encoding: 'buffer' }); + // eslint-disable-next-line local/code-no-dangerous-type-assertions + return es.readArray([new File({ path: 'node', contents, stat: { mode: parseInt('755', 8) } as fs.Stats })]); +} + +const { nodeVersion, internalNodeVersion } = getNodeVersion(); + +BUILD_TARGETS.forEach(({ platform, arch }) => { + gulp.task(task.define(`node-${platform}-${arch}`, () => { + const nodePath = path.join('.build', 'node', `v${nodeVersion}`, `${platform}-${arch}`); + + if (!fs.existsSync(nodePath)) { + util.rimraf(nodePath); + + return nodejs(platform, arch)! + .pipe(vfs.dest(nodePath)); + } + + return Promise.resolve(null); + })); +}); + +const defaultNodeTask = gulp.task(`node-${process.platform}-${process.arch}`); + +if (defaultNodeTask) { + // eslint-disable-next-line local/code-no-any-casts + gulp.task(task.define('node', defaultNodeTask as any)); +} + +function nodejs(platform: string, arch: string): NodeJS.ReadWriteStream | undefined { + + if (arch === 'armhf') { + arch = 'armv7l'; + } else if (arch === 'alpine') { + platform = 'alpine'; + arch = 'x64'; + } + + log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from ${product.nodejsRepository}...`); + + const glibcPrefix = process.env['VSCODE_NODE_GLIBC'] ?? ''; + let expectedName: string | undefined; + switch (platform) { + case 'win32': + expectedName = product.nodejsRepository !== 'https://nodejs.org' ? + `win-${arch}-node.exe` : `win-${arch}/node.exe`; + break; + + case 'darwin': + expectedName = `node-v${nodeVersion}-${platform}-${arch}.tar.gz`; + break; + case 'linux': + expectedName = `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`; + break; + case 'alpine': + expectedName = `node-v${nodeVersion}-linux-${arch}-musl.tar.gz`; + break; + } + const checksumSha256 = expectedName ? getNodeChecksum(expectedName) : undefined; + + if (checksumSha256) { + log(`Using SHA256 checksum for checking integrity: ${checksumSha256}`); + } else { + log.warn(`Unable to verify integrity of downloaded node.js binary because no SHA256 checksum was found!`); + } + + switch (platform) { + case 'win32': + return (product.nodejsRepository !== 'https://nodejs.org' ? + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName!, checksumSha256 }) : + fetchUrls(`/dist/v${nodeVersion}/win-${arch}/node.exe`, { base: 'https://nodejs.org', checksumSha256 })) + .pipe(rename('node.exe')); + case 'darwin': + case 'linux': + return (product.nodejsRepository !== 'https://nodejs.org' ? + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName!, checksumSha256 }) : + fetchUrls(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', checksumSha256 }) + ).pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) + .pipe(filter('**/node')) + .pipe(util.setExecutableBit('**')) + .pipe(rename('node')); + case 'alpine': + return product.nodejsRepository !== 'https://nodejs.org' ? + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName!, checksumSha256 }) + .pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) + .pipe(filter('**/node')) + .pipe(util.setExecutableBit('**')) + .pipe(rename('node')) + : extractAlpinefromDocker(nodeVersion, platform, arch); + } +} + +function packageTask(type: string, platform: string, arch: string, sourceFolderName: string, destinationFolderName: string) { + const destination = path.join(BUILD_ROOT, destinationFolderName); + + return () => { + const src = gulp.src(sourceFolderName + '/**', { base: '.' }) + .pipe(rename(function (path) { path.dirname = path.dirname!.replace(new RegExp('^' + sourceFolderName), 'out'); })) + .pipe(util.setExecutableBit(['**/*.sh'])) + .pipe(filter(['**', '!**/*.{js,css}.map'])); + + const workspaceExtensionPoints = ['debuggers', 'jsonValidation']; + const isUIExtension = (manifest: { extensionKind?: string; main?: string; contributes?: Record }) => { + switch (manifest.extensionKind) { + case 'ui': return true; + case 'workspace': return false; + default: { + if (manifest.main) { + return false; + } + if (manifest.contributes && Object.keys(manifest.contributes).some(key => workspaceExtensionPoints.indexOf(key) !== -1)) { + return false; + } + // Default is UI Extension + return true; + } + } + }; + const localWorkspaceExtensions = glob.sync('extensions/*/package.json') + .filter((extensionPath) => { + if (type === 'reh-web') { + return true; // web: ship all extensions for now + } + + // Skip shipping UI extensions because the client side will have them anyways + // and they'd just increase the download without being used + const manifest = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, extensionPath)).toString()); + return !isUIExtension(manifest); + }).map((extensionPath) => path.basename(path.dirname(extensionPath))) + .filter(name => name !== 'vscode-api-tests' && name !== 'vscode-test-resolver'); // Do not ship the test extensions + const builtInExtensions: Array<{ name: string; platforms?: string[]; clientOnly?: boolean }> = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'product.json'), 'utf8')).builtInExtensions; + const marketplaceExtensions = builtInExtensions + .filter(entry => !entry.platforms || new Set(entry.platforms).has(platform)) + .filter(entry => !entry.clientOnly) + .map(entry => entry.name); + const extensionPaths = [...localWorkspaceExtensions, ...marketplaceExtensions] + .map(name => `.build/extensions/${name}/**`); + + const extensions = gulp.src(extensionPaths, { base: '.build', dot: true }); + const extensionsCommonDependencies = gulp.src('.build/extensions/node_modules/**', { base: '.build', dot: true }); + const sources = es.merge(src, extensions, extensionsCommonDependencies) + .pipe(filter(['**', '!**/*.{js,css}.map'], { dot: true })); + + let version = packageJson.version; + const quality = (product as typeof product & { quality?: string }).quality; + + if (quality && quality !== 'stable') { + version += '-' + quality; + } + + const name = product.nameShort; + + let packageJsonContents = ''; + const packageJsonStream = gulp.src(['remote/package.json'], { base: 'remote' }) + .pipe(jsonEditor({ name, version, dependencies: undefined, optionalDependencies: undefined, type: 'module' })) + .pipe(es.through(function (file) { + packageJsonContents = file.contents.toString(); + this.emit('data', file); + })); + + let productJsonContents = ''; + const productJsonStream = gulp.src(['product.json'], { base: '.' }) + .pipe(jsonEditor({ commit, date: readISODate('out-build'), version })) + .pipe(es.through(function (file) { + productJsonContents = file.contents.toString(); + this.emit('data', file); + })); + + const license = gulp.src(['remote/LICENSE'], { base: 'remote', allowEmpty: true }); + + const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path)); + + const productionDependencies = getProductionDependencies(REMOTE_FOLDER); + const dependenciesSrc = productionDependencies.map(d => path.relative(REPO_ROOT, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`]).flat(); + const deps = gulp.src(dependenciesSrc, { base: 'remote', dot: true }) + // filter out unnecessary files, no source maps in server build + .pipe(filter(['**', '!**/package-lock.json', '!**/*.{js,css}.map'])) + .pipe(util.cleanNodeModules(path.join(import.meta.dirname, '.moduleignore'))) + .pipe(util.cleanNodeModules(path.join(import.meta.dirname, `.moduleignore.${process.platform}`))) + .pipe(jsFilter) + .pipe(util.stripSourceMappingURL()) + .pipe(jsFilter.restore); + + const nodePath = `.build/node/v${nodeVersion}/${platform}-${arch}`; + const node = gulp.src(`${nodePath}/**`, { base: nodePath, dot: true }); + + let web: NodeJS.ReadWriteStream[] = []; + if (type === 'reh-web') { + web = [ + 'resources/server/favicon.ico', + 'resources/server/code-192.png', + 'resources/server/code-512.png', + 'resources/server/manifest.json' + ].map(resource => gulp.src(resource, { base: '.' }).pipe(rename(resource))); + } + + const all = es.merge( + packageJsonStream, + productJsonStream, + license, + sources, + deps, + node, + ...web + ); + + let result = all + .pipe(util.skipDirectories()) + .pipe(util.fixWin32DirectoryPermissions()); + + if (platform === 'win32') { + result = es.merge(result, + gulp.src('resources/server/bin/remote-cli/code.cmd', { base: '.' }) + .pipe(replace('@@VERSION@@', version)) + .pipe(replace('@@COMMIT@@', commit || '')) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename(`bin/remote-cli/${product.applicationName}.cmd`)), + gulp.src('resources/server/bin/helpers/browser.cmd', { base: '.' }) + .pipe(replace('@@VERSION@@', version)) + .pipe(replace('@@COMMIT@@', commit || '')) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename(`bin/helpers/browser.cmd`)), + gulp.src('resources/server/bin/code-server.cmd', { base: '.' }) + .pipe(rename(`bin/${product.serverApplicationName}.cmd`)), + ); + } else if (platform === 'linux' || platform === 'alpine' || platform === 'darwin') { + result = es.merge(result, + gulp.src(`resources/server/bin/remote-cli/${platform === 'darwin' ? 'code-darwin.sh' : 'code-linux.sh'}`, { base: '.' }) + .pipe(replace('@@VERSION@@', version)) + .pipe(replace('@@COMMIT@@', commit || '')) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename(`bin/remote-cli/${product.applicationName}`)) + .pipe(util.setExecutableBit()), + gulp.src(`resources/server/bin/helpers/${platform === 'darwin' ? 'browser-darwin.sh' : 'browser-linux.sh'}`, { base: '.' }) + .pipe(replace('@@VERSION@@', version)) + .pipe(replace('@@COMMIT@@', commit || '')) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename(`bin/helpers/browser.sh`)) + .pipe(util.setExecutableBit()), + gulp.src(`resources/server/bin/${platform === 'darwin' ? 'code-server-darwin.sh' : 'code-server-linux.sh'}`, { base: '.' }) + .pipe(rename(`bin/${product.serverApplicationName}`)) + .pipe(util.setExecutableBit()) + ); + } + + if (platform === 'linux' || platform === 'alpine') { + result = es.merge(result, + gulp.src(`resources/server/bin/helpers/check-requirements-linux.sh`, { base: '.' }) + .pipe(rename(`bin/helpers/check-requirements.sh`)) + .pipe(util.setExecutableBit()) + ); + } + + result = inlineMeta(result, { + targetPaths: bootstrapEntryPoints, + packageJsonFn: () => packageJsonContents, + productJsonFn: () => productJsonContents + }); + + return result.pipe(vfs.dest(destination)); + }; +} + +/** + * @param product The parsed product.json file contents + */ +function tweakProductForServerWeb(product: typeof import('../product.json')) { + const result: typeof product & { webEndpointUrlTemplate?: string } = { ...product }; + delete result.webEndpointUrlTemplate; + return result; +} + +['reh', 'reh-web'].forEach(type => { + const bundleTask = task.define(`bundle-vscode-${type}`, task.series( + util.rimraf(`out-vscode-${type}`), + optimize.bundleTask( + { + out: `out-vscode-${type}`, + esm: { + src: 'out-build', + entryPoints: [ + ...(type === 'reh' ? serverEntryPoints : serverWithWebEntryPoints), + ...bootstrapEntryPoints + ], + resources: type === 'reh' ? serverResources : serverWithWebResources, + fileContentMapper: createVSCodeWebFileContentMapper('.build/extensions', type === 'reh-web' ? tweakProductForServerWeb(product) : product) + } + } + ) + )); + + const minifyTask = task.define(`minify-vscode-${type}`, task.series( + bundleTask, + util.rimraf(`out-vscode-${type}-min`), + optimize.minifyTask(`out-vscode-${type}`, `https://main.vscode-cdn.net/sourcemaps/${commit}/core`) + )); + gulp.task(minifyTask); + + BUILD_TARGETS.forEach(buildTarget => { + const dashed = (str: string) => (str ? `-${str}` : ``); + const platform = buildTarget.platform; + const arch = buildTarget.arch; + + ['', 'min'].forEach(minified => { + const sourceFolderName = `out-vscode-${type}${dashed(minified)}`; + const destinationFolderName = `vscode-${type}${dashed(platform)}${dashed(arch)}`; + + const serverTaskCI = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series( + compileNativeExtensionsBuildTask, + gulp.task(`node-${platform}-${arch}`) as task.Task, + util.rimraf(path.join(BUILD_ROOT, destinationFolderName)), + packageTask(type, platform, arch, sourceFolderName, destinationFolderName) + )); + gulp.task(serverTaskCI); + + const serverTask = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( + compileBuildWithManglingTask, + cleanExtensionsBuildTask, + compileNonNativeExtensionsBuildTask, + compileExtensionMediaBuildTask, + minified ? minifyTask : bundleTask, + serverTaskCI + )); + gulp.task(serverTask); + }); + }); +}); diff --git a/build/gulpfile.scan.js b/build/gulpfile.scan.js deleted file mode 100644 index aafc64e81c2f4..0000000000000 --- a/build/gulpfile.scan.js +++ /dev/null @@ -1,129 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -const gulp = require('gulp'); -const path = require('path'); -const task = require('./lib/task'); -const util = require('./lib/util'); -const electron = require('@vscode/gulp-electron'); -const { config } = require('./lib/electron'); -const filter = require('gulp-filter'); -const deps = require('./lib/dependencies'); -const { existsSync, readdirSync } = require('fs'); - -const root = path.dirname(__dirname); - -const BUILD_TARGETS = [ - { platform: 'win32', arch: 'x64' }, - { platform: 'win32', arch: 'arm64' }, - { platform: 'darwin', arch: null, opts: { stats: true } }, - { platform: 'linux', arch: 'x64' }, - { platform: 'linux', arch: 'armhf' }, - { platform: 'linux', arch: 'arm64' }, -]; - -// The following files do not have PDBs downloaded for them during the download symbols process. -const excludedCheckList = ['d3dcompiler_47.dll']; - -BUILD_TARGETS.forEach(buildTarget => { - const dashed = (/** @type {string | null} */ str) => (str ? `-${str}` : ``); - const platform = buildTarget.platform; - const arch = buildTarget.arch; - - const destinationExe = path.join(path.dirname(root), 'scanbin', `VSCode${dashed(platform)}${dashed(arch)}`, 'bin'); - const destinationPdb = path.join(path.dirname(root), 'scanbin', `VSCode${dashed(platform)}${dashed(arch)}`, 'pdb'); - - const tasks = []; - - // removal tasks - tasks.push(util.rimraf(destinationExe), util.rimraf(destinationPdb)); - - // electron - tasks.push(() => electron.dest(destinationExe, { ...config, platform, arch: arch === 'armhf' ? 'arm' : arch })); - - // pdbs for windows - if (platform === 'win32') { - tasks.push( - () => electron.dest(destinationPdb, { ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, pdbs: true }), - () => confirmPdbsExist(destinationExe, destinationPdb) - ); - } - - if (platform === 'linux') { - tasks.push( - () => electron.dest(destinationPdb, { ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, symbols: true }) - ); - } - - // node modules - tasks.push( - nodeModules(destinationExe, destinationPdb, platform) - ); - - const setupSymbolsTask = task.define(`vscode-symbols${dashed(platform)}${dashed(arch)}`, - task.series(...tasks) - ); - - gulp.task(setupSymbolsTask); -}); - -function getProductionDependencySources() { - const productionDependencies = deps.getProductionDependencies(root); - return productionDependencies.map(d => path.relative(root, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat(); -} - -function nodeModules(destinationExe, destinationPdb, platform) { - - const exe = () => { - return gulp.src(getProductionDependencySources(), { base: '.', dot: true }) - .pipe(filter([ - '**/*.node', - // Exclude these paths. - // We don't build the prebuilt node files so we don't scan them - '!**/prebuilds/**/*.node' - ])) - .pipe(gulp.dest(destinationExe)); - }; - - if (platform === 'win32') { - const pdb = () => { - return gulp.src(getProductionDependencySources(), { base: '.', dot: true }) - .pipe(filter(['**/*.pdb'])) - .pipe(gulp.dest(destinationPdb)); - }; - - return gulp.parallel(exe, pdb); - } - - if (platform === 'linux') { - const pdb = () => { - return gulp.src(getProductionDependencySources(), { base: '.', dot: true }) - .pipe(filter(['**/*.sym'])) - .pipe(gulp.dest(destinationPdb)); - }; - - return gulp.parallel(exe, pdb); - } - - return exe; -} - -function confirmPdbsExist(destinationExe, destinationPdb) { - readdirSync(destinationExe).forEach(file => { - if (excludedCheckList.includes(file)) { - return; - } - - if (file.endsWith('.dll') || file.endsWith('.exe')) { - const pdb = `${file}.pdb`; - if (!existsSync(path.join(destinationPdb, pdb))) { - throw new Error(`Missing pdb file for ${file}. Tried searching for ${pdb} in ${destinationPdb}.`); - } - } - }); - return Promise.resolve(); -} diff --git a/build/gulpfile.scan.ts b/build/gulpfile.scan.ts new file mode 100644 index 0000000000000..19e50c016e64f --- /dev/null +++ b/build/gulpfile.scan.ts @@ -0,0 +1,131 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import gulp from 'gulp'; +import * as path from 'path'; +import * as task from './lib/task.ts'; +import * as util from './lib/util.ts'; +import electron from '@vscode/gulp-electron'; +import { config } from './lib/electron.ts'; +import filter from 'gulp-filter'; +import * as deps from './lib/dependencies.ts'; +import { existsSync, readdirSync } from 'fs'; + +const root = path.dirname(import.meta.dirname); + +const BUILD_TARGETS = [ + { platform: 'win32', arch: 'x64' }, + { platform: 'win32', arch: 'arm64' }, + { platform: 'darwin', arch: null, opts: { stats: true } }, + { platform: 'linux', arch: 'x64' }, + { platform: 'linux', arch: 'armhf' }, + { platform: 'linux', arch: 'arm64' }, +]; + +// The following files do not have PDBs downloaded for them during the download symbols process. +const excludedCheckList = [ + 'd3dcompiler_47.dll', + 'dxil.dll', + 'dxcompiler.dll', +]; + +BUILD_TARGETS.forEach(buildTarget => { + const dashed = (str: string | null) => (str ? `-${str}` : ``); + const platform = buildTarget.platform; + const arch = buildTarget.arch; + + const destinationExe = path.join(path.dirname(root), 'scanbin', `VSCode${dashed(platform)}${dashed(arch)}`, 'bin'); + const destinationPdb = path.join(path.dirname(root), 'scanbin', `VSCode${dashed(platform)}${dashed(arch)}`, 'pdb'); + + const tasks: task.Task[] = []; + + // removal tasks + tasks.push(util.rimraf(destinationExe), util.rimraf(destinationPdb)); + + // electron + tasks.push(() => electron.dest(destinationExe, { ...config, platform, arch: arch === 'armhf' ? 'arm' : arch })); + + // pdbs for windows + if (platform === 'win32') { + tasks.push( + () => electron.dest(destinationPdb, { ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, pdbs: true }), + () => confirmPdbsExist(destinationExe, destinationPdb) + ); + } + + if (platform === 'linux') { + tasks.push( + () => electron.dest(destinationPdb, { ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, symbols: true }) + ); + } + + // node modules + tasks.push( + nodeModules(destinationExe, destinationPdb, platform) + ); + + const setupSymbolsTask = task.define(`vscode-symbols${dashed(platform)}${dashed(arch)}`, + task.series(...tasks) + ); + + gulp.task(setupSymbolsTask); +}); + +function getProductionDependencySources() { + const productionDependencies = deps.getProductionDependencies(root); + return productionDependencies.map(d => path.relative(root, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat(); +} + +function nodeModules(destinationExe: string, destinationPdb: string, platform: string): task.CallbackTask { + + const exe = () => { + return gulp.src(getProductionDependencySources(), { base: '.', dot: true }) + .pipe(filter([ + '**/*.node', + // Exclude these paths. + // We don't build the prebuilt node files so we don't scan them + '!**/prebuilds/**/*.node' + ])) + .pipe(gulp.dest(destinationExe)); + }; + + if (platform === 'win32') { + const pdb = () => { + return gulp.src(getProductionDependencySources(), { base: '.', dot: true }) + .pipe(filter(['**/*.pdb'])) + .pipe(gulp.dest(destinationPdb)); + }; + + return gulp.parallel(exe, pdb) as task.CallbackTask; + } + + if (platform === 'linux') { + const pdb = () => { + return gulp.src(getProductionDependencySources(), { base: '.', dot: true }) + .pipe(filter(['**/*.sym'])) + .pipe(gulp.dest(destinationPdb)); + }; + + return gulp.parallel(exe, pdb) as task.CallbackTask; + } + + return exe; +} + +function confirmPdbsExist(destinationExe: string, destinationPdb: string) { + readdirSync(destinationExe).forEach(file => { + if (excludedCheckList.includes(file)) { + return; + } + + if (file.endsWith('.dll') || file.endsWith('.exe')) { + const pdb = `${file}.pdb`; + if (!existsSync(path.join(destinationPdb, pdb))) { + throw new Error(`Missing pdb file for ${file}. Tried searching for ${pdb} in ${destinationPdb}.`); + } + } + }); + return Promise.resolve(); +} diff --git a/build/gulpfile.ts b/build/gulpfile.ts new file mode 100644 index 0000000000000..e83b9a08d2808 --- /dev/null +++ b/build/gulpfile.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { EventEmitter } from 'events'; +import glob from 'glob'; +import gulp from 'gulp'; +import { createRequire } from 'node:module'; +import { monacoTypecheckTask /* , monacoTypecheckWatchTask */ } from './gulpfile.editor.ts'; +import { compileExtensionMediaTask, compileExtensionsTask, watchExtensionsTask } from './gulpfile.extensions.ts'; +import * as compilation from './lib/compilation.ts'; +import * as task from './lib/task.ts'; +import * as util from './lib/util.ts'; + +EventEmitter.defaultMaxListeners = 100; + +const require = createRequire(import.meta.url); + +const { transpileTask, compileTask, watchTask, compileApiProposalNamesTask, watchApiProposalNamesTask } = compilation; + +// API proposal names +gulp.task(compileApiProposalNamesTask); +gulp.task(watchApiProposalNamesTask); + +// SWC Client Transpile +const transpileClientSWCTask = task.define('transpile-client-esbuild', task.series(util.rimraf('out'), transpileTask('src', 'out', true))); +gulp.task(transpileClientSWCTask); + +// Transpile only +const transpileClientTask = task.define('transpile-client', task.series(util.rimraf('out'), transpileTask('src', 'out'))); +gulp.task(transpileClientTask); + +// Fast compile for development time +const compileClientTask = task.define('compile-client', task.series(util.rimraf('out'), compileApiProposalNamesTask, compileTask('src', 'out', false))); +gulp.task(compileClientTask); + +const watchClientTask = task.define('watch-client', task.series(util.rimraf('out'), task.parallel(watchTask('out', false), watchApiProposalNamesTask))); +gulp.task(watchClientTask); + +// All +const _compileTask = task.define('compile', task.parallel(monacoTypecheckTask, compileClientTask, compileExtensionsTask, compileExtensionMediaTask)); +gulp.task(_compileTask); + +gulp.task(task.define('watch', task.parallel(/* monacoTypecheckWatchTask, */ watchClientTask, watchExtensionsTask))); + +// Default +gulp.task('default', _compileTask); + +process.on('unhandledRejection', (reason, p) => { + console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); + process.exit(1); +}); + +// Load all the gulpfiles only if running tasks other than the editor tasks +glob.sync('gulpfile.*.ts', { cwd: import.meta.dirname }) + .forEach(f => { + return require(`./${f}`); + }); diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js deleted file mode 100644 index 25d891645172e..0000000000000 --- a/build/gulpfile.vscode.js +++ /dev/null @@ -1,575 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -const gulp = require('gulp'); -const fs = require('fs'); -const path = require('path'); -const es = require('event-stream'); -const vfs = require('vinyl-fs'); -const rename = require('gulp-rename'); -const replace = require('gulp-replace'); -const filter = require('gulp-filter'); -const util = require('./lib/util'); -const { getVersion } = require('./lib/getVersion'); -const { readISODate } = require('./lib/date'); -const task = require('./lib/task'); -const buildfile = require('./buildfile'); -const optimize = require('./lib/optimize'); -const { inlineMeta } = require('./lib/inlineMeta'); -const root = path.dirname(__dirname); -const commit = getVersion(root); -const packageJson = require('../package.json'); -const product = require('../product.json'); -const crypto = require('crypto'); -const i18n = require('./lib/i18n'); -const { getProductionDependencies } = require('./lib/dependencies'); -const { config } = require('./lib/electron'); -const createAsar = require('./lib/asar').createAsar; -const minimist = require('minimist'); -const { compileBuildWithoutManglingTask, compileBuildWithManglingTask } = require('./gulpfile.compile'); -const { compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileAllExtensionsBuildTask, compileExtensionMediaBuildTask, cleanExtensionsBuildTask } = require('./gulpfile.extensions'); -const { promisify } = require('util'); -const glob = promisify(require('glob')); -const rcedit = promisify(require('rcedit')); - -// Build -const vscodeEntryPoints = [ - buildfile.workerEditor, - buildfile.workerExtensionHost, - buildfile.workerNotebook, - buildfile.workerLanguageDetection, - buildfile.workerLocalFileSearch, - buildfile.workerProfileAnalysis, - buildfile.workerOutputLinks, - buildfile.workerBackgroundTokenization, - buildfile.workbenchDesktop, - buildfile.code -].flat(); - -const vscodeResourceIncludes = [ - - // NLS - 'out-build/nls.messages.json', - 'out-build/nls.keys.json', - - // Workbench - 'out-build/vs/code/electron-browser/workbench/workbench.html', - - // Electron Preload - 'out-build/vs/base/parts/sandbox/electron-browser/preload.js', - 'out-build/vs/base/parts/sandbox/electron-browser/preload-aux.js', - - // Node Scripts - 'out-build/vs/base/node/{terminateProcess.sh,cpuUsage.sh,ps.sh}', - - // Touchbar - 'out-build/vs/workbench/browser/parts/editor/media/*.png', - 'out-build/vs/workbench/contrib/debug/browser/media/*.png', - - // External Terminal - 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', - - // Terminal shell integration - 'out-build/vs/workbench/contrib/terminal/common/scripts/*.fish', - 'out-build/vs/workbench/contrib/terminal/common/scripts/*.ps1', - 'out-build/vs/workbench/contrib/terminal/common/scripts/*.psm1', - 'out-build/vs/workbench/contrib/terminal/common/scripts/*.sh', - 'out-build/vs/workbench/contrib/terminal/common/scripts/*.zsh', - - // Accessibility Signals - 'out-build/vs/platform/accessibilitySignal/browser/media/*.mp3', - - // Welcome - 'out-build/vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.{svg,png}', - - // Extensions - 'out-build/vs/workbench/contrib/extensions/browser/media/{theme-icon.png,language-icon.svg}', - 'out-build/vs/workbench/services/extensionManagement/common/media/*.{svg,png}', - - // Webview - 'out-build/vs/workbench/contrib/webview/browser/pre/*.{js,html}', - - // Extension Host Worker - 'out-build/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html', - - // Tree Sitter highlights - 'out-build/vs/editor/common/languages/highlights/*.scm', - - // Tree Sitter injection queries - 'out-build/vs/editor/common/languages/injections/*.scm' -]; - -const vscodeResources = [ - - // Includes - ...vscodeResourceIncludes, - - // Excludes - '!out-build/vs/code/browser/**', - '!out-build/vs/editor/standalone/**', - '!out-build/vs/code/**/*-dev.html', - '!out-build/vs/workbench/contrib/issue/**/*-dev.html', - '!**/test/**' -]; - -const bootstrapEntryPoints = [ - 'out-build/main.js', - 'out-build/cli.js', - 'out-build/bootstrap-fork.js' -]; - -const bundleVSCodeTask = task.define('bundle-vscode', task.series( - util.rimraf('out-vscode'), - // Optimize: bundles source files automatically based on - // import statements based on the passed in entry points. - // In addition, concat window related bootstrap files into - // a single file. - optimize.bundleTask( - { - out: 'out-vscode', - esm: { - src: 'out-build', - entryPoints: [ - ...vscodeEntryPoints, - ...bootstrapEntryPoints - ], - resources: vscodeResources, - skipTSBoilerplateRemoval: entryPoint => entryPoint === 'vs/code/electron-browser/workbench/workbench' - } - } - ) -)); -gulp.task(bundleVSCodeTask); - -const sourceMappingURLBase = `https://main.vscode-cdn.net/sourcemaps/${commit}`; -const minifyVSCodeTask = task.define('minify-vscode', task.series( - bundleVSCodeTask, - util.rimraf('out-vscode-min'), - optimize.minifyTask('out-vscode', `${sourceMappingURLBase}/core`) -)); -gulp.task(minifyVSCodeTask); - -const coreCI = task.define('core-ci', task.series( - gulp.task('compile-build-with-mangling'), - task.parallel( - gulp.task('minify-vscode'), - gulp.task('minify-vscode-reh'), - gulp.task('minify-vscode-reh-web'), - ) -)); -gulp.task(coreCI); - -const coreCIPR = task.define('core-ci-pr', task.series( - gulp.task('compile-build-without-mangling'), - task.parallel( - gulp.task('minify-vscode'), - gulp.task('minify-vscode-reh'), - gulp.task('minify-vscode-reh-web'), - ) -)); -gulp.task(coreCIPR); - -/** - * Compute checksums for some files. - * - * @param {string} out The out folder to read the file from. - * @param {string[]} filenames The paths to compute a checksum for. - * @return {Object} A map of paths to checksums. - */ -function computeChecksums(out, filenames) { - const result = {}; - filenames.forEach(function (filename) { - const fullPath = path.join(process.cwd(), out, filename); - result[filename] = computeChecksum(fullPath); - }); - return result; -} - -/** - * Compute checksum for a file. - * - * @param {string} filename The absolute path to a filename. - * @return {string} The checksum for `filename`. - */ -function computeChecksum(filename) { - const contents = fs.readFileSync(filename); - - const hash = crypto - .createHash('sha256') - .update(contents) - .digest('base64') - .replace(/=+$/, ''); - - return hash; -} - -function packageTask(platform, arch, sourceFolderName, destinationFolderName, opts) { - opts = opts || {}; - - const destination = path.join(path.dirname(root), destinationFolderName); - platform = platform || process.platform; - - const task = () => { - const electron = require('@vscode/gulp-electron'); - const json = require('gulp-json-editor'); - - const out = sourceFolderName; - - const checksums = computeChecksums(out, [ - 'vs/base/parts/sandbox/electron-browser/preload.js', - 'vs/workbench/workbench.desktop.main.js', - 'vs/workbench/workbench.desktop.main.css', - 'vs/workbench/api/node/extensionHostProcess.js', - 'vs/code/electron-browser/workbench/workbench.html', - 'vs/code/electron-browser/workbench/workbench.js' - ]); - - const src = gulp.src(out + '/**', { base: '.' }) - .pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + out), 'out'); })) - .pipe(util.setExecutableBit(['**/*.sh'])); - - const platformSpecificBuiltInExtensionsExclusions = product.builtInExtensions.filter(ext => { - if (!ext.platforms) { - return false; - } - - const set = new Set(ext.platforms); - return !set.has(platform); - }).map(ext => `!.build/extensions/${ext.name}/**`); - - const extensions = gulp.src(['.build/extensions/**', ...platformSpecificBuiltInExtensionsExclusions], { base: '.build', dot: true }); - - const sources = es.merge(src, extensions) - .pipe(filter(['**', '!**/*.{js,css}.map'], { dot: true })); - - let version = packageJson.version; - const quality = product.quality; - - if (quality && quality !== 'stable') { - version += '-' + quality; - } - - const name = product.nameShort; - const packageJsonUpdates = { name, version }; - - if (platform === 'linux') { - packageJsonUpdates.desktopName = `${product.applicationName}.desktop`; - } - - let packageJsonContents; - const packageJsonStream = gulp.src(['package.json'], { base: '.' }) - .pipe(json(packageJsonUpdates)) - .pipe(es.through(function (file) { - packageJsonContents = file.contents.toString(); - this.emit('data', file); - })); - - let productJsonContents; - const productJsonStream = gulp.src(['product.json'], { base: '.' }) - .pipe(json({ commit, date: readISODate('out-build'), checksums, version })) - .pipe(es.through(function (file) { - productJsonContents = file.contents.toString(); - this.emit('data', file); - })); - - const license = gulp.src([product.licenseFileName, 'ThirdPartyNotices.txt', 'licenses/**'], { base: '.', allowEmpty: true }); - - // TODO the API should be copied to `out` during compile, not here - const api = gulp.src('src/vscode-dts/vscode.d.ts').pipe(rename('out/vscode-dts/vscode.d.ts')); - - const telemetry = gulp.src('.build/telemetry/**', { base: '.build/telemetry', dot: true }); - - const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path)); - const root = path.resolve(path.join(__dirname, '..')); - const productionDependencies = getProductionDependencies(root); - const dependenciesSrc = productionDependencies.map(d => path.relative(root, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat().concat('!**/*.mk'); - - const deps = gulp.src(dependenciesSrc, { base: '.', dot: true }) - .pipe(filter(['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-87/**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.{js,css}.map'])) - .pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore'))) - .pipe(util.cleanNodeModules(path.join(__dirname, `.moduleignore.${process.platform}`))) - .pipe(jsFilter) - .pipe(util.rewriteSourceMappingURL(sourceMappingURLBase)) - .pipe(jsFilter.restore) - .pipe(createAsar(path.join(process.cwd(), 'node_modules'), [ - '**/*.node', - '**/@vscode/ripgrep/bin/*', - '**/node-pty/build/Release/*', - '**/node-pty/build/Release/conpty/*', - '**/node-pty/lib/worker/conoutSocketWorker.js', - '**/node-pty/lib/shared/conout.js', - '**/*.wasm', - '**/@vscode/vsce-sign/bin/*', - ], [ - '**/*.mk', - '!node_modules/vsda/**' // stay compatible with extensions that depend on us shipping `vsda` into ASAR - ], [ - 'node_modules/vsda/**' // retain copy of `vsda` in node_modules for internal use - ], 'node_modules.asar')); - - let all = es.merge( - packageJsonStream, - productJsonStream, - license, - api, - telemetry, - sources, - deps - ); - - if (platform === 'win32') { - all = es.merge(all, gulp.src([ - 'resources/win32/bower.ico', - 'resources/win32/c.ico', - 'resources/win32/config.ico', - 'resources/win32/cpp.ico', - 'resources/win32/csharp.ico', - 'resources/win32/css.ico', - 'resources/win32/default.ico', - 'resources/win32/go.ico', - 'resources/win32/html.ico', - 'resources/win32/jade.ico', - 'resources/win32/java.ico', - 'resources/win32/javascript.ico', - 'resources/win32/json.ico', - 'resources/win32/less.ico', - 'resources/win32/markdown.ico', - 'resources/win32/php.ico', - 'resources/win32/powershell.ico', - 'resources/win32/python.ico', - 'resources/win32/react.ico', - 'resources/win32/ruby.ico', - 'resources/win32/sass.ico', - 'resources/win32/shell.ico', - 'resources/win32/sql.ico', - 'resources/win32/typescript.ico', - 'resources/win32/vue.ico', - 'resources/win32/xml.ico', - 'resources/win32/yaml.ico', - 'resources/win32/code_70x70.png', - 'resources/win32/code_150x150.png' - ], { base: '.' })); - } else if (platform === 'linux') { - all = es.merge(all, gulp.src('resources/linux/code.png', { base: '.' })); - } else if (platform === 'darwin') { - const shortcut = gulp.src('resources/darwin/bin/code.sh') - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename('bin/code')); - const policyDest = gulp.src('.build/policies/darwin/**', { base: '.build/policies/darwin' }) - .pipe(rename(f => f.dirname = `policies/${f.dirname}`)); - all = es.merge(all, shortcut, policyDest); - } - - let result = all - .pipe(util.skipDirectories()) - .pipe(util.fixWin32DirectoryPermissions()) - .pipe(filter(['**', '!**/.github/**'], { dot: true })) // https://github.com/microsoft/vscode/issues/116523 - .pipe(electron({ ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: false })) - .pipe(filter(['**', '!LICENSE', '!version'], { dot: true })); - - if (platform === 'linux') { - result = es.merge(result, gulp.src('resources/completions/bash/code', { base: '.' }) - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename(function (f) { f.basename = product.applicationName; }))); - - result = es.merge(result, gulp.src('resources/completions/zsh/_code', { base: '.' }) - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename(function (f) { f.basename = '_' + product.applicationName; }))); - } - - if (platform === 'win32') { - result = es.merge(result, gulp.src('resources/win32/bin/code.js', { base: 'resources/win32', allowEmpty: true })); - - result = es.merge(result, gulp.src('resources/win32/bin/code.cmd', { base: 'resources/win32' }) - .pipe(replace('@@NAME@@', product.nameShort)) - .pipe(rename(function (f) { f.basename = product.applicationName; }))); - - result = es.merge(result, gulp.src('resources/win32/bin/code.sh', { base: 'resources/win32' }) - .pipe(replace('@@NAME@@', product.nameShort)) - .pipe(replace('@@PRODNAME@@', product.nameLong)) - .pipe(replace('@@VERSION@@', version)) - .pipe(replace('@@COMMIT@@', commit)) - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(replace('@@SERVERDATAFOLDER@@', product.serverDataFolderName || '.vscode-remote')) - .pipe(replace('@@QUALITY@@', quality)) - .pipe(rename(function (f) { f.basename = product.applicationName; f.extname = ''; }))); - - result = es.merge(result, gulp.src('resources/win32/VisualElementsManifest.xml', { base: 'resources/win32' }) - .pipe(rename(product.nameShort + '.VisualElementsManifest.xml'))); - - result = es.merge(result, gulp.src('.build/policies/win32/**', { base: '.build/policies/win32' }) - .pipe(rename(f => f.dirname = `policies/${f.dirname}`))); - - if (quality !== 'exploration') { - result = es.merge(result, gulp.src('.build/win32/appx/**', { base: '.build/win32' })); - const rawVersion = version.replace(/-\w+$/, '').split('.'); - const appxVersion = `${rawVersion[0]}.0.${rawVersion[1]}.${rawVersion[2]}`; - result = es.merge(result, gulp.src('resources/win32/appx/AppxManifest.xml', { base: '.' }) - .pipe(replace('@@AppxPackageName@@', product.win32AppUserModelId)) - .pipe(replace('@@AppxPackageVersion@@', appxVersion)) - .pipe(replace('@@AppxPackageDisplayName@@', product.nameLong)) - .pipe(replace('@@AppxPackageDescription@@', product.win32NameVersion)) - .pipe(replace('@@ApplicationIdShort@@', product.win32RegValueName)) - .pipe(replace('@@ApplicationExe@@', product.nameShort + '.exe')) - .pipe(replace('@@FileExplorerContextMenuID@@', quality === 'stable' ? 'OpenWithCode' : 'OpenWithCodeInsiders')) - .pipe(replace('@@FileExplorerContextMenuCLSID@@', product.win32ContextMenu[arch].clsid)) - .pipe(replace('@@FileExplorerContextMenuDLL@@', `${quality === 'stable' ? 'code' : 'code_insider'}_explorer_command_${arch}.dll`)) - .pipe(rename(f => f.dirname = `appx/manifest`))); - } - } else if (platform === 'linux') { - result = es.merge(result, gulp.src('resources/linux/bin/code.sh', { base: '.' }) - .pipe(replace('@@PRODNAME@@', product.nameLong)) - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename('bin/' + product.applicationName))); - } - - result = inlineMeta(result, { - targetPaths: bootstrapEntryPoints, - packageJsonFn: () => packageJsonContents, - productJsonFn: () => productJsonContents - }); - - return result.pipe(vfs.dest(destination)); - }; - task.taskName = `package-${platform}-${arch}`; - return task; -} - -function patchWin32DependenciesTask(destinationFolderName) { - const cwd = path.join(path.dirname(root), destinationFolderName); - - return async () => { - const deps = await glob('**/*.node', { cwd, ignore: 'extensions/node_modules/@parcel/watcher/**' }); - const packageJson = JSON.parse(await fs.promises.readFile(path.join(cwd, 'resources', 'app', 'package.json'), 'utf8')); - const product = JSON.parse(await fs.promises.readFile(path.join(cwd, 'resources', 'app', 'product.json'), 'utf8')); - const baseVersion = packageJson.version.replace(/-.*$/, ''); - - await Promise.all(deps.map(async dep => { - const basename = path.basename(dep); - - await rcedit(path.join(cwd, dep), { - 'file-version': baseVersion, - 'version-string': { - 'CompanyName': 'Microsoft Corporation', - 'FileDescription': product.nameLong, - 'FileVersion': packageJson.version, - 'InternalName': basename, - 'LegalCopyright': 'Copyright (C) 2022 Microsoft. All rights reserved', - 'OriginalFilename': basename, - 'ProductName': product.nameLong, - 'ProductVersion': packageJson.version, - } - }); - })); - }; -} - -const buildRoot = path.dirname(root); - -const BUILD_TARGETS = [ - { platform: 'win32', arch: 'x64' }, - { platform: 'win32', arch: 'arm64' }, - { platform: 'darwin', arch: 'x64', opts: { stats: true } }, - { platform: 'darwin', arch: 'arm64', opts: { stats: true } }, - { platform: 'linux', arch: 'x64' }, - { platform: 'linux', arch: 'armhf' }, - { platform: 'linux', arch: 'arm64' }, -]; -BUILD_TARGETS.forEach(buildTarget => { - const dashed = (str) => (str ? `-${str}` : ``); - const platform = buildTarget.platform; - const arch = buildTarget.arch; - const opts = buildTarget.opts; - - const [vscode, vscodeMin] = ['', 'min'].map(minified => { - const sourceFolderName = `out-vscode${dashed(minified)}`; - const destinationFolderName = `VSCode${dashed(platform)}${dashed(arch)}`; - - const tasks = [ - compileNativeExtensionsBuildTask, - util.rimraf(path.join(buildRoot, destinationFolderName)), - packageTask(platform, arch, sourceFolderName, destinationFolderName, opts) - ]; - - if (platform === 'win32') { - tasks.push(patchWin32DependenciesTask(destinationFolderName)); - } - - const vscodeTaskCI = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series(...tasks)); - gulp.task(vscodeTaskCI); - - const vscodeTask = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( - minified ? compileBuildWithManglingTask : compileBuildWithoutManglingTask, - cleanExtensionsBuildTask, - compileNonNativeExtensionsBuildTask, - compileExtensionMediaBuildTask, - minified ? minifyVSCodeTask : bundleVSCodeTask, - vscodeTaskCI - )); - gulp.task(vscodeTask); - - return vscodeTask; - }); - - if (process.platform === platform && process.arch === arch) { - gulp.task(task.define('vscode', task.series(vscode))); - gulp.task(task.define('vscode-min', task.series(vscodeMin))); - } -}); - -// #region nls - -const innoSetupConfig = { - 'zh-cn': { codePage: 'CP936', defaultInfo: { name: 'Simplified Chinese', id: '$0804', } }, - 'zh-tw': { codePage: 'CP950', defaultInfo: { name: 'Traditional Chinese', id: '$0404' } }, - 'ko': { codePage: 'CP949', defaultInfo: { name: 'Korean', id: '$0412' } }, - 'ja': { codePage: 'CP932' }, - 'de': { codePage: 'CP1252' }, - 'fr': { codePage: 'CP1252' }, - 'es': { codePage: 'CP1252' }, - 'ru': { codePage: 'CP1251' }, - 'it': { codePage: 'CP1252' }, - 'pt-br': { codePage: 'CP1252' }, - 'hu': { codePage: 'CP1250' }, - 'tr': { codePage: 'CP1254' } -}; - -gulp.task(task.define( - 'vscode-translations-export', - task.series( - coreCI, - compileAllExtensionsBuildTask, - function () { - const pathToMetadata = './out-build/nls.metadata.json'; - const pathToExtensions = '.build/extensions/*'; - const pathToSetup = 'build/win32/i18n/messages.en.isl'; - - return es.merge( - gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()), - gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()), - gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions()) - ).pipe(vfs.dest('../vscode-translations-export')); - } - ) -)); - -gulp.task('vscode-translations-import', function () { - const options = minimist(process.argv.slice(2), { - string: 'location', - default: { - location: '../vscode-translations-import' - } - }); - return es.merge([...i18n.defaultLanguages, ...i18n.extraLanguages].map(language => { - const id = language.id; - return gulp.src(`${options.location}/${id}/vscode-setup/messages.xlf`) - .pipe(i18n.prepareIslFiles(language, innoSetupConfig[language.id])) - .pipe(vfs.dest(`./build/win32/i18n`)); - })); -}); - -// #endregion diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js deleted file mode 100644 index 9cf6411e46af6..0000000000000 --- a/build/gulpfile.vscode.linux.js +++ /dev/null @@ -1,325 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -const gulp = require('gulp'); -const replace = require('gulp-replace'); -const rename = require('gulp-rename'); -const es = require('event-stream'); -const vfs = require('vinyl-fs'); -const { rimraf } = require('./lib/util'); -const { getVersion } = require('./lib/getVersion'); -const task = require('./lib/task'); -const packageJson = require('../package.json'); -const product = require('../product.json'); -const dependenciesGenerator = require('./linux/dependencies-generator'); -const debianRecommendedDependencies = require('./linux/debian/dep-lists').recommendedDeps; -const path = require('path'); -const cp = require('child_process'); -const util = require('util'); - -const exec = util.promisify(cp.exec); -const root = path.dirname(__dirname); -const commit = getVersion(root); - -const linuxPackageRevision = Math.floor(new Date().getTime() / 1000); - -/** - * @param {string} arch - */ -function getDebPackageArch(arch) { - return { x64: 'amd64', armhf: 'armhf', arm64: 'arm64' }[arch]; -} - -function prepareDebPackage(arch) { - const binaryDir = '../VSCode-linux-' + arch; - const debArch = getDebPackageArch(arch); - const destination = '.build/linux/deb/' + debArch + '/' + product.applicationName + '-' + debArch; - - return async function () { - const dependencies = await dependenciesGenerator.getDependencies('deb', binaryDir, product.applicationName, debArch); - - const desktop = gulp.src('resources/linux/code.desktop', { base: '.' }) - .pipe(rename('usr/share/applications/' + product.applicationName + '.desktop')); - - const desktopUrlHandler = gulp.src('resources/linux/code-url-handler.desktop', { base: '.' }) - .pipe(rename('usr/share/applications/' + product.applicationName + '-url-handler.desktop')); - - const desktops = es.merge(desktop, desktopUrlHandler) - .pipe(replace('@@NAME_LONG@@', product.nameLong)) - .pipe(replace('@@NAME_SHORT@@', product.nameShort)) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(replace('@@EXEC@@', `/usr/share/${product.applicationName}/${product.applicationName}`)) - .pipe(replace('@@ICON@@', product.linuxIconName)) - .pipe(replace('@@URLPROTOCOL@@', product.urlProtocol)); - - const appdata = gulp.src('resources/linux/code.appdata.xml', { base: '.' }) - .pipe(replace('@@NAME_LONG@@', product.nameLong)) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(replace('@@LICENSE@@', product.licenseName)) - .pipe(rename('usr/share/appdata/' + product.applicationName + '.appdata.xml')); - - const workspaceMime = gulp.src('resources/linux/code-workspace.xml', { base: '.' }) - .pipe(replace('@@NAME_LONG@@', product.nameLong)) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(rename('usr/share/mime/packages/' + product.applicationName + '-workspace.xml')); - - const icon = gulp.src('resources/linux/code.png', { base: '.' }) - .pipe(rename('usr/share/pixmaps/' + product.linuxIconName + '.png')); - - const bash_completion = gulp.src('resources/completions/bash/code') - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename('usr/share/bash-completion/completions/' + product.applicationName)); - - const zsh_completion = gulp.src('resources/completions/zsh/_code') - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename('usr/share/zsh/vendor-completions/_' + product.applicationName)); - - const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) - .pipe(rename(function (p) { p.dirname = 'usr/share/' + product.applicationName + '/' + p.dirname; })); - - let size = 0; - const control = code.pipe(es.through( - function (f) { size += f.isDirectory() ? 4096 : f.contents.length; }, - function () { - const that = this; - gulp.src('resources/linux/debian/control.template', { base: '.' }) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(replace('@@VERSION@@', packageJson.version + '-' + linuxPackageRevision)) - .pipe(replace('@@ARCHITECTURE@@', debArch)) - .pipe(replace('@@DEPENDS@@', dependencies.join(', '))) - .pipe(replace('@@RECOMMENDS@@', debianRecommendedDependencies.join(', '))) - .pipe(replace('@@INSTALLEDSIZE@@', Math.ceil(size / 1024))) - .pipe(rename('DEBIAN/control')) - .pipe(es.through(function (f) { that.emit('data', f); }, function () { that.emit('end'); })); - })); - - const prerm = gulp.src('resources/linux/debian/prerm.template', { base: '.' }) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(rename('DEBIAN/prerm')); - - const postrm = gulp.src('resources/linux/debian/postrm.template', { base: '.' }) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(rename('DEBIAN/postrm')); - - const postinst = gulp.src('resources/linux/debian/postinst.template', { base: '.' }) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(replace('@@ARCHITECTURE@@', debArch)) - .pipe(rename('DEBIAN/postinst')); - - const templates = gulp.src('resources/linux/debian/templates.template', { base: '.' }) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(rename('DEBIAN/templates')); - - const all = es.merge(control, templates, postinst, postrm, prerm, desktops, appdata, workspaceMime, icon, bash_completion, zsh_completion, code); - - return all.pipe(vfs.dest(destination)); - }; -} - -/** - * @param {string} arch - */ -function buildDebPackage(arch) { - const debArch = getDebPackageArch(arch); - const cwd = `.build/linux/deb/${debArch}`; - - return async () => { - await exec(`chmod 755 ${product.applicationName}-${debArch}/DEBIAN/postinst ${product.applicationName}-${debArch}/DEBIAN/prerm ${product.applicationName}-${debArch}/DEBIAN/postrm`, { cwd }); - await exec('mkdir -p deb', { cwd }); - await exec(`fakeroot dpkg-deb -Zxz -b ${product.applicationName}-${debArch} deb`, { cwd }); - }; -} - -/** - * @param {string} rpmArch - */ -function getRpmBuildPath(rpmArch) { - return '.build/linux/rpm/' + rpmArch + '/rpmbuild'; -} - -/** - * @param {string} arch - */ -function getRpmPackageArch(arch) { - return { x64: 'x86_64', armhf: 'armv7hl', arm64: 'aarch64' }[arch]; -} - -/** - * @param {string} arch - */ -function prepareRpmPackage(arch) { - const binaryDir = '../VSCode-linux-' + arch; - const rpmArch = getRpmPackageArch(arch); - const stripBinary = process.env['STRIP'] ?? '/usr/bin/strip'; - - return async function () { - const dependencies = await dependenciesGenerator.getDependencies('rpm', binaryDir, product.applicationName, rpmArch); - - const desktop = gulp.src('resources/linux/code.desktop', { base: '.' }) - .pipe(rename('BUILD/usr/share/applications/' + product.applicationName + '.desktop')); - - const desktopUrlHandler = gulp.src('resources/linux/code-url-handler.desktop', { base: '.' }) - .pipe(rename('BUILD/usr/share/applications/' + product.applicationName + '-url-handler.desktop')); - - const desktops = es.merge(desktop, desktopUrlHandler) - .pipe(replace('@@NAME_LONG@@', product.nameLong)) - .pipe(replace('@@NAME_SHORT@@', product.nameShort)) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(replace('@@EXEC@@', `/usr/share/${product.applicationName}/${product.applicationName}`)) - .pipe(replace('@@ICON@@', product.linuxIconName)) - .pipe(replace('@@URLPROTOCOL@@', product.urlProtocol)); - - const appdata = gulp.src('resources/linux/code.appdata.xml', { base: '.' }) - .pipe(replace('@@NAME_LONG@@', product.nameLong)) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(replace('@@LICENSE@@', product.licenseName)) - .pipe(rename('BUILD/usr/share/appdata/' + product.applicationName + '.appdata.xml')); - - const workspaceMime = gulp.src('resources/linux/code-workspace.xml', { base: '.' }) - .pipe(replace('@@NAME_LONG@@', product.nameLong)) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(rename('BUILD/usr/share/mime/packages/' + product.applicationName + '-workspace.xml')); - - const icon = gulp.src('resources/linux/code.png', { base: '.' }) - .pipe(rename('BUILD/usr/share/pixmaps/' + product.linuxIconName + '.png')); - - const bash_completion = gulp.src('resources/completions/bash/code') - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename('BUILD/usr/share/bash-completion/completions/' + product.applicationName)); - - const zsh_completion = gulp.src('resources/completions/zsh/_code') - .pipe(replace('@@APPNAME@@', product.applicationName)) - .pipe(rename('BUILD/usr/share/zsh/site-functions/_' + product.applicationName)); - - const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) - .pipe(rename(function (p) { p.dirname = 'BUILD/usr/share/' + product.applicationName + '/' + p.dirname; })); - - const spec = gulp.src('resources/linux/rpm/code.spec.template', { base: '.' }) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(replace('@@NAME_LONG@@', product.nameLong)) - .pipe(replace('@@ICON@@', product.linuxIconName)) - .pipe(replace('@@VERSION@@', packageJson.version)) - .pipe(replace('@@RELEASE@@', linuxPackageRevision)) - .pipe(replace('@@ARCHITECTURE@@', rpmArch)) - .pipe(replace('@@LICENSE@@', product.licenseName)) - .pipe(replace('@@QUALITY@@', product.quality || '@@QUALITY@@')) - .pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@')) - .pipe(replace('@@DEPENDENCIES@@', dependencies.join(', '))) - .pipe(replace('@@STRIP@@', stripBinary)) - .pipe(rename('SPECS/' + product.applicationName + '.spec')); - - const specIcon = gulp.src('resources/linux/rpm/code.xpm', { base: '.' }) - .pipe(rename('SOURCES/' + product.applicationName + '.xpm')); - - const all = es.merge(code, desktops, appdata, workspaceMime, icon, bash_completion, zsh_completion, spec, specIcon); - - return all.pipe(vfs.dest(getRpmBuildPath(rpmArch))); - }; -} - -/** - * @param {string} arch - */ -function buildRpmPackage(arch) { - const rpmArch = getRpmPackageArch(arch); - const rpmBuildPath = getRpmBuildPath(rpmArch); - const rpmOut = `${rpmBuildPath}/RPMS/${rpmArch}`; - const destination = `.build/linux/rpm/${rpmArch}`; - - return async () => { - await exec(`mkdir -p ${destination}`); - await exec(`HOME="$(pwd)/${destination}" rpmbuild -bb ${rpmBuildPath}/SPECS/${product.applicationName}.spec --target=${rpmArch}`); - await exec(`cp "${rpmOut}/$(ls ${rpmOut})" ${destination}/`); - }; -} - -/** - * @param {string} arch - */ -function getSnapBuildPath(arch) { - return `.build/linux/snap/${arch}/${product.applicationName}-${arch}`; -} - -/** - * @param {string} arch - */ -function prepareSnapPackage(arch) { - const binaryDir = '../VSCode-linux-' + arch; - const destination = getSnapBuildPath(arch); - - return function () { - // A desktop file that is placed in snap/gui will be placed into meta/gui verbatim. - const desktop = gulp.src('resources/linux/code.desktop', { base: '.' }) - .pipe(rename(`snap/gui/${product.applicationName}.desktop`)); - - // A desktop file that is placed in snap/gui will be placed into meta/gui verbatim. - const desktopUrlHandler = gulp.src('resources/linux/code-url-handler.desktop', { base: '.' }) - .pipe(rename(`snap/gui/${product.applicationName}-url-handler.desktop`)); - - const desktops = es.merge(desktop, desktopUrlHandler) - .pipe(replace('@@NAME_LONG@@', product.nameLong)) - .pipe(replace('@@NAME_SHORT@@', product.nameShort)) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(replace('@@EXEC@@', `${product.applicationName} --force-user-env`)) - .pipe(replace('@@ICON@@', `\${SNAP}/meta/gui/${product.linuxIconName}.png`)) - .pipe(replace('@@URLPROTOCOL@@', product.urlProtocol)); - - // An icon that is placed in snap/gui will be placed into meta/gui verbatim. - const icon = gulp.src('resources/linux/code.png', { base: '.' }) - .pipe(rename(`snap/gui/${product.linuxIconName}.png`)); - - const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) - .pipe(rename(function (p) { p.dirname = `usr/share/${product.applicationName}/${p.dirname}`; })); - - const snapcraft = gulp.src('resources/linux/snap/snapcraft.yaml', { base: '.' }) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(replace('@@VERSION@@', commit.substr(0, 8))) - // Possible run-on values https://snapcraft.io/docs/architectures - .pipe(replace('@@ARCHITECTURE@@', arch === 'x64' ? 'amd64' : arch)) - .pipe(rename('snap/snapcraft.yaml')); - - const electronLaunch = gulp.src('resources/linux/snap/electron-launch', { base: '.' }) - .pipe(rename('electron-launch')); - - const all = es.merge(desktops, icon, code, snapcraft, electronLaunch); - - return all.pipe(vfs.dest(destination)); - }; -} - -/** - * @param {string} arch - */ -function buildSnapPackage(arch) { - const cwd = getSnapBuildPath(arch); - return () => exec('snapcraft', { cwd }); -} - -const BUILD_TARGETS = [ - { arch: 'x64' }, - { arch: 'armhf' }, - { arch: 'arm64' }, -]; - -BUILD_TARGETS.forEach(({ arch }) => { - const debArch = getDebPackageArch(arch); - const prepareDebTask = task.define(`vscode-linux-${arch}-prepare-deb`, task.series(rimraf(`.build/linux/deb/${debArch}`), prepareDebPackage(arch))); - gulp.task(prepareDebTask); - const buildDebTask = task.define(`vscode-linux-${arch}-build-deb`, buildDebPackage(arch)); - gulp.task(buildDebTask); - - const rpmArch = getRpmPackageArch(arch); - const prepareRpmTask = task.define(`vscode-linux-${arch}-prepare-rpm`, task.series(rimraf(`.build/linux/rpm/${rpmArch}`), prepareRpmPackage(arch))); - gulp.task(prepareRpmTask); - const buildRpmTask = task.define(`vscode-linux-${arch}-build-rpm`, buildRpmPackage(arch)); - gulp.task(buildRpmTask); - - const prepareSnapTask = task.define(`vscode-linux-${arch}-prepare-snap`, task.series(rimraf(`.build/linux/snap/${arch}`), prepareSnapPackage(arch))); - gulp.task(prepareSnapTask); - const buildSnapTask = task.define(`vscode-linux-${arch}-build-snap`, task.series(prepareSnapTask, buildSnapPackage(arch))); - gulp.task(buildSnapTask); -}); diff --git a/build/gulpfile.vscode.linux.ts b/build/gulpfile.vscode.linux.ts new file mode 100644 index 0000000000000..c5d216319ce07 --- /dev/null +++ b/build/gulpfile.vscode.linux.ts @@ -0,0 +1,306 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import gulp from 'gulp'; +import replace from 'gulp-replace'; +import rename from 'gulp-rename'; +import es from 'event-stream'; +import vfs from 'vinyl-fs'; +import { rimraf } from './lib/util.ts'; +import { getVersion } from './lib/getVersion.ts'; +import * as task from './lib/task.ts'; +import packageJson from '../package.json' with { type: 'json' }; +import product from '../product.json' with { type: 'json' }; +import { getDependencies } from './linux/dependencies-generator.ts'; +import { recommendedDeps as debianRecommendedDependencies } from './linux/debian/dep-lists.ts'; +import * as path from 'path'; +import * as cp from 'child_process'; +import { promisify } from 'util'; + +const exec = promisify(cp.exec); +const root = path.dirname(import.meta.dirname); +const commit = getVersion(root); + +const linuxPackageRevision = Math.floor(new Date().getTime() / 1000); + +function getDebPackageArch(arch: string): string { + switch (arch) { + case 'x64': return 'amd64'; + case 'armhf': return 'armhf'; + case 'arm64': return 'arm64'; + default: throw new Error(`Unknown arch: ${arch}`); + } +} + +function prepareDebPackage(arch: string) { + const binaryDir = '../VSCode-linux-' + arch; + const debArch = getDebPackageArch(arch); + const destination = '.build/linux/deb/' + debArch + '/' + product.applicationName + '-' + debArch; + + return async function () { + const dependencies = await getDependencies('deb', binaryDir, product.applicationName, debArch); + + const desktop = gulp.src('resources/linux/code.desktop', { base: '.' }) + .pipe(rename('usr/share/applications/' + product.applicationName + '.desktop')); + + const desktopUrlHandler = gulp.src('resources/linux/code-url-handler.desktop', { base: '.' }) + .pipe(rename('usr/share/applications/' + product.applicationName + '-url-handler.desktop')); + + const desktops = es.merge(desktop, desktopUrlHandler) + .pipe(replace('@@NAME_LONG@@', product.nameLong)) + .pipe(replace('@@NAME_SHORT@@', product.nameShort)) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(replace('@@EXEC@@', `/usr/share/${product.applicationName}/${product.applicationName}`)) + .pipe(replace('@@ICON@@', product.linuxIconName)) + .pipe(replace('@@URLPROTOCOL@@', product.urlProtocol)); + + const appdata = gulp.src('resources/linux/code.appdata.xml', { base: '.' }) + .pipe(replace('@@NAME_LONG@@', product.nameLong)) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(replace('@@LICENSE@@', product.licenseName)) + .pipe(rename('usr/share/appdata/' + product.applicationName + '.appdata.xml')); + + const workspaceMime = gulp.src('resources/linux/code-workspace.xml', { base: '.' }) + .pipe(replace('@@NAME_LONG@@', product.nameLong)) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(rename('usr/share/mime/packages/' + product.applicationName + '-workspace.xml')); + + const icon = gulp.src('resources/linux/code.png', { base: '.' }) + .pipe(rename('usr/share/pixmaps/' + product.linuxIconName + '.png')); + + const bash_completion = gulp.src('resources/completions/bash/code') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('usr/share/bash-completion/completions/' + product.applicationName)); + + const zsh_completion = gulp.src('resources/completions/zsh/_code') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('usr/share/zsh/vendor-completions/_' + product.applicationName)); + + const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) + .pipe(rename(function (p) { p.dirname = 'usr/share/' + product.applicationName + '/' + p.dirname; })); + + let size = 0; + const control = code.pipe(es.through( + function (f) { size += f.isDirectory() ? 4096 : f.contents.length; }, + function () { + const that = this; + gulp.src('resources/linux/debian/control.template', { base: '.' }) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(replace('@@VERSION@@', packageJson.version + '-' + linuxPackageRevision)) + .pipe(replace('@@ARCHITECTURE@@', debArch)) + .pipe(replace('@@DEPENDS@@', dependencies.join(', '))) + .pipe(replace('@@RECOMMENDS@@', debianRecommendedDependencies.join(', '))) + .pipe(replace('@@INSTALLEDSIZE@@', Math.ceil(size / 1024).toString())) + .pipe(rename('DEBIAN/control')) + .pipe(es.through(function (f) { that.emit('data', f); }, function () { that.emit('end'); })); + })); + + const prerm = gulp.src('resources/linux/debian/prerm.template', { base: '.' }) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(rename('DEBIAN/prerm')); + + const postrm = gulp.src('resources/linux/debian/postrm.template', { base: '.' }) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(rename('DEBIAN/postrm')); + + const postinst = gulp.src('resources/linux/debian/postinst.template', { base: '.' }) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(replace('@@ARCHITECTURE@@', debArch)) + .pipe(rename('DEBIAN/postinst')); + + const templates = gulp.src('resources/linux/debian/templates.template', { base: '.' }) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(rename('DEBIAN/templates')); + + const all = es.merge(control, templates, postinst, postrm, prerm, desktops, appdata, workspaceMime, icon, bash_completion, zsh_completion, code); + + return all.pipe(vfs.dest(destination)); + }; +} + +function buildDebPackage(arch: string) { + const debArch = getDebPackageArch(arch); + const cwd = `.build/linux/deb/${debArch}`; + + return async () => { + await exec(`chmod 755 ${product.applicationName}-${debArch}/DEBIAN/postinst ${product.applicationName}-${debArch}/DEBIAN/prerm ${product.applicationName}-${debArch}/DEBIAN/postrm`, { cwd }); + await exec('mkdir -p deb', { cwd }); + await exec(`fakeroot dpkg-deb -Zxz -b ${product.applicationName}-${debArch} deb`, { cwd }); + }; +} + +function getRpmBuildPath(rpmArch: string): string { + return '.build/linux/rpm/' + rpmArch + '/rpmbuild'; +} + +function getRpmPackageArch(arch: string): string { + switch (arch) { + case 'x64': return 'x86_64'; + case 'armhf': return 'armv7hl'; + case 'arm64': return 'aarch64'; + default: throw new Error(`Unknown arch: ${arch}`); + } +} + +function prepareRpmPackage(arch: string) { + const binaryDir = '../VSCode-linux-' + arch; + const rpmArch = getRpmPackageArch(arch); + const stripBinary = process.env['STRIP'] ?? '/usr/bin/strip'; + + return async function () { + const dependencies = await getDependencies('rpm', binaryDir, product.applicationName, rpmArch); + + const desktop = gulp.src('resources/linux/code.desktop', { base: '.' }) + .pipe(rename('BUILD/usr/share/applications/' + product.applicationName + '.desktop')); + + const desktopUrlHandler = gulp.src('resources/linux/code-url-handler.desktop', { base: '.' }) + .pipe(rename('BUILD/usr/share/applications/' + product.applicationName + '-url-handler.desktop')); + + const desktops = es.merge(desktop, desktopUrlHandler) + .pipe(replace('@@NAME_LONG@@', product.nameLong)) + .pipe(replace('@@NAME_SHORT@@', product.nameShort)) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(replace('@@EXEC@@', `/usr/share/${product.applicationName}/${product.applicationName}`)) + .pipe(replace('@@ICON@@', product.linuxIconName)) + .pipe(replace('@@URLPROTOCOL@@', product.urlProtocol)); + + const appdata = gulp.src('resources/linux/code.appdata.xml', { base: '.' }) + .pipe(replace('@@NAME_LONG@@', product.nameLong)) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(replace('@@LICENSE@@', product.licenseName)) + .pipe(rename('BUILD/usr/share/appdata/' + product.applicationName + '.appdata.xml')); + + const workspaceMime = gulp.src('resources/linux/code-workspace.xml', { base: '.' }) + .pipe(replace('@@NAME_LONG@@', product.nameLong)) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(rename('BUILD/usr/share/mime/packages/' + product.applicationName + '-workspace.xml')); + + const icon = gulp.src('resources/linux/code.png', { base: '.' }) + .pipe(rename('BUILD/usr/share/pixmaps/' + product.linuxIconName + '.png')); + + const bash_completion = gulp.src('resources/completions/bash/code') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('BUILD/usr/share/bash-completion/completions/' + product.applicationName)); + + const zsh_completion = gulp.src('resources/completions/zsh/_code') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('BUILD/usr/share/zsh/site-functions/_' + product.applicationName)); + + const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) + .pipe(rename(function (p) { p.dirname = 'BUILD/usr/share/' + product.applicationName + '/' + p.dirname; })); + + const spec = gulp.src('resources/linux/rpm/code.spec.template', { base: '.' }) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(replace('@@NAME_LONG@@', product.nameLong)) + .pipe(replace('@@ICON@@', product.linuxIconName)) + .pipe(replace('@@VERSION@@', packageJson.version)) + .pipe(replace('@@RELEASE@@', linuxPackageRevision.toString())) + .pipe(replace('@@ARCHITECTURE@@', rpmArch)) + .pipe(replace('@@LICENSE@@', product.licenseName)) + .pipe(replace('@@QUALITY@@', (product as typeof product & { quality?: string }).quality || '@@QUALITY@@')) + .pipe(replace('@@UPDATEURL@@', (product as typeof product & { updateUrl?: string }).updateUrl || '@@UPDATEURL@@')) + .pipe(replace('@@DEPENDENCIES@@', dependencies.join(', '))) + .pipe(replace('@@STRIP@@', stripBinary)) + .pipe(rename('SPECS/' + product.applicationName + '.spec')); + + const specIcon = gulp.src('resources/linux/rpm/code.xpm', { base: '.' }) + .pipe(rename('SOURCES/' + product.applicationName + '.xpm')); + + const all = es.merge(code, desktops, appdata, workspaceMime, icon, bash_completion, zsh_completion, spec, specIcon); + + return all.pipe(vfs.dest(getRpmBuildPath(rpmArch))); + }; +} + +function buildRpmPackage(arch: string) { + const rpmArch = getRpmPackageArch(arch); + const rpmBuildPath = getRpmBuildPath(rpmArch); + const rpmOut = `${rpmBuildPath}/RPMS/${rpmArch}`; + const destination = `.build/linux/rpm/${rpmArch}`; + + return async () => { + await exec(`mkdir -p ${destination}`); + await exec(`HOME="$(pwd)/${destination}" rpmbuild -bb ${rpmBuildPath}/SPECS/${product.applicationName}.spec --target=${rpmArch}`); + await exec(`cp "${rpmOut}/$(ls ${rpmOut})" ${destination}/`); + }; +} + +function getSnapBuildPath(arch: string): string { + return `.build/linux/snap/${arch}/${product.applicationName}-${arch}`; +} + +function prepareSnapPackage(arch: string) { + const binaryDir = '../VSCode-linux-' + arch; + const destination = getSnapBuildPath(arch); + + return function () { + // A desktop file that is placed in snap/gui will be placed into meta/gui verbatim. + const desktop = gulp.src('resources/linux/code.desktop', { base: '.' }) + .pipe(rename(`snap/gui/${product.applicationName}.desktop`)); + + // A desktop file that is placed in snap/gui will be placed into meta/gui verbatim. + const desktopUrlHandler = gulp.src('resources/linux/code-url-handler.desktop', { base: '.' }) + .pipe(rename(`snap/gui/${product.applicationName}-url-handler.desktop`)); + + const desktops = es.merge(desktop, desktopUrlHandler) + .pipe(replace('@@NAME_LONG@@', product.nameLong)) + .pipe(replace('@@NAME_SHORT@@', product.nameShort)) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(replace('@@EXEC@@', `${product.applicationName} --force-user-env`)) + .pipe(replace('@@ICON@@', `\${SNAP}/meta/gui/${product.linuxIconName}.png`)) + .pipe(replace('@@URLPROTOCOL@@', product.urlProtocol)); + + // An icon that is placed in snap/gui will be placed into meta/gui verbatim. + const icon = gulp.src('resources/linux/code.png', { base: '.' }) + .pipe(rename(`snap/gui/${product.linuxIconName}.png`)); + + const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) + .pipe(rename(function (p) { p.dirname = `usr/share/${product.applicationName}/${p.dirname}`; })); + + const snapcraft = gulp.src('resources/linux/snap/snapcraft.yaml', { base: '.' }) + .pipe(replace('@@NAME@@', product.applicationName)) + .pipe(replace('@@VERSION@@', commit!.substr(0, 8))) + // Possible run-on values https://snapcraft.io/docs/architectures + .pipe(replace('@@ARCHITECTURE@@', arch === 'x64' ? 'amd64' : arch)) + .pipe(rename('snap/snapcraft.yaml')); + + const electronLaunch = gulp.src('resources/linux/snap/electron-launch', { base: '.' }) + .pipe(rename('electron-launch')); + + const all = es.merge(desktops, icon, code, snapcraft, electronLaunch); + + return all.pipe(vfs.dest(destination)); + }; +} + +function buildSnapPackage(arch: string) { + const cwd = getSnapBuildPath(arch); + return () => exec('snapcraft', { cwd }); +} + +const BUILD_TARGETS = [ + { arch: 'x64' }, + { arch: 'armhf' }, + { arch: 'arm64' }, +]; + +BUILD_TARGETS.forEach(({ arch }) => { + const debArch = getDebPackageArch(arch); + const prepareDebTask = task.define(`vscode-linux-${arch}-prepare-deb`, task.series(rimraf(`.build/linux/deb/${debArch}`), prepareDebPackage(arch))); + gulp.task(prepareDebTask); + const buildDebTask = task.define(`vscode-linux-${arch}-build-deb`, buildDebPackage(arch)); + gulp.task(buildDebTask); + + const rpmArch = getRpmPackageArch(arch); + const prepareRpmTask = task.define(`vscode-linux-${arch}-prepare-rpm`, task.series(rimraf(`.build/linux/rpm/${rpmArch}`), prepareRpmPackage(arch))); + gulp.task(prepareRpmTask); + const buildRpmTask = task.define(`vscode-linux-${arch}-build-rpm`, buildRpmPackage(arch)); + gulp.task(buildRpmTask); + + const prepareSnapTask = task.define(`vscode-linux-${arch}-prepare-snap`, task.series(rimraf(`.build/linux/snap/${arch}`), prepareSnapPackage(arch))); + gulp.task(prepareSnapTask); + const buildSnapTask = task.define(`vscode-linux-${arch}-build-snap`, task.series(prepareSnapTask, buildSnapPackage(arch))); + gulp.task(buildSnapTask); +}); diff --git a/build/gulpfile.vscode.ts b/build/gulpfile.vscode.ts new file mode 100644 index 0000000000000..ac70ecbd57f07 --- /dev/null +++ b/build/gulpfile.vscode.ts @@ -0,0 +1,602 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import gulp from 'gulp'; +import * as fs from 'fs'; +import * as path from 'path'; +import es from 'event-stream'; +import vfs from 'vinyl-fs'; +import rename from 'gulp-rename'; +import replace from 'gulp-replace'; +import filter from 'gulp-filter'; +import electron from '@vscode/gulp-electron'; +import jsonEditor from 'gulp-json-editor'; +import * as util from './lib/util.ts'; +import { getVersion } from './lib/getVersion.ts'; +import { readISODate } from './lib/date.ts'; +import * as task from './lib/task.ts'; +import buildfile from './buildfile.ts'; +import * as optimize from './lib/optimize.ts'; +import { inlineMeta } from './lib/inlineMeta.ts'; +import packageJson from '../package.json' with { type: 'json' }; +import product from '../product.json' with { type: 'json' }; +import * as crypto from 'crypto'; +import * as i18n from './lib/i18n.ts'; +import { getProductionDependencies } from './lib/dependencies.ts'; +import { config } from './lib/electron.ts'; +import { createAsar } from './lib/asar.ts'; +import minimist from 'minimist'; +import { compileBuildWithoutManglingTask, compileBuildWithManglingTask } from './gulpfile.compile.ts'; +import { compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileAllExtensionsBuildTask, compileExtensionMediaBuildTask, cleanExtensionsBuildTask } from './gulpfile.extensions.ts'; +import { promisify } from 'util'; +import globCallback from 'glob'; +import rceditCallback from 'rcedit'; + + +const glob = promisify(globCallback); +const rcedit = promisify(rceditCallback); +const root = path.dirname(import.meta.dirname); +const commit = getVersion(root); +const versionedResourcesFolder = (product as typeof product & { quality?: string })?.quality === 'insider' ? commit!.substring(0, 10) : ''; + +// Build +const vscodeEntryPoints = [ + buildfile.workerEditor, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.workerProfileAnalysis, + buildfile.workerOutputLinks, + buildfile.workerBackgroundTokenization, + buildfile.workbenchDesktop, + buildfile.code +].flat(); + +const vscodeResourceIncludes = [ + + // NLS + 'out-build/nls.messages.json', + 'out-build/nls.keys.json', + + // Workbench + 'out-build/vs/code/electron-browser/workbench/workbench.html', + + // Electron Preload + 'out-build/vs/base/parts/sandbox/electron-browser/preload.js', + 'out-build/vs/base/parts/sandbox/electron-browser/preload-aux.js', + + // Node Scripts + 'out-build/vs/base/node/{terminateProcess.sh,cpuUsage.sh,ps.sh}', + + // Touchbar + 'out-build/vs/workbench/browser/parts/editor/media/*.png', + 'out-build/vs/workbench/contrib/debug/browser/media/*.png', + + // External Terminal + 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', + + // Terminal shell integration + 'out-build/vs/workbench/contrib/terminal/common/scripts/*.fish', + 'out-build/vs/workbench/contrib/terminal/common/scripts/*.ps1', + 'out-build/vs/workbench/contrib/terminal/common/scripts/*.psm1', + 'out-build/vs/workbench/contrib/terminal/common/scripts/*.sh', + 'out-build/vs/workbench/contrib/terminal/common/scripts/*.zsh', + + // Accessibility Signals + 'out-build/vs/platform/accessibilitySignal/browser/media/*.mp3', + + // Welcome + 'out-build/vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.{svg,png}', + + // Extensions + 'out-build/vs/workbench/contrib/extensions/browser/media/{theme-icon.png,language-icon.svg}', + 'out-build/vs/workbench/services/extensionManagement/common/media/*.{svg,png}', + + // Webview + 'out-build/vs/workbench/contrib/webview/browser/pre/*.{js,html}', + + // Extension Host Worker + 'out-build/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html', + + // Tree Sitter highlights + 'out-build/vs/editor/common/languages/highlights/*.scm', + + // Tree Sitter injection queries + 'out-build/vs/editor/common/languages/injections/*.scm' +]; + +const vscodeResources = [ + + // Includes + ...vscodeResourceIncludes, + + // Excludes + '!out-build/vs/code/browser/**', + '!out-build/vs/editor/standalone/**', + '!out-build/vs/code/**/*-dev.html', + '!out-build/vs/workbench/contrib/issue/**/*-dev.html', + '!**/test/**' +]; + +const bootstrapEntryPoints = [ + 'out-build/main.js', + 'out-build/cli.js', + 'out-build/bootstrap-fork.js' +]; + +const bundleVSCodeTask = task.define('bundle-vscode', task.series( + util.rimraf('out-vscode'), + // Optimize: bundles source files automatically based on + // import statements based on the passed in entry points. + // In addition, concat window related bootstrap files into + // a single file. + optimize.bundleTask( + { + out: 'out-vscode', + esm: { + src: 'out-build', + entryPoints: [ + ...vscodeEntryPoints, + ...bootstrapEntryPoints + ], + resources: vscodeResources, + skipTSBoilerplateRemoval: entryPoint => entryPoint === 'vs/code/electron-browser/workbench/workbench' + } + } + ) +)); +gulp.task(bundleVSCodeTask); + +const sourceMappingURLBase = `https://main.vscode-cdn.net/sourcemaps/${commit}`; +const minifyVSCodeTask = task.define('minify-vscode', task.series( + bundleVSCodeTask, + util.rimraf('out-vscode-min'), + optimize.minifyTask('out-vscode', `${sourceMappingURLBase}/core`) +)); +gulp.task(minifyVSCodeTask); + +const coreCI = task.define('core-ci', task.series( + gulp.task('compile-build-with-mangling') as task.Task, + task.parallel( + gulp.task('minify-vscode') as task.Task, + gulp.task('minify-vscode-reh') as task.Task, + gulp.task('minify-vscode-reh-web') as task.Task, + ) +)); +gulp.task(coreCI); + +const coreCIPR = task.define('core-ci-pr', task.series( + gulp.task('compile-build-without-mangling') as task.Task, + task.parallel( + gulp.task('minify-vscode') as task.Task, + gulp.task('minify-vscode-reh') as task.Task, + gulp.task('minify-vscode-reh-web') as task.Task, + ) +)); +gulp.task(coreCIPR); + +/** + * Compute checksums for some files. + * + * @param out The out folder to read the file from. + * @param filenames The paths to compute a checksum for. + * @return A map of paths to checksums. + */ +function computeChecksums(out: string, filenames: string[]): Record { + const result: Record = {}; + filenames.forEach(function (filename) { + const fullPath = path.join(process.cwd(), out, filename); + result[filename] = computeChecksum(fullPath); + }); + return result; +} + +/** + * Compute checksums for a file. + * + * @param filename The absolute path to a filename. + * @return The checksum for `filename`. + */ +function computeChecksum(filename: string): string { + const contents = fs.readFileSync(filename); + + const hash = crypto + .createHash('sha256') + .update(contents) + .digest('base64') + .replace(/=+$/, ''); + + return hash; +} + +function packageTask(platform: string, arch: string, sourceFolderName: string, destinationFolderName: string, _opts?: { stats?: boolean }) { + const destination = path.join(path.dirname(root), destinationFolderName); + platform = platform || process.platform; + + const task = () => { + const out = sourceFolderName; + + const checksums = computeChecksums(out, [ + 'vs/base/parts/sandbox/electron-browser/preload.js', + 'vs/workbench/workbench.desktop.main.js', + 'vs/workbench/workbench.desktop.main.css', + 'vs/workbench/api/node/extensionHostProcess.js', + 'vs/code/electron-browser/workbench/workbench.html', + 'vs/code/electron-browser/workbench/workbench.js' + ]); + + const src = gulp.src(out + '/**', { base: '.' }) + .pipe(rename(function (path) { path.dirname = path.dirname!.replace(new RegExp('^' + out), 'out'); })) + .pipe(util.setExecutableBit(['**/*.sh'])); + + const platformSpecificBuiltInExtensionsExclusions = product.builtInExtensions.filter(ext => { + if (!(ext as { platforms?: string[] }).platforms) { + return false; + } + + const set = new Set((ext as { platforms?: string[] }).platforms); + return !set.has(platform); + }).map(ext => `!.build/extensions/${ext.name}/**`); + + const extensions = gulp.src(['.build/extensions/**', ...platformSpecificBuiltInExtensionsExclusions], { base: '.build', dot: true }); + + const sources = es.merge(src, extensions) + .pipe(filter(['**', '!**/*.{js,css}.map'], { dot: true })); + + let version = packageJson.version; + const quality = (product as { quality?: string }).quality; + + if (quality && quality !== 'stable') { + version += '-' + quality; + } + + const name = product.nameShort; + const packageJsonUpdates: Record = { name, version }; + + if (platform === 'linux') { + packageJsonUpdates.desktopName = `${product.applicationName}.desktop`; + } + + let packageJsonContents: string; + const packageJsonStream = gulp.src(['package.json'], { base: '.' }) + .pipe(jsonEditor(packageJsonUpdates)) + .pipe(es.through(function (file) { + packageJsonContents = file.contents.toString(); + this.emit('data', file); + })); + + let productJsonContents: string; + const productJsonStream = gulp.src(['product.json'], { base: '.' }) + .pipe(jsonEditor({ commit, date: readISODate('out-build'), checksums, version })) + .pipe(es.through(function (file) { + productJsonContents = file.contents.toString(); + this.emit('data', file); + })); + + const license = gulp.src([product.licenseFileName, 'ThirdPartyNotices.txt', 'licenses/**'], { base: '.', allowEmpty: true }); + + // TODO the API should be copied to `out` during compile, not here + const api = gulp.src('src/vscode-dts/vscode.d.ts').pipe(rename('out/vscode-dts/vscode.d.ts')); + + const telemetry = gulp.src('.build/telemetry/**', { base: '.build/telemetry', dot: true }); + + const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path)); + const root = path.resolve(path.join(import.meta.dirname, '..')); + const productionDependencies = getProductionDependencies(root); + const dependenciesSrc = productionDependencies.map(d => path.relative(root, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat().concat('!**/*.mk'); + + const deps = gulp.src(dependenciesSrc, { base: '.', dot: true }) + .pipe(filter(['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-87/**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.{js,css}.map'])) + .pipe(util.cleanNodeModules(path.join(import.meta.dirname, '.moduleignore'))) + .pipe(util.cleanNodeModules(path.join(import.meta.dirname, `.moduleignore.${process.platform}`))) + .pipe(jsFilter) + .pipe(util.rewriteSourceMappingURL(sourceMappingURLBase)) + .pipe(jsFilter.restore) + .pipe(createAsar(path.join(process.cwd(), 'node_modules'), [ + '**/*.node', + '**/@vscode/ripgrep/bin/*', + '**/node-pty/build/Release/*', + '**/node-pty/build/Release/conpty/*', + '**/node-pty/lib/worker/conoutSocketWorker.js', + '**/node-pty/lib/shared/conout.js', + '**/*.wasm', + '**/@vscode/vsce-sign/bin/*', + ], [ + '**/*.mk', + '!node_modules/vsda/**' // stay compatible with extensions that depend on us shipping `vsda` into ASAR + ], [ + 'node_modules/vsda/**' // retain copy of `vsda` in node_modules for internal use + ], 'node_modules.asar')); + + let all = es.merge( + packageJsonStream, + productJsonStream, + license, + api, + telemetry, + sources, + deps + ); + + let customElectronConfig = {}; + if (platform === 'win32') { + all = es.merge(all, gulp.src([ + 'resources/win32/bower.ico', + 'resources/win32/c.ico', + 'resources/win32/config.ico', + 'resources/win32/cpp.ico', + 'resources/win32/csharp.ico', + 'resources/win32/css.ico', + 'resources/win32/default.ico', + 'resources/win32/go.ico', + 'resources/win32/html.ico', + 'resources/win32/jade.ico', + 'resources/win32/java.ico', + 'resources/win32/javascript.ico', + 'resources/win32/json.ico', + 'resources/win32/less.ico', + 'resources/win32/markdown.ico', + 'resources/win32/php.ico', + 'resources/win32/powershell.ico', + 'resources/win32/python.ico', + 'resources/win32/react.ico', + 'resources/win32/ruby.ico', + 'resources/win32/sass.ico', + 'resources/win32/shell.ico', + 'resources/win32/sql.ico', + 'resources/win32/typescript.ico', + 'resources/win32/vue.ico', + 'resources/win32/xml.ico', + 'resources/win32/yaml.ico', + 'resources/win32/code_70x70.png', + 'resources/win32/code_150x150.png' + ], { base: '.' })); + if (quality && quality === 'insider') { + customElectronConfig = { + createVersionedResources: true, + productVersionString: `${versionedResourcesFolder}`, + }; + } + } else if (platform === 'linux') { + const policyDest = gulp.src('.build/policies/linux/**', { base: '.build/policies/linux' }) + .pipe(rename(f => f.dirname = `policies/${f.dirname}`)); + all = es.merge(all, gulp.src('resources/linux/code.png', { base: '.' }), policyDest); + } else if (platform === 'darwin') { + const shortcut = gulp.src('resources/darwin/bin/code.sh') + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('bin/code')); + const policyDest = gulp.src('.build/policies/darwin/**', { base: '.build/policies/darwin' }) + .pipe(rename(f => f.dirname = `policies/${f.dirname}`)); + all = es.merge(all, shortcut, policyDest); + } + + let result: NodeJS.ReadWriteStream = all + .pipe(util.skipDirectories()) + .pipe(util.fixWin32DirectoryPermissions()) + .pipe(filter(['**', '!**/.github/**'], { dot: true })) // https://github.com/microsoft/vscode/issues/116523 + .pipe(electron({ ...config, platform, arch: arch === 'armhf' ? 'arm' : arch, ffmpegChromium: false, ...customElectronConfig })) + .pipe(filter(['**', '!LICENSE', '!version'], { dot: true })); + + if (platform === 'linux') { + result = es.merge(result, gulp.src('resources/completions/bash/code', { base: '.' }) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename(function (f) { f.basename = product.applicationName; }))); + + result = es.merge(result, gulp.src('resources/completions/zsh/_code', { base: '.' }) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename(function (f) { f.basename = '_' + product.applicationName; }))); + } + + if (platform === 'win32') { + result = es.merge(result, gulp.src('resources/win32/bin/code.js', { base: 'resources/win32', allowEmpty: true })); + + if (quality && quality === 'insider') { + result = es.merge(result, gulp.src('resources/win32/insider/bin/code.cmd', { base: 'resources/win32/insider' }) + .pipe(replace('@@NAME@@', product.nameShort)) + .pipe(replace('@@VERSIONFOLDER@@', versionedResourcesFolder)) + .pipe(rename(function (f) { f.basename = product.applicationName; }))); + + result = es.merge(result, gulp.src('resources/win32/insider/bin/code.sh', { base: 'resources/win32/insider' }) + .pipe(replace('@@NAME@@', product.nameShort)) + .pipe(replace('@@PRODNAME@@', product.nameLong)) + .pipe(replace('@@VERSION@@', version)) + .pipe(replace('@@COMMIT@@', String(commit))) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(replace('@@VERSIONFOLDER@@', versionedResourcesFolder)) + .pipe(replace('@@SERVERDATAFOLDER@@', product.serverDataFolderName || '.vscode-remote')) + .pipe(replace('@@QUALITY@@', quality)) + .pipe(rename(function (f) { f.basename = product.applicationName; f.extname = ''; }))); + } else { + result = es.merge(result, gulp.src('resources/win32/bin/code.cmd', { base: 'resources/win32' }) + .pipe(replace('@@NAME@@', product.nameShort)) + .pipe(rename(function (f) { f.basename = product.applicationName; }))); + + result = es.merge(result, gulp.src('resources/win32/bin/code.sh', { base: 'resources/win32' }) + .pipe(replace('@@NAME@@', product.nameShort)) + .pipe(replace('@@PRODNAME@@', product.nameLong)) + .pipe(replace('@@VERSION@@', version)) + .pipe(replace('@@COMMIT@@', String(commit))) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(replace('@@SERVERDATAFOLDER@@', product.serverDataFolderName || '.vscode-remote')) + .pipe(replace('@@QUALITY@@', String(quality))) + .pipe(rename(function (f) { f.basename = product.applicationName; f.extname = ''; }))); + } + + result = es.merge(result, gulp.src('resources/win32/VisualElementsManifest.xml', { base: 'resources/win32' }) + .pipe(rename(product.nameShort + '.VisualElementsManifest.xml'))); + + result = es.merge(result, gulp.src('.build/policies/win32/**', { base: '.build/policies/win32' }) + .pipe(rename(f => f.dirname = `policies/${f.dirname}`))); + + if (quality === 'stable' || quality === 'insider') { + result = es.merge(result, gulp.src('.build/win32/appx/**', { base: '.build/win32' })); + const rawVersion = version.replace(/-\w+$/, '').split('.'); + const appxVersion = `${rawVersion[0]}.0.${rawVersion[1]}.${rawVersion[2]}`; + result = es.merge(result, gulp.src('resources/win32/appx/AppxManifest.xml', { base: '.' }) + .pipe(replace('@@AppxPackageName@@', product.win32AppUserModelId)) + .pipe(replace('@@AppxPackageVersion@@', appxVersion)) + .pipe(replace('@@AppxPackageDisplayName@@', product.nameLong)) + .pipe(replace('@@AppxPackageDescription@@', product.win32NameVersion)) + .pipe(replace('@@ApplicationIdShort@@', product.win32RegValueName)) + .pipe(replace('@@ApplicationExe@@', product.nameShort + '.exe')) + .pipe(replace('@@FileExplorerContextMenuID@@', quality === 'stable' ? 'OpenWithCode' : 'OpenWithCodeInsiders')) + .pipe(replace('@@FileExplorerContextMenuCLSID@@', (product as { win32ContextMenu?: Record }).win32ContextMenu![arch].clsid)) + .pipe(replace('@@FileExplorerContextMenuDLL@@', `${quality === 'stable' ? 'code' : 'code_insider'}_explorer_command_${arch}.dll`)) + .pipe(rename(f => f.dirname = `appx/manifest`))); + } + } else if (platform === 'linux') { + result = es.merge(result, gulp.src('resources/linux/bin/code.sh', { base: '.' }) + .pipe(replace('@@PRODNAME@@', product.nameLong)) + .pipe(replace('@@APPNAME@@', product.applicationName)) + .pipe(rename('bin/' + product.applicationName))); + } + + result = inlineMeta(result, { + targetPaths: bootstrapEntryPoints, + packageJsonFn: () => packageJsonContents, + productJsonFn: () => productJsonContents + }); + + return result.pipe(vfs.dest(destination)); + }; + task.taskName = `package-${platform}-${arch}`; + return task; +} + +function patchWin32DependenciesTask(destinationFolderName: string) { + const cwd = path.join(path.dirname(root), destinationFolderName); + + return async () => { + const deps = await glob('**/*.node', { cwd, ignore: 'extensions/node_modules/@vscode/watcher/**' }); + const packageJson = JSON.parse(await fs.promises.readFile(path.join(cwd, versionedResourcesFolder, 'resources', 'app', 'package.json'), 'utf8')); + const product = JSON.parse(await fs.promises.readFile(path.join(cwd, versionedResourcesFolder, 'resources', 'app', 'product.json'), 'utf8')); + const baseVersion = packageJson.version.replace(/-.*$/, ''); + + await Promise.all(deps.map(async dep => { + const basename = path.basename(dep); + + await rcedit(path.join(cwd, dep), { + 'file-version': baseVersion, + 'version-string': { + 'CompanyName': 'Microsoft Corporation', + 'FileDescription': product.nameLong, + 'FileVersion': packageJson.version, + 'InternalName': basename, + 'LegalCopyright': 'Copyright (C) 2022 Microsoft. All rights reserved', + 'OriginalFilename': basename, + 'ProductName': product.nameLong, + 'ProductVersion': packageJson.version, + } + }); + })); + }; +} + +const buildRoot = path.dirname(root); + +const BUILD_TARGETS = [ + { platform: 'win32', arch: 'x64' }, + { platform: 'win32', arch: 'arm64' }, + { platform: 'darwin', arch: 'x64', opts: { stats: true } }, + { platform: 'darwin', arch: 'arm64', opts: { stats: true } }, + { platform: 'linux', arch: 'x64' }, + { platform: 'linux', arch: 'armhf' }, + { platform: 'linux', arch: 'arm64' }, +]; +BUILD_TARGETS.forEach(buildTarget => { + const dashed = (str: string) => (str ? `-${str}` : ``); + const platform = buildTarget.platform; + const arch = buildTarget.arch; + const opts = buildTarget.opts; + + const [vscode, vscodeMin] = ['', 'min'].map(minified => { + const sourceFolderName = `out-vscode${dashed(minified)}`; + const destinationFolderName = `VSCode${dashed(platform)}${dashed(arch)}`; + + const tasks = [ + compileNativeExtensionsBuildTask, + util.rimraf(path.join(buildRoot, destinationFolderName)), + packageTask(platform, arch, sourceFolderName, destinationFolderName, opts) + ]; + + if (platform === 'win32') { + tasks.push(patchWin32DependenciesTask(destinationFolderName)); + } + + const vscodeTaskCI = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series(...tasks)); + gulp.task(vscodeTaskCI); + + const vscodeTask = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( + minified ? compileBuildWithManglingTask : compileBuildWithoutManglingTask, + cleanExtensionsBuildTask, + compileNonNativeExtensionsBuildTask, + compileExtensionMediaBuildTask, + minified ? minifyVSCodeTask : bundleVSCodeTask, + vscodeTaskCI + )); + gulp.task(vscodeTask); + + return vscodeTask; + }); + + if (process.platform === platform && process.arch === arch) { + gulp.task(task.define('vscode', task.series(vscode))); + gulp.task(task.define('vscode-min', task.series(vscodeMin))); + } +}); + +// #region nls + +const innoSetupConfig: Record = { + 'zh-cn': { codePage: 'CP936', defaultInfo: { name: 'Simplified Chinese', id: '$0804', } }, + 'zh-tw': { codePage: 'CP950', defaultInfo: { name: 'Traditional Chinese', id: '$0404' } }, + 'ko': { codePage: 'CP949', defaultInfo: { name: 'Korean', id: '$0412' } }, + 'ja': { codePage: 'CP932' }, + 'de': { codePage: 'CP1252' }, + 'fr': { codePage: 'CP1252' }, + 'es': { codePage: 'CP1252' }, + 'ru': { codePage: 'CP1251' }, + 'it': { codePage: 'CP1252' }, + 'pt-br': { codePage: 'CP1252' }, + 'hu': { codePage: 'CP1250' }, + 'tr': { codePage: 'CP1254' } +}; + +gulp.task(task.define( + 'vscode-translations-export', + task.series( + coreCI, + compileAllExtensionsBuildTask, + function () { + const pathToMetadata = './out-build/nls.metadata.json'; + const pathToExtensions = '.build/extensions/*'; + const pathToSetup = 'build/win32/i18n/messages.en.isl'; + + return es.merge( + gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()), + gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()), + gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions()) + ).pipe(vfs.dest('../vscode-translations-export')); + } + ) +)); + +gulp.task('vscode-translations-import', function () { + const options = minimist(process.argv.slice(2), { + string: 'location', + default: { + location: '../vscode-translations-import' + } + }); + return es.merge([...i18n.defaultLanguages, ...i18n.extraLanguages].map(language => { + const id = language.id; + return gulp.src(`${options.location}/${id}/vscode-setup/messages.xlf`) + .pipe(i18n.prepareIslFiles(language, innoSetupConfig[language.id])) + .pipe(vfs.dest(`./build/win32/i18n`)); + })); +}); + +// #endregion diff --git a/build/gulpfile.vscode.web.js b/build/gulpfile.vscode.web.js deleted file mode 100644 index 08dfcfd0cd915..0000000000000 --- a/build/gulpfile.vscode.web.js +++ /dev/null @@ -1,233 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -const gulp = require('gulp'); -const path = require('path'); -const es = require('event-stream'); -const util = require('./lib/util'); -const { getVersion } = require('./lib/getVersion'); -const task = require('./lib/task'); -const optimize = require('./lib/optimize'); -const { readISODate } = require('./lib/date'); -const product = require('../product.json'); -const rename = require('gulp-rename'); -const filter = require('gulp-filter'); -const { getProductionDependencies } = require('./lib/dependencies'); -const vfs = require('vinyl-fs'); -const packageJson = require('../package.json'); -const { compileBuildWithManglingTask } = require('./gulpfile.compile'); -const extensions = require('./lib/extensions'); -const VinylFile = require('vinyl'); - -const REPO_ROOT = path.dirname(__dirname); -const BUILD_ROOT = path.dirname(REPO_ROOT); -const WEB_FOLDER = path.join(REPO_ROOT, 'remote', 'web'); - -const commit = getVersion(REPO_ROOT); -const quality = product.quality; -const version = (quality && quality !== 'stable') ? `${packageJson.version}-${quality}` : packageJson.version; - -const vscodeWebResourceIncludes = [ - - // NLS - 'out-build/nls.messages.js', - - // Accessibility Signals - 'out-build/vs/platform/accessibilitySignal/browser/media/*.mp3', - - // Welcome - 'out-build/vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.{svg,png}', - - // Extensions - 'out-build/vs/workbench/contrib/extensions/browser/media/{theme-icon.png,language-icon.svg}', - 'out-build/vs/workbench/services/extensionManagement/common/media/*.{svg,png}', - - // Webview - 'out-build/vs/workbench/contrib/webview/browser/pre/*.{js,html}', - - // Tree Sitter highlights - 'out-build/vs/editor/common/languages/highlights/*.scm', - - // Tree Sitter injections - 'out-build/vs/editor/common/languages/injections/*.scm', - - // Extension Host Worker - 'out-build/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html' -]; -exports.vscodeWebResourceIncludes = vscodeWebResourceIncludes; - -const vscodeWebResources = [ - - // Includes - ...vscodeWebResourceIncludes, - - // Excludes - '!out-build/vs/**/{node,electron-browser,electron-main,electron-utility}/**', - '!out-build/vs/editor/standalone/**', - '!out-build/vs/workbench/**/*-tb.png', - '!out-build/vs/code/**/*-dev.html', - '!**/test/**' -]; - -const buildfile = require('./buildfile'); - -const vscodeWebEntryPoints = [ - buildfile.workerEditor, - buildfile.workerExtensionHost, - buildfile.workerNotebook, - buildfile.workerLanguageDetection, - buildfile.workerLocalFileSearch, - buildfile.workerOutputLinks, - buildfile.workerBackgroundTokenization, - buildfile.keyboardMaps, - buildfile.workbenchWeb, - buildfile.entrypoint('vs/workbench/workbench.web.main.internal') // TODO@esm remove line when we stop supporting web-amd-esm-bridge -].flat(); - -/** - * @param extensionsRoot {string} The location where extension will be read from - * @param {object} product The parsed product.json file contents - */ -const createVSCodeWebFileContentMapper = (extensionsRoot, product) => { - return path => { - if (path.endsWith('vs/platform/product/common/product.js')) { - return content => { - const productConfiguration = JSON.stringify({ - ...product, - version, - commit, - date: readISODate('out-build') - }); - return content.replace('/*BUILD->INSERT_PRODUCT_CONFIGURATION*/', () => productConfiguration.substr(1, productConfiguration.length - 2) /* without { and }*/); - }; - } else if (path.endsWith('vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.js')) { - return content => { - const builtinExtensions = JSON.stringify(extensions.scanBuiltinExtensions(extensionsRoot)); - return content.replace('/*BUILD->INSERT_BUILTIN_EXTENSIONS*/', () => builtinExtensions.substr(1, builtinExtensions.length - 2) /* without [ and ]*/); - }; - } - - return undefined; - }; -}; -exports.createVSCodeWebFileContentMapper = createVSCodeWebFileContentMapper; - -const bundleVSCodeWebTask = task.define('bundle-vscode-web', task.series( - util.rimraf('out-vscode-web'), - optimize.bundleTask( - { - out: 'out-vscode-web', - esm: { - src: 'out-build', - entryPoints: vscodeWebEntryPoints, - resources: vscodeWebResources, - fileContentMapper: createVSCodeWebFileContentMapper('.build/web/extensions', product) - } - } - ) -)); - -const minifyVSCodeWebTask = task.define('minify-vscode-web', task.series( - bundleVSCodeWebTask, - util.rimraf('out-vscode-web-min'), - optimize.minifyTask('out-vscode-web', `https://main.vscode-cdn.net/sourcemaps/${commit}/core`) -)); -gulp.task(minifyVSCodeWebTask); - -function packageTask(sourceFolderName, destinationFolderName) { - const destination = path.join(BUILD_ROOT, destinationFolderName); - - return () => { - const json = require('gulp-json-editor'); - - const src = gulp.src(sourceFolderName + '/**', { base: '.' }) - .pipe(rename(function (path) { path.dirname = path.dirname.replace(new RegExp('^' + sourceFolderName), 'out'); })); - - const extensions = gulp.src('.build/web/extensions/**', { base: '.build/web', dot: true }); - - const loader = gulp.src('build/loader.min', { base: 'build', dot: true }).pipe(rename('out/vs/loader.js')); // TODO@esm remove line when we stop supporting web-amd-esm-bridge - - const sources = es.merge(src, extensions, loader) - .pipe(filter(['**', '!**/*.{js,css}.map'], { dot: true })) - // TODO@esm remove me once we stop supporting our web-esm-bridge - .pipe(es.through(function (file) { - if (file.relative === 'out/vs/workbench/workbench.web.main.internal.css') { - this.emit('data', new VinylFile({ - contents: file.contents, - path: file.path.replace('workbench.web.main.internal.css', 'workbench.web.main.css'), - base: file.base - })); - } - this.emit('data', file); - })); - - const name = product.nameShort; - const packageJsonStream = gulp.src(['remote/web/package.json'], { base: 'remote/web' }) - .pipe(json({ name, version, type: 'module' })); - - const license = gulp.src(['remote/LICENSE'], { base: 'remote', allowEmpty: true }); - - const productionDependencies = getProductionDependencies(WEB_FOLDER); - const dependenciesSrc = productionDependencies.map(d => path.relative(REPO_ROOT, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`]).flat(); - - const deps = gulp.src(dependenciesSrc, { base: 'remote/web', dot: true }) - .pipe(filter(['**', '!**/package-lock.json'])) - .pipe(util.cleanNodeModules(path.join(__dirname, '.webignore'))); - - const favicon = gulp.src('resources/server/favicon.ico', { base: 'resources/server' }); - const manifest = gulp.src('resources/server/manifest.json', { base: 'resources/server' }); - const pwaicons = es.merge( - gulp.src('resources/server/code-192.png', { base: 'resources/server' }), - gulp.src('resources/server/code-512.png', { base: 'resources/server' }) - ); - - const all = es.merge( - packageJsonStream, - license, - sources, - deps, - favicon, - manifest, - pwaicons - ); - - const result = all - .pipe(util.skipDirectories()) - .pipe(util.fixWin32DirectoryPermissions()); - - return result.pipe(vfs.dest(destination)); - }; -} - -const compileWebExtensionsBuildTask = task.define('compile-web-extensions-build', task.series( - task.define('clean-web-extensions-build', util.rimraf('.build/web/extensions')), - task.define('bundle-web-extensions-build', () => extensions.packageAllLocalExtensionsStream(true, false).pipe(gulp.dest('.build/web'))), - task.define('bundle-marketplace-web-extensions-build', () => extensions.packageMarketplaceExtensionsStream(true).pipe(gulp.dest('.build/web'))), - task.define('bundle-web-extension-media-build', () => extensions.buildExtensionMedia(false, '.build/web/extensions')), -)); -gulp.task(compileWebExtensionsBuildTask); - -const dashed = (/** @type {string} */ str) => (str ? `-${str}` : ``); - -['', 'min'].forEach(minified => { - const sourceFolderName = `out-vscode-web${dashed(minified)}`; - const destinationFolderName = `vscode-web`; - - const vscodeWebTaskCI = task.define(`vscode-web${dashed(minified)}-ci`, task.series( - compileWebExtensionsBuildTask, - minified ? minifyVSCodeWebTask : bundleVSCodeWebTask, - util.rimraf(path.join(BUILD_ROOT, destinationFolderName)), - packageTask(sourceFolderName, destinationFolderName) - )); - gulp.task(vscodeWebTaskCI); - - const vscodeWebTask = task.define(`vscode-web${dashed(minified)}`, task.series( - compileBuildWithManglingTask, - vscodeWebTaskCI - )); - gulp.task(vscodeWebTask); -}); diff --git a/build/gulpfile.vscode.web.ts b/build/gulpfile.vscode.web.ts new file mode 100644 index 0000000000000..3f1cc1fdc51f0 --- /dev/null +++ b/build/gulpfile.vscode.web.ts @@ -0,0 +1,212 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import gulp from 'gulp'; +import * as path from 'path'; +import es from 'event-stream'; +import * as util from './lib/util.ts'; +import { getVersion } from './lib/getVersion.ts'; +import * as task from './lib/task.ts'; +import * as optimize from './lib/optimize.ts'; +import { readISODate } from './lib/date.ts'; +import product from '../product.json' with { type: 'json' }; +import rename from 'gulp-rename'; +import filter from 'gulp-filter'; +import { getProductionDependencies } from './lib/dependencies.ts'; +import vfs from 'vinyl-fs'; +import packageJson from '../package.json' with { type: 'json' }; +import { compileBuildWithManglingTask } from './gulpfile.compile.ts'; +import * as extensions from './lib/extensions.ts'; +import jsonEditor from 'gulp-json-editor'; +import buildfile from './buildfile.ts'; + +const REPO_ROOT = path.dirname(import.meta.dirname); +const BUILD_ROOT = path.dirname(REPO_ROOT); +const WEB_FOLDER = path.join(REPO_ROOT, 'remote', 'web'); + +const commit = getVersion(REPO_ROOT); +const quality = (product as { quality?: string }).quality; +const version = (quality && quality !== 'stable') ? `${packageJson.version}-${quality}` : packageJson.version; + +export const vscodeWebResourceIncludes = [ + + // NLS + 'out-build/nls.messages.js', + + // Accessibility Signals + 'out-build/vs/platform/accessibilitySignal/browser/media/*.mp3', + + // Welcome + 'out-build/vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.{svg,png}', + + // Extensions + 'out-build/vs/workbench/contrib/extensions/browser/media/{theme-icon.png,language-icon.svg}', + 'out-build/vs/workbench/services/extensionManagement/common/media/*.{svg,png}', + + // Webview + 'out-build/vs/workbench/contrib/webview/browser/pre/*.{js,html}', + + // Tree Sitter highlights + 'out-build/vs/editor/common/languages/highlights/*.scm', + + // Tree Sitter injections + 'out-build/vs/editor/common/languages/injections/*.scm', + + // Extension Host Worker + 'out-build/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html' +]; + +const vscodeWebResources = [ + + // Includes + ...vscodeWebResourceIncludes, + + // Excludes + '!out-build/vs/**/{node,electron-browser,electron-main,electron-utility}/**', + '!out-build/vs/editor/standalone/**', + '!out-build/vs/workbench/**/*-tb.png', + '!out-build/vs/code/**/*-dev.html', + '!**/test/**' +]; + +const vscodeWebEntryPoints = [ + buildfile.workerEditor, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.workerOutputLinks, + buildfile.workerBackgroundTokenization, + buildfile.keyboardMaps, + buildfile.workbenchWeb, +].flat(); + +/** + * @param extensionsRoot The location where extension will be read from + * @param product The parsed product.json file contents + */ +export const createVSCodeWebFileContentMapper = (extensionsRoot: string, product: typeof import('../product.json')) => { + return (path: string): ((content: string) => string) | undefined => { + if (path.endsWith('vs/platform/product/common/product.js')) { + return content => { + const productConfiguration = JSON.stringify({ + ...product, + version, + commit, + date: readISODate('out-build') + }); + return content.replace('/*BUILD->INSERT_PRODUCT_CONFIGURATION*/', () => productConfiguration.substr(1, productConfiguration.length - 2) /* without { and }*/); + }; + } else if (path.endsWith('vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.js')) { + return content => { + const builtinExtensions = JSON.stringify(extensions.scanBuiltinExtensions(extensionsRoot)); + return content.replace('/*BUILD->INSERT_BUILTIN_EXTENSIONS*/', () => builtinExtensions.substr(1, builtinExtensions.length - 2) /* without [ and ]*/); + }; + } + + return undefined; + }; +}; + +const bundleVSCodeWebTask = task.define('bundle-vscode-web', task.series( + util.rimraf('out-vscode-web'), + optimize.bundleTask( + { + out: 'out-vscode-web', + esm: { + src: 'out-build', + entryPoints: vscodeWebEntryPoints, + resources: vscodeWebResources, + fileContentMapper: createVSCodeWebFileContentMapper('.build/web/extensions', product) + } + } + ) +)); + +const minifyVSCodeWebTask = task.define('minify-vscode-web', task.series( + bundleVSCodeWebTask, + util.rimraf('out-vscode-web-min'), + optimize.minifyTask('out-vscode-web', `https://main.vscode-cdn.net/sourcemaps/${commit}/core`) +)); +gulp.task(minifyVSCodeWebTask); + +function packageTask(sourceFolderName: string, destinationFolderName: string) { + const destination = path.join(BUILD_ROOT, destinationFolderName); + + return () => { + const src = gulp.src(sourceFolderName + '/**', { base: '.' }) + .pipe(rename(function (path) { path.dirname = path.dirname!.replace(new RegExp('^' + sourceFolderName), 'out'); })); + + const extensions = gulp.src('.build/web/extensions/**', { base: '.build/web', dot: true }); + + const sources = es.merge(src, extensions) + .pipe(filter(['**', '!**/*.{js,css}.map'], { dot: true })); + + const name = product.nameShort; + const packageJsonStream = gulp.src(['remote/web/package.json'], { base: 'remote/web' }) + .pipe(jsonEditor({ name, version, type: 'module' })); + + const license = gulp.src(['remote/LICENSE'], { base: 'remote', allowEmpty: true }); + + const productionDependencies = getProductionDependencies(WEB_FOLDER); + const dependenciesSrc = productionDependencies.map(d => path.relative(REPO_ROOT, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`]).flat(); + + const deps = gulp.src(dependenciesSrc, { base: 'remote/web', dot: true }) + .pipe(filter(['**', '!**/package-lock.json'])) + .pipe(util.cleanNodeModules(path.join(import.meta.dirname, '.webignore'))); + + const favicon = gulp.src('resources/server/favicon.ico', { base: 'resources/server' }); + const manifest = gulp.src('resources/server/manifest.json', { base: 'resources/server' }); + const pwaicons = es.merge( + gulp.src('resources/server/code-192.png', { base: 'resources/server' }), + gulp.src('resources/server/code-512.png', { base: 'resources/server' }) + ); + + const all = es.merge( + packageJsonStream, + license, + sources, + deps, + favicon, + manifest, + pwaicons + ); + + const result = all + .pipe(util.skipDirectories()) + .pipe(util.fixWin32DirectoryPermissions()); + + return result.pipe(vfs.dest(destination)); + }; +} + +const compileWebExtensionsBuildTask = task.define('compile-web-extensions-build', task.series( + task.define('clean-web-extensions-build', util.rimraf('.build/web/extensions')), + task.define('bundle-web-extensions-build', () => extensions.packageAllLocalExtensionsStream(true, false).pipe(gulp.dest('.build/web'))), + task.define('bundle-marketplace-web-extensions-build', () => extensions.packageMarketplaceExtensionsStream(true).pipe(gulp.dest('.build/web'))), + task.define('bundle-web-extension-media-build', () => extensions.buildExtensionMedia(false, '.build/web/extensions')), +)); +gulp.task(compileWebExtensionsBuildTask); + +const dashed = (str: string) => (str ? `-${str}` : ``); + +['', 'min'].forEach(minified => { + const sourceFolderName = `out-vscode-web${dashed(minified)}`; + const destinationFolderName = `vscode-web`; + + const vscodeWebTaskCI = task.define(`vscode-web${dashed(minified)}-ci`, task.series( + compileWebExtensionsBuildTask, + minified ? minifyVSCodeWebTask : bundleVSCodeWebTask, + util.rimraf(path.join(BUILD_ROOT, destinationFolderName)), + packageTask(sourceFolderName, destinationFolderName) + )); + gulp.task(vscodeWebTaskCI); + + const vscodeWebTask = task.define(`vscode-web${dashed(minified)}`, task.series( + compileBuildWithManglingTask, + vscodeWebTaskCI + )); + gulp.task(vscodeWebTask); +}); diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js deleted file mode 100644 index 0c4dc5226cc87..0000000000000 --- a/build/gulpfile.vscode.win32.js +++ /dev/null @@ -1,160 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -const gulp = require('gulp'); -const path = require('path'); -const fs = require('fs'); -const assert = require('assert'); -const cp = require('child_process'); -const util = require('./lib/util'); -const task = require('./lib/task'); -const pkg = require('../package.json'); -const product = require('../product.json'); -const vfs = require('vinyl-fs'); -const rcedit = require('rcedit'); - -const repoPath = path.dirname(__dirname); -const buildPath = (/** @type {string} */ arch) => path.join(path.dirname(repoPath), `VSCode-win32-${arch}`); -const setupDir = (/** @type {string} */ arch, /** @type {string} */ target) => path.join(repoPath, '.build', `win32-${arch}`, `${target}-setup`); -const issPath = path.join(__dirname, 'win32', 'code.iss'); -const innoSetupPath = path.join(path.dirname(path.dirname(require.resolve('innosetup'))), 'bin', 'ISCC.exe'); -const signWin32Path = path.join(repoPath, 'build', 'azure-pipelines', 'common', 'sign-win32'); - -function packageInnoSetup(iss, options, cb) { - options = options || {}; - - const definitions = options.definitions || {}; - - if (process.argv.some(arg => arg === '--debug-inno')) { - definitions['Debug'] = 'true'; - } - - if (process.argv.some(arg => arg === '--sign')) { - definitions['Sign'] = 'true'; - } - - const keys = Object.keys(definitions); - - keys.forEach(key => assert(typeof definitions[key] === 'string', `Missing value for '${key}' in Inno Setup package step`)); - - const defs = keys.map(key => `/d${key}=${definitions[key]}`); - const args = [ - iss, - ...defs, - `/sesrp=node ${signWin32Path} $f` - ]; - - cp.spawn(innoSetupPath, args, { stdio: ['ignore', 'inherit', 'inherit'] }) - .on('error', cb) - .on('exit', code => { - if (code === 0) { - cb(null); - } else { - cb(new Error(`InnoSetup returned exit code: ${code}`)); - } - }); -} - -/** - * @param {string} arch - * @param {string} target - */ -function buildWin32Setup(arch, target) { - if (target !== 'system' && target !== 'user') { - throw new Error('Invalid setup target'); - } - - return cb => { - const x64AppId = target === 'system' ? product.win32x64AppId : product.win32x64UserAppId; - const arm64AppId = target === 'system' ? product.win32arm64AppId : product.win32arm64UserAppId; - - const sourcePath = buildPath(arch); - const outputPath = setupDir(arch, target); - fs.mkdirSync(outputPath, { recursive: true }); - - const originalProductJsonPath = path.join(sourcePath, 'resources/app/product.json'); - const productJsonPath = path.join(outputPath, 'product.json'); - const productJson = JSON.parse(fs.readFileSync(originalProductJsonPath, 'utf8')); - productJson['target'] = target; - fs.writeFileSync(productJsonPath, JSON.stringify(productJson, undefined, '\t')); - - const quality = product.quality || 'dev'; - const definitions = { - NameLong: product.nameLong, - NameShort: product.nameShort, - DirName: product.win32DirName, - Version: pkg.version, - RawVersion: pkg.version.replace(/-\w+$/, ''), - NameVersion: product.win32NameVersion + (target === 'user' ? ' (User)' : ''), - ExeBasename: product.nameShort, - RegValueName: product.win32RegValueName, - ShellNameShort: product.win32ShellNameShort, - AppMutex: product.win32MutexName, - TunnelMutex: product.win32TunnelMutex, - TunnelServiceMutex: product.win32TunnelServiceMutex, - TunnelApplicationName: product.tunnelApplicationName, - ApplicationName: product.applicationName, - Arch: arch, - AppId: { 'x64': x64AppId, 'arm64': arm64AppId }[arch], - IncompatibleTargetAppId: { 'x64': product.win32x64AppId, 'arm64': product.win32arm64AppId }[arch], - AppUserId: product.win32AppUserModelId, - ArchitecturesAllowed: { 'x64': 'x64', 'arm64': 'arm64' }[arch], - ArchitecturesInstallIn64BitMode: { 'x64': 'x64', 'arm64': 'arm64' }[arch], - SourceDir: sourcePath, - RepoDir: repoPath, - OutputDir: outputPath, - InstallTarget: target, - ProductJsonPath: productJsonPath, - Quality: quality - }; - - if (quality !== 'exploration') { - definitions['AppxPackage'] = `${quality === 'stable' ? 'code' : 'code_insider'}_${arch}.appx`; - definitions['AppxPackageDll'] = `${quality === 'stable' ? 'code' : 'code_insider'}_explorer_command_${arch}.dll`; - definitions['AppxPackageName'] = `${product.win32AppUserModelId}`; - } - - packageInnoSetup(issPath, { definitions }, cb); - }; -} - -/** - * @param {string} arch - * @param {string} target - */ -function defineWin32SetupTasks(arch, target) { - const cleanTask = util.rimraf(setupDir(arch, target)); - gulp.task(task.define(`vscode-win32-${arch}-${target}-setup`, task.series(cleanTask, buildWin32Setup(arch, target)))); -} - -defineWin32SetupTasks('x64', 'system'); -defineWin32SetupTasks('arm64', 'system'); -defineWin32SetupTasks('x64', 'user'); -defineWin32SetupTasks('arm64', 'user'); - -/** - * @param {string} arch - */ -function copyInnoUpdater(arch) { - return () => { - return gulp.src('build/win32/{inno_updater.exe,vcruntime140.dll}', { base: 'build/win32' }) - .pipe(vfs.dest(path.join(buildPath(arch), 'tools'))); - }; -} - -/** - * @param {string} executablePath - */ -function updateIcon(executablePath) { - return cb => { - const icon = path.join(repoPath, 'resources', 'win32', 'code.ico'); - rcedit(executablePath, { icon }, cb); - }; -} - -gulp.task(task.define('vscode-win32-x64-inno-updater', task.series(copyInnoUpdater('x64'), updateIcon(path.join(buildPath('x64'), 'tools', 'inno_updater.exe'))))); -gulp.task(task.define('vscode-win32-arm64-inno-updater', task.series(copyInnoUpdater('arm64'), updateIcon(path.join(buildPath('arm64'), 'tools', 'inno_updater.exe'))))); diff --git a/build/gulpfile.vscode.win32.ts b/build/gulpfile.vscode.win32.ts new file mode 100644 index 0000000000000..a7b01f0a371a0 --- /dev/null +++ b/build/gulpfile.vscode.win32.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import assert from 'assert'; +import * as cp from 'child_process'; +import * as fs from 'fs'; +import gulp from 'gulp'; +import * as path from 'path'; +import rcedit from 'rcedit'; +import vfs from 'vinyl-fs'; +import pkg from '../package.json' with { type: 'json' }; +import product from '../product.json' with { type: 'json' }; +import { getVersion } from './lib/getVersion.ts'; +import * as task from './lib/task.ts'; +import * as util from './lib/util.ts'; + +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); + +const repoPath = path.dirname(import.meta.dirname); +const commit = getVersion(repoPath); +const buildPath = (arch: string) => path.join(path.dirname(repoPath), `VSCode-win32-${arch}`); +const setupDir = (arch: string, target: string) => path.join(repoPath, '.build', `win32-${arch}`, `${target}-setup`); +const innoSetupPath = path.join(path.dirname(path.dirname(require.resolve('innosetup'))), 'bin', 'ISCC.exe'); +const signWin32Path = path.join(repoPath, 'build', 'azure-pipelines', 'common', 'sign-win32.ts'); + +function packageInnoSetup(iss: string, options: { definitions?: Record }, cb: (err?: Error | null) => void) { + const definitions = options.definitions || {}; + + if (process.argv.some(arg => arg === '--debug-inno')) { + definitions['Debug'] = 'true'; + } + + if (process.argv.some(arg => arg === '--sign')) { + definitions['Sign'] = 'true'; + } + + const keys = Object.keys(definitions); + + keys.forEach(key => assert(typeof definitions[key] === 'string', `Missing value for '${key}' in Inno Setup package step`)); + + const defs = keys.map(key => `/d${key}=${definitions[key]}`); + const args = [ + iss, + ...defs, + `/sesrp=node ${signWin32Path} $f` + ]; + + cp.spawn(innoSetupPath, args, { stdio: ['ignore', 'inherit', 'inherit'] }) + .on('error', cb) + .on('exit', code => { + if (code === 0) { + cb(null); + } else { + cb(new Error(`InnoSetup returned exit code: ${code}`)); + } + }); +} + +function buildWin32Setup(arch: string, target: string): task.CallbackTask { + if (target !== 'system' && target !== 'user') { + throw new Error('Invalid setup target'); + } + + return (cb) => { + const x64AppId = target === 'system' ? product.win32x64AppId : product.win32x64UserAppId; + const arm64AppId = target === 'system' ? product.win32arm64AppId : product.win32arm64UserAppId; + + const sourcePath = buildPath(arch); + const outputPath = setupDir(arch, target); + fs.mkdirSync(outputPath, { recursive: true }); + + const quality = (product as typeof product & { quality?: string }).quality || 'dev'; + let versionedResourcesFolder = ''; + let issPath = path.join(import.meta.dirname, 'win32', 'code.iss'); + if (quality && quality === 'insider') { + versionedResourcesFolder = commit!.substring(0, 10); + issPath = path.join(import.meta.dirname, 'win32', 'code-insider.iss'); + } + const originalProductJsonPath = path.join(sourcePath, versionedResourcesFolder, 'resources/app/product.json'); + const productJsonPath = path.join(outputPath, 'product.json'); + const productJson = JSON.parse(fs.readFileSync(originalProductJsonPath, 'utf8')); + productJson['target'] = target; + fs.writeFileSync(productJsonPath, JSON.stringify(productJson, undefined, '\t')); + + const definitions: Record = { + NameLong: product.nameLong, + NameShort: product.nameShort, + DirName: product.win32DirName, + Version: pkg.version, + RawVersion: pkg.version.replace(/-\w+$/, ''), + Commit: commit, + NameVersion: product.win32NameVersion + (target === 'user' ? ' (User)' : ''), + ExeBasename: product.nameShort, + RegValueName: product.win32RegValueName, + ShellNameShort: product.win32ShellNameShort, + AppMutex: product.win32MutexName, + TunnelMutex: product.win32TunnelMutex, + TunnelServiceMutex: product.win32TunnelServiceMutex, + TunnelApplicationName: product.tunnelApplicationName, + ApplicationName: product.applicationName, + Arch: arch, + AppId: { 'x64': x64AppId, 'arm64': arm64AppId }[arch], + IncompatibleTargetAppId: { 'x64': product.win32x64AppId, 'arm64': product.win32arm64AppId }[arch], + AppUserId: product.win32AppUserModelId, + ArchitecturesAllowed: { 'x64': 'x64', 'arm64': 'arm64' }[arch], + ArchitecturesInstallIn64BitMode: { 'x64': 'x64', 'arm64': 'arm64' }[arch], + SourceDir: sourcePath, + RepoDir: repoPath, + OutputDir: outputPath, + InstallTarget: target, + ProductJsonPath: productJsonPath, + VersionedResourcesFolder: versionedResourcesFolder, + Quality: quality + }; + + if (quality === 'stable' || quality === 'insider') { + definitions['AppxPackage'] = `${quality === 'stable' ? 'code' : 'code_insider'}_${arch}.appx`; + definitions['AppxPackageDll'] = `${quality === 'stable' ? 'code' : 'code_insider'}_explorer_command_${arch}.dll`; + definitions['AppxPackageName'] = `${product.win32AppUserModelId}`; + } + + packageInnoSetup(issPath, { definitions }, cb as (err?: Error | null) => void); + }; +} + +function defineWin32SetupTasks(arch: string, target: string) { + const cleanTask = util.rimraf(setupDir(arch, target)); + gulp.task(task.define(`vscode-win32-${arch}-${target}-setup`, task.series(cleanTask, buildWin32Setup(arch, target)))); +} + +defineWin32SetupTasks('x64', 'system'); +defineWin32SetupTasks('arm64', 'system'); +defineWin32SetupTasks('x64', 'user'); +defineWin32SetupTasks('arm64', 'user'); + +function copyInnoUpdater(arch: string) { + return () => { + return gulp.src('build/win32/{inno_updater.exe,vcruntime140.dll}', { base: 'build/win32' }) + .pipe(vfs.dest(path.join(buildPath(arch), 'tools'))); + }; +} + +function updateIcon(executablePath: string): task.CallbackTask { + return cb => { + const icon = path.join(repoPath, 'resources', 'win32', 'code.ico'); + rcedit(executablePath, { icon }, cb); + }; +} + +gulp.task(task.define('vscode-win32-x64-inno-updater', task.series(copyInnoUpdater('x64'), updateIcon(path.join(buildPath('x64'), 'tools', 'inno_updater.exe'))))); +gulp.task(task.define('vscode-win32-arm64-inno-updater', task.series(copyInnoUpdater('arm64'), updateIcon(path.join(buildPath('arm64'), 'tools', 'inno_updater.exe'))))); diff --git a/build/hygiene.js b/build/hygiene.js deleted file mode 100644 index c844ebd574b4b..0000000000000 --- a/build/hygiene.js +++ /dev/null @@ -1,313 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const filter = require('gulp-filter'); -const es = require('event-stream'); -const VinylFile = require('vinyl'); -const vfs = require('vinyl-fs'); -const path = require('path'); -const fs = require('fs'); -const pall = require('p-all'); - -const { all, copyrightFilter, unicodeFilter, indentationFilter, tsFormattingFilter, eslintFilter, stylelintFilter } = require('./filters'); - -const copyrightHeaderLines = [ - '/*---------------------------------------------------------------------------------------------', - ' * Copyright (c) Microsoft Corporation. All rights reserved.', - ' * Licensed under the MIT License. See License.txt in the project root for license information.', - ' *--------------------------------------------------------------------------------------------*/', -]; - -function hygiene(some, linting = true) { - const eslint = require('./gulp-eslint'); - const gulpstylelint = require('./stylelint'); - const formatter = require('./lib/formatter'); - - let errorCount = 0; - - const productJson = es.through(function (file) { - const product = JSON.parse(file.contents.toString('utf8')); - - if (product.extensionsGallery) { - console.error(`product.json: Contains 'extensionsGallery'`); - errorCount++; - } - - this.emit('data', file); - }); - - const unicode = es.through(function (file) { - const lines = file.contents.toString('utf8').split(/\r\n|\r|\n/); - file.__lines = lines; - const allowInComments = lines.some(line => /allow-any-unicode-comment-file/.test(line)); - let skipNext = false; - lines.forEach((line, i) => { - if (/allow-any-unicode-next-line/.test(line)) { - skipNext = true; - return; - } - if (skipNext) { - skipNext = false; - return; - } - // If unicode is allowed in comments, trim the comment from the line - if (allowInComments) { - if (line.match(/\s+(\*)/)) { // Naive multi-line comment check - line = ''; - } else { - const index = line.indexOf('\/\/'); - line = index === -1 ? line : line.substring(0, index); - } - } - // Please do not add symbols that resemble ASCII letters! - // eslint-disable-next-line no-misleading-character-class - const m = /([^\t\n\r\x20-\x7E⊃⊇✔︎✓🎯🧪✍️⚠️🛑🔴🚗🚙🚕🎉✨❗⇧⌥⌘×÷¦⋯…↑↓→→←↔⟷·•●◆▼⟪⟫┌└├⏎↩√φ]+)/g.exec(line); - if (m) { - console.error( - file.relative + `(${i + 1},${m.index + 1}): Unexpected unicode character: "${m[0]}" (charCode: ${m[0].charCodeAt(0)}). To suppress, use // allow-any-unicode-next-line` - ); - errorCount++; - } - }); - - this.emit('data', file); - }); - - const indentation = es.through(function (file) { - const lines = file.__lines || file.contents.toString('utf8').split(/\r\n|\r|\n/); - file.__lines = lines; - - lines.forEach((line, i) => { - if (/^\s*$/.test(line)) { - // empty or whitespace lines are OK - } else if (/^[\t]*[^\s]/.test(line)) { - // good indent - } else if (/^[\t]* \*/.test(line)) { - // block comment using an extra space - } else { - console.error( - file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation' - ); - errorCount++; - } - }); - - this.emit('data', file); - }); - - const copyrights = es.through(function (file) { - const lines = file.__lines; - - for (let i = 0; i < copyrightHeaderLines.length; i++) { - if (lines[i] !== copyrightHeaderLines[i]) { - console.error(file.relative + ': Missing or bad copyright statement'); - errorCount++; - break; - } - } - - this.emit('data', file); - }); - - const formatting = es.map(function (file, cb) { - try { - const rawInput = file.contents.toString('utf8'); - const rawOutput = formatter.format(file.path, rawInput); - - const original = rawInput.replace(/\r\n/gm, '\n'); - const formatted = rawOutput.replace(/\r\n/gm, '\n'); - if (original !== formatted) { - console.error( - `File not formatted. Run the 'Format Document' command to fix it:`, - file.relative - ); - errorCount++; - } - cb(null, file); - } catch (err) { - cb(err); - } - }); - - let input; - - if (Array.isArray(some) || typeof some === 'string' || !some) { - const options = { base: '.', follow: true, allowEmpty: true }; - if (some) { - input = vfs.src(some, options).pipe(filter(all)); // split this up to not unnecessarily filter all a second time - } else { - input = vfs.src(all, options); - } - } else { - input = some; - } - - const productJsonFilter = filter('product.json', { restore: true }); - const snapshotFilter = filter(['**', '!**/*.snap', '!**/*.snap.actual']); - const yarnLockFilter = filter(['**', '!**/yarn.lock']); - const unicodeFilterStream = filter(unicodeFilter, { restore: true }); - - const result = input - .pipe(filter((f) => !f.stat.isDirectory())) - .pipe(snapshotFilter) - .pipe(yarnLockFilter) - .pipe(productJsonFilter) - .pipe(process.env['BUILD_SOURCEVERSION'] ? es.through() : productJson) - .pipe(productJsonFilter.restore) - .pipe(unicodeFilterStream) - .pipe(unicode) - .pipe(unicodeFilterStream.restore) - .pipe(filter(indentationFilter)) - .pipe(indentation) - .pipe(filter(copyrightFilter)) - .pipe(copyrights); - - const streams = [ - result.pipe(filter(tsFormattingFilter)).pipe(formatting) - ]; - - if (linting) { - streams.push( - result - .pipe(filter(eslintFilter)) - .pipe( - eslint((results) => { - errorCount += results.warningCount; - errorCount += results.errorCount; - }) - ) - ); - streams.push( - result.pipe(filter(stylelintFilter)).pipe(gulpstylelint(((message, isError) => { - if (isError) { - console.error(message); - errorCount++; - } else { - console.warn(message); - } - }))) - ); - } - - let count = 0; - return es.merge(...streams).pipe( - es.through( - function (data) { - count++; - if (process.env['TRAVIS'] && count % 10 === 0) { - process.stdout.write('.'); - } - this.emit('data', data); - }, - function () { - process.stdout.write('\n'); - if (errorCount > 0) { - this.emit( - 'error', - 'Hygiene failed with ' + - errorCount + - ` errors. Check 'build / gulpfile.hygiene.js'.` - ); - } else { - this.emit('end'); - } - } - ) - ); -} - -module.exports.hygiene = hygiene; - -function createGitIndexVinyls(paths) { - const cp = require('child_process'); - const repositoryPath = process.cwd(); - - const fns = paths.map((relativePath) => () => - new Promise((c, e) => { - const fullPath = path.join(repositoryPath, relativePath); - - fs.stat(fullPath, (err, stat) => { - if (err && err.code === 'ENOENT') { - // ignore deletions - return c(null); - } else if (err) { - return e(err); - } - - cp.exec( - process.platform === 'win32' ? `git show :${relativePath}` : `git show ':${relativePath}'`, - { maxBuffer: stat.size, encoding: 'buffer' }, - (err, out) => { - if (err) { - return e(err); - } - - c( - new VinylFile({ - path: fullPath, - base: repositoryPath, - contents: out, - stat, - }) - ); - } - ); - }); - }) - ); - - return pall(fns, { concurrency: 4 }).then((r) => r.filter((p) => !!p)); -} - -// this allows us to run hygiene as a git pre-commit hook -if (require.main === module) { - const cp = require('child_process'); - - process.on('unhandledRejection', (reason, p) => { - console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); - process.exit(1); - }); - - if (process.argv.length > 2) { - hygiene(process.argv.slice(2)).on('error', (err) => { - console.error(); - console.error(err); - process.exit(1); - }); - } else { - cp.exec( - 'git diff --cached --name-only', - { maxBuffer: 2000 * 1024 }, - (err, out) => { - if (err) { - console.error(); - console.error(err); - process.exit(1); - } - - const some = out.split(/\r?\n/).filter((l) => !!l); - - if (some.length > 0) { - console.log('Reading git index versions...'); - - createGitIndexVinyls(some) - .then( - (vinyls) => - new Promise((c, e) => - hygiene(es.readArray(vinyls).pipe(filter(all))) - .on('end', () => c()) - .on('error', e) - ) - ) - .catch((err) => { - console.error(); - console.error(err); - process.exit(1); - }); - } - } - ); - } -} diff --git a/build/hygiene.ts b/build/hygiene.ts new file mode 100644 index 0000000000000..8778907f13f63 --- /dev/null +++ b/build/hygiene.ts @@ -0,0 +1,314 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import cp from 'child_process'; +import es from 'event-stream'; +import fs from 'fs'; +import filter from 'gulp-filter'; +import pall from 'p-all'; +import path from 'path'; +import VinylFile from 'vinyl'; +import vfs from 'vinyl-fs'; +import { all, copyrightFilter, eslintFilter, indentationFilter, stylelintFilter, tsFormattingFilter, unicodeFilter } from './filters.ts'; +import eslint from './gulp-eslint.ts'; +import * as formatter from './lib/formatter.ts'; +import gulpstylelint from './stylelint.ts'; + +const copyrightHeaderLines = [ + '/*---------------------------------------------------------------------------------------------', + ' * Copyright (c) Microsoft Corporation. All rights reserved.', + ' * Licensed under the MIT License. See License.txt in the project root for license information.', + ' *--------------------------------------------------------------------------------------------*/', +]; + +interface VinylFileWithLines extends VinylFile { + __lines: string[]; +} + +/** + * Main hygiene function that runs checks on files + */ +export function hygiene(some: NodeJS.ReadWriteStream | string[] | undefined, runEslint = true): NodeJS.ReadWriteStream { + console.log('Starting hygiene...'); + let errorCount = 0; + + const productJson = es.through(function (file: VinylFile) { + const product = JSON.parse(file.contents!.toString('utf8')); + + if (product.extensionsGallery) { + console.error(`product.json: Contains 'extensionsGallery'`); + errorCount++; + } + + this.emit('data', file); + }); + + const unicode = es.through(function (file: VinylFileWithLines) { + const lines = file.contents!.toString('utf8').split(/\r\n|\r|\n/); + file.__lines = lines; + const allowInComments = lines.some(line => /allow-any-unicode-comment-file/.test(line)); + let skipNext = false; + lines.forEach((line, i) => { + if (/allow-any-unicode-next-line/.test(line)) { + skipNext = true; + return; + } + if (skipNext) { + skipNext = false; + return; + } + // If unicode is allowed in comments, trim the comment from the line + if (allowInComments) { + if (line.match(/\s+(\*)/)) { // Naive multi-line comment check + line = ''; + } else { + const index = line.indexOf('//'); + line = index === -1 ? line : line.substring(0, index); + } + } + // Please do not add symbols that resemble ASCII letters! + // eslint-disable-next-line no-misleading-character-class + const m = /([^\t\n\r\x20-\x7E⊃⊇✔︎✓🎯🧪✍️⚠️🛑🔴🚗🚙🚕🎉✨❗⇧⌥⌘×÷¦⋯…↑↓→→←↔⟷·•●◆▼⟪⟫┌└├⏎↩√φ]+)/g.exec(line); + if (m) { + console.error( + file.relative + `(${i + 1},${m.index + 1}): Unexpected unicode character: "${m[0]}" (charCode: ${m[0].charCodeAt(0)}). To suppress, use // allow-any-unicode-next-line` + ); + errorCount++; + } + }); + + this.emit('data', file); + }); + + const indentation = es.through(function (file: VinylFileWithLines) { + const lines = file.__lines || file.contents!.toString('utf8').split(/\r\n|\r|\n/); + file.__lines = lines; + + lines.forEach((line, i) => { + if (/^\s*$/.test(line)) { + // empty or whitespace lines are OK + } else if (/^[\t]*[^\s]/.test(line)) { + // good indent + } else if (/^[\t]* \*/.test(line)) { + // block comment using an extra space + } else { + console.error( + file.relative + '(' + (i + 1) + ',1): Bad whitespace indentation' + ); + errorCount++; + } + }); + + this.emit('data', file); + }); + + const copyrights = es.through(function (file: VinylFileWithLines) { + const lines = file.__lines; + + for (let i = 0; i < copyrightHeaderLines.length; i++) { + if (lines[i] !== copyrightHeaderLines[i]) { + console.error(file.relative + ': Missing or bad copyright statement'); + errorCount++; + break; + } + } + + this.emit('data', file); + }); + + const formatting = es.map(function (file: any, cb) { + try { + const rawInput = file.contents!.toString('utf8'); + const rawOutput = formatter.format(file.path, rawInput); + + const original = rawInput.replace(/\r\n/gm, '\n'); + const formatted = rawOutput.replace(/\r\n/gm, '\n'); + if (original !== formatted) { + console.error( + `File not formatted. Run the 'Format Document' command to fix it:`, + file.relative + ); + errorCount++; + } + cb(undefined, file); + } catch (err) { + cb(err); + } + }); + + let input: NodeJS.ReadWriteStream; + if (Array.isArray(some) || typeof some === 'string' || !some) { + const options = { base: '.', follow: true, allowEmpty: true }; + if (some) { + input = vfs.src(some, options).pipe(filter(Array.from(all))); // split this up to not unnecessarily filter all a second time + } else { + input = vfs.src(Array.from(all), options); + } + } else { + input = some; + } + + const productJsonFilter = filter('product.json', { restore: true }); + const snapshotFilter = filter(['**', '!**/*.snap', '!**/*.snap.actual']); + const yarnLockFilter = filter(['**', '!**/yarn.lock']); + const unicodeFilterStream = filter(Array.from(unicodeFilter), { restore: true }); + + const result = input + .pipe(filter((f) => Boolean(f.stat && !f.stat.isDirectory()))) + .pipe(snapshotFilter) + .pipe(yarnLockFilter) + .pipe(productJsonFilter) + .pipe(process.env['BUILD_SOURCEVERSION'] ? es.through() : productJson) + .pipe(productJsonFilter.restore) + .pipe(unicodeFilterStream) + .pipe(unicode) + .pipe(unicodeFilterStream.restore) + .pipe(filter(Array.from(indentationFilter))) + .pipe(indentation) + .pipe(filter(Array.from(copyrightFilter))) + .pipe(copyrights); + + const streams: NodeJS.ReadWriteStream[] = [ + result.pipe(filter(Array.from(tsFormattingFilter))).pipe(formatting) + ]; + + if (runEslint) { + streams.push( + result + .pipe(filter(Array.from(eslintFilter))) + .pipe( + eslint((results) => { + errorCount += results.warningCount; + errorCount += results.errorCount; + }) + ) + ); + } + + streams.push( + result.pipe(filter(Array.from(stylelintFilter))).pipe(gulpstylelint(((message: string, isError: boolean) => { + if (isError) { + console.error(message); + errorCount++; + } else { + console.warn(message); + } + }))) + ); + + let count = 0; + return es.merge(...streams).pipe( + es.through( + function (data: unknown) { + count++; + if (process.env['TRAVIS'] && count % 10 === 0) { + process.stdout.write('.'); + } + this.emit('data', data); + }, + function () { + process.stdout.write('\n'); + if (errorCount > 0) { + this.emit( + 'error', + 'Hygiene failed with ' + + errorCount + + ` errors. Check 'build / gulpfile.hygiene.js'.` + ); + } else { + this.emit('end'); + } + } + ) + ); +} + +function createGitIndexVinyls(paths: string[]): Promise { + const repositoryPath = process.cwd(); + + const fns = paths.map((relativePath) => () => + new Promise((c, e) => { + const fullPath = path.join(repositoryPath, relativePath); + + fs.stat(fullPath, (err, stat) => { + if (err && err.code === 'ENOENT') { + // ignore deletions + return c(null); + } else if (err) { + return e(err); + } + + cp.exec( + process.platform === 'win32' ? `git show :${relativePath}` : `git show ':${relativePath}'`, + { maxBuffer: stat.size, encoding: 'buffer' }, + (err, out) => { + if (err) { + return e(err); + } + + c(new VinylFile({ + path: fullPath, + base: repositoryPath, + contents: out, + stat: stat, + })); + } + ); + }); + }) + ); + + return pall(fns, { concurrency: 4 }).then((r) => r.filter((p): p is VinylFile => !!p)); +} + +// this allows us to run hygiene as a git pre-commit hook +if (import.meta.main) { + process.on('unhandledRejection', (reason: unknown, p: Promise) => { + console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); + process.exit(1); + }); + + if (process.argv.length > 2) { + hygiene(process.argv.slice(2)).on('error', (err: Error) => { + console.error(); + console.error(err); + process.exit(1); + }); + } else { + cp.exec( + 'git diff --cached --name-only', + { maxBuffer: 2000 * 1024 }, + (err, out) => { + if (err) { + console.error(); + console.error(err); + process.exit(1); + } + + const some = out.split(/\r?\n/).filter((l) => !!l); + + if (some.length > 0) { + console.log('Reading git index versions...'); + + createGitIndexVinyls(some) + .then( + (vinyls) => { + return new Promise((c, e) => + hygiene(es.readArray(vinyls).pipe(filter(Array.from(all)))) + .on('end', () => c()) + .on('error', e) + ); + } + ) + .catch((err: Error) => { + console.error(); + console.error(err); + process.exit(1); + }); + } + } + ); + } +} diff --git a/build/lib/asar.js b/build/lib/asar.js deleted file mode 100644 index 20c982a66217d..0000000000000 --- a/build/lib/asar.js +++ /dev/null @@ -1,156 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.createAsar = createAsar; -const path_1 = __importDefault(require("path")); -const event_stream_1 = __importDefault(require("event-stream")); -const pickle = require('chromium-pickle-js'); -const Filesystem = require('asar/lib/filesystem'); -const vinyl_1 = __importDefault(require("vinyl")); -const minimatch_1 = __importDefault(require("minimatch")); -function createAsar(folderPath, unpackGlobs, skipGlobs, duplicateGlobs, destFilename) { - const shouldUnpackFile = (file) => { - for (let i = 0; i < unpackGlobs.length; i++) { - if ((0, minimatch_1.default)(file.relative, unpackGlobs[i])) { - return true; - } - } - return false; - }; - const shouldSkipFile = (file) => { - for (const skipGlob of skipGlobs) { - if ((0, minimatch_1.default)(file.relative, skipGlob)) { - return true; - } - } - return false; - }; - // Files that should be duplicated between - // node_modules.asar and node_modules - const shouldDuplicateFile = (file) => { - for (const duplicateGlob of duplicateGlobs) { - if ((0, minimatch_1.default)(file.relative, duplicateGlob)) { - return true; - } - } - return false; - }; - const filesystem = new Filesystem(folderPath); - const out = []; - // Keep track of pending inserts - let pendingInserts = 0; - let onFileInserted = () => { pendingInserts--; }; - // Do not insert twice the same directory - const seenDir = {}; - const insertDirectoryRecursive = (dir) => { - if (seenDir[dir]) { - return; - } - let lastSlash = dir.lastIndexOf('/'); - if (lastSlash === -1) { - lastSlash = dir.lastIndexOf('\\'); - } - if (lastSlash !== -1) { - insertDirectoryRecursive(dir.substring(0, lastSlash)); - } - seenDir[dir] = true; - filesystem.insertDirectory(dir); - }; - const insertDirectoryForFile = (file) => { - let lastSlash = file.lastIndexOf('/'); - if (lastSlash === -1) { - lastSlash = file.lastIndexOf('\\'); - } - if (lastSlash !== -1) { - insertDirectoryRecursive(file.substring(0, lastSlash)); - } - }; - const insertFile = (relativePath, stat, shouldUnpack) => { - insertDirectoryForFile(relativePath); - pendingInserts++; - // Do not pass `onFileInserted` directly because it gets overwritten below. - // Create a closure capturing `onFileInserted`. - filesystem.insertFile(relativePath, shouldUnpack, { stat: stat }, {}).then(() => onFileInserted(), () => onFileInserted()); - }; - return event_stream_1.default.through(function (file) { - if (file.stat.isDirectory()) { - return; - } - if (!file.stat.isFile()) { - throw new Error(`unknown item in stream!`); - } - if (shouldSkipFile(file)) { - this.queue(new vinyl_1.default({ - base: '.', - path: file.path, - stat: file.stat, - contents: file.contents - })); - return; - } - if (shouldDuplicateFile(file)) { - this.queue(new vinyl_1.default({ - base: '.', - path: file.path, - stat: file.stat, - contents: file.contents - })); - } - const shouldUnpack = shouldUnpackFile(file); - insertFile(file.relative, { size: file.contents.length, mode: file.stat.mode }, shouldUnpack); - if (shouldUnpack) { - // The file goes outside of xx.asar, in a folder xx.asar.unpacked - const relative = path_1.default.relative(folderPath, file.path); - this.queue(new vinyl_1.default({ - base: '.', - path: path_1.default.join(destFilename + '.unpacked', relative), - stat: file.stat, - contents: file.contents - })); - } - else { - // The file goes inside of xx.asar - out.push(file.contents); - } - }, function () { - const finish = () => { - { - const headerPickle = pickle.createEmpty(); - headerPickle.writeString(JSON.stringify(filesystem.header)); - const headerBuf = headerPickle.toBuffer(); - const sizePickle = pickle.createEmpty(); - sizePickle.writeUInt32(headerBuf.length); - const sizeBuf = sizePickle.toBuffer(); - out.unshift(headerBuf); - out.unshift(sizeBuf); - } - const contents = Buffer.concat(out); - out.length = 0; - this.queue(new vinyl_1.default({ - base: '.', - path: destFilename, - contents: contents - })); - this.queue(null); - }; - // Call finish() only when all file inserts have finished... - if (pendingInserts === 0) { - finish(); - } - else { - onFileInserted = () => { - pendingInserts--; - if (pendingInserts === 0) { - finish(); - } - }; - } - }); -} -//# sourceMappingURL=asar.js.map \ No newline at end of file diff --git a/build/lib/asar.ts b/build/lib/asar.ts index 5f2df925bde9c..873b3f946fd99 100644 --- a/build/lib/asar.ts +++ b/build/lib/asar.ts @@ -5,18 +5,11 @@ import path from 'path'; import es from 'event-stream'; -const pickle = require('chromium-pickle-js'); -const Filesystem = require('asar/lib/filesystem'); +import pickle from 'chromium-pickle-js'; +import Filesystem from 'asar/lib/filesystem.js'; import VinylFile from 'vinyl'; import minimatch from 'minimatch'; -declare class AsarFilesystem { - readonly header: unknown; - constructor(src: string); - insertDirectory(path: string, shouldUnpack?: boolean): unknown; - insertFile(path: string, shouldUnpack: boolean, file: { stat: { size: number; mode: number } }, options: {}): Promise; -} - export function createAsar(folderPath: string, unpackGlobs: string[], skipGlobs: string[], duplicateGlobs: string[], destFilename: string): NodeJS.ReadWriteStream { const shouldUnpackFile = (file: VinylFile): boolean => { diff --git a/build/lib/builtInExtensions.js b/build/lib/builtInExtensions.js deleted file mode 100644 index 249777c44588e..0000000000000 --- a/build/lib/builtInExtensions.js +++ /dev/null @@ -1,179 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getExtensionStream = getExtensionStream; -exports.getBuiltInExtensions = getBuiltInExtensions; -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const os_1 = __importDefault(require("os")); -const rimraf_1 = __importDefault(require("rimraf")); -const event_stream_1 = __importDefault(require("event-stream")); -const gulp_rename_1 = __importDefault(require("gulp-rename")); -const vinyl_fs_1 = __importDefault(require("vinyl-fs")); -const ext = __importStar(require("./extensions")); -const fancy_log_1 = __importDefault(require("fancy-log")); -const ansi_colors_1 = __importDefault(require("ansi-colors")); -const root = path_1.default.dirname(path_1.default.dirname(__dirname)); -const productjson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '../../product.json'), 'utf8')); -const builtInExtensions = productjson.builtInExtensions || []; -const webBuiltInExtensions = productjson.webBuiltInExtensions || []; -const controlFilePath = path_1.default.join(os_1.default.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); -const ENABLE_LOGGING = !process.env['VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE']; -function log(...messages) { - if (ENABLE_LOGGING) { - (0, fancy_log_1.default)(...messages); - } -} -function getExtensionPath(extension) { - return path_1.default.join(root, '.build', 'builtInExtensions', extension.name); -} -function isUpToDate(extension) { - const packagePath = path_1.default.join(getExtensionPath(extension), 'package.json'); - if (!fs_1.default.existsSync(packagePath)) { - return false; - } - const packageContents = fs_1.default.readFileSync(packagePath, { encoding: 'utf8' }); - try { - const diskVersion = JSON.parse(packageContents).version; - return (diskVersion === extension.version); - } - catch (err) { - return false; - } -} -function getExtensionDownloadStream(extension) { - let input; - if (extension.vsix) { - input = ext.fromVsix(path_1.default.join(root, extension.vsix), extension); - } - else if (productjson.extensionsGallery?.serviceUrl) { - input = ext.fromMarketplace(productjson.extensionsGallery.serviceUrl, extension); - } - else { - input = ext.fromGithub(extension); - } - return input.pipe((0, gulp_rename_1.default)(p => p.dirname = `${extension.name}/${p.dirname}`)); -} -function getExtensionStream(extension) { - // if the extension exists on disk, use those files instead of downloading anew - if (isUpToDate(extension)) { - log('[extensions]', `${extension.name}@${extension.version} up to date`, ansi_colors_1.default.green('✔︎')); - return vinyl_fs_1.default.src(['**'], { cwd: getExtensionPath(extension), dot: true }) - .pipe((0, gulp_rename_1.default)(p => p.dirname = `${extension.name}/${p.dirname}`)); - } - return getExtensionDownloadStream(extension); -} -function syncMarketplaceExtension(extension) { - const galleryServiceUrl = productjson.extensionsGallery?.serviceUrl; - const source = ansi_colors_1.default.blue(galleryServiceUrl ? '[marketplace]' : '[github]'); - if (isUpToDate(extension)) { - log(source, `${extension.name}@${extension.version}`, ansi_colors_1.default.green('✔︎')); - return event_stream_1.default.readArray([]); - } - rimraf_1.default.sync(getExtensionPath(extension)); - return getExtensionDownloadStream(extension) - .pipe(vinyl_fs_1.default.dest('.build/builtInExtensions')) - .on('end', () => log(source, extension.name, ansi_colors_1.default.green('✔︎'))); -} -function syncExtension(extension, controlState) { - if (extension.platforms) { - const platforms = new Set(extension.platforms); - if (!platforms.has(process.platform)) { - log(ansi_colors_1.default.gray('[skip]'), `${extension.name}@${extension.version}: Platform '${process.platform}' not supported: [${extension.platforms}]`, ansi_colors_1.default.green('✔︎')); - return event_stream_1.default.readArray([]); - } - } - switch (controlState) { - case 'disabled': - log(ansi_colors_1.default.blue('[disabled]'), ansi_colors_1.default.gray(extension.name)); - return event_stream_1.default.readArray([]); - case 'marketplace': - return syncMarketplaceExtension(extension); - default: - if (!fs_1.default.existsSync(controlState)) { - log(ansi_colors_1.default.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but that path does not exist.`)); - return event_stream_1.default.readArray([]); - } - else if (!fs_1.default.existsSync(path_1.default.join(controlState, 'package.json'))) { - log(ansi_colors_1.default.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but there is no 'package.json' file in that directory.`)); - return event_stream_1.default.readArray([]); - } - log(ansi_colors_1.default.blue('[local]'), `${extension.name}: ${ansi_colors_1.default.cyan(controlState)}`, ansi_colors_1.default.green('✔︎')); - return event_stream_1.default.readArray([]); - } -} -function readControlFile() { - try { - return JSON.parse(fs_1.default.readFileSync(controlFilePath, 'utf8')); - } - catch (err) { - return {}; - } -} -function writeControlFile(control) { - fs_1.default.mkdirSync(path_1.default.dirname(controlFilePath), { recursive: true }); - fs_1.default.writeFileSync(controlFilePath, JSON.stringify(control, null, 2)); -} -function getBuiltInExtensions() { - log('Synchronizing built-in extensions...'); - log(`You can manage built-in extensions with the ${ansi_colors_1.default.cyan('--builtin')} flag`); - const control = readControlFile(); - const streams = []; - for (const extension of [...builtInExtensions, ...webBuiltInExtensions]) { - const controlState = control[extension.name] || 'marketplace'; - control[extension.name] = controlState; - streams.push(syncExtension(extension, controlState)); - } - writeControlFile(control); - return new Promise((resolve, reject) => { - event_stream_1.default.merge(streams) - .on('error', reject) - .on('end', resolve); - }); -} -if (require.main === module) { - getBuiltInExtensions().then(() => process.exit(0)).catch(err => { - console.error(err); - process.exit(1); - }); -} -//# sourceMappingURL=builtInExtensions.js.map \ No newline at end of file diff --git a/build/lib/builtInExtensions.ts b/build/lib/builtInExtensions.ts index e9a1180ce35ea..d52567b17d1d6 100644 --- a/build/lib/builtInExtensions.ts +++ b/build/lib/builtInExtensions.ts @@ -10,7 +10,7 @@ import rimraf from 'rimraf'; import es from 'event-stream'; import rename from 'gulp-rename'; import vfs from 'vinyl-fs'; -import * as ext from './extensions'; +import * as ext from './extensions.ts'; import fancyLog from 'fancy-log'; import ansiColors from 'ansi-colors'; import { Stream } from 'stream'; @@ -34,10 +34,10 @@ export interface IExtensionDefinition { }; } -const root = path.dirname(path.dirname(__dirname)); -const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../product.json'), 'utf8')); -const builtInExtensions = productjson.builtInExtensions || []; -const webBuiltInExtensions = productjson.webBuiltInExtensions || []; +const root = path.dirname(path.dirname(import.meta.dirname)); +const productjson = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, '../../product.json'), 'utf8')); +const builtInExtensions = productjson.builtInExtensions as IExtensionDefinition[] || []; +const webBuiltInExtensions = productjson.webBuiltInExtensions as IExtensionDefinition[] || []; const controlFilePath = path.join(os.homedir(), '.vscode-oss-dev', 'extensions', 'control.json'); const ENABLE_LOGGING = !process.env['VSCODE_BUILD_BUILTIN_EXTENSIONS_SILENCE_PLEASE']; @@ -181,7 +181,7 @@ export function getBuiltInExtensions(): Promise { }); } -if (require.main === module) { +if (import.meta.main) { getBuiltInExtensions().then(() => process.exit(0)).catch(err => { console.error(err); process.exit(1); diff --git a/build/lib/builtInExtensionsCG.js b/build/lib/builtInExtensionsCG.js deleted file mode 100644 index 3dc0ae27f0a8c..0000000000000 --- a/build/lib/builtInExtensionsCG.js +++ /dev/null @@ -1,81 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const url_1 = __importDefault(require("url")); -const ansi_colors_1 = __importDefault(require("ansi-colors")); -const root = path_1.default.dirname(path_1.default.dirname(__dirname)); -const rootCG = path_1.default.join(root, 'extensionsCG'); -const productjson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '../../product.json'), 'utf8')); -const builtInExtensions = productjson.builtInExtensions || []; -const webBuiltInExtensions = productjson.webBuiltInExtensions || []; -const token = process.env['GITHUB_TOKEN']; -const contentBasePath = 'raw.githubusercontent.com'; -const contentFileNames = ['package.json', 'package-lock.json']; -async function downloadExtensionDetails(extension) { - const extensionLabel = `${extension.name}@${extension.version}`; - const repository = url_1.default.parse(extension.repo).path.substr(1); - const repositoryContentBaseUrl = `https://${token ? `${token}@` : ''}${contentBasePath}/${repository}/v${extension.version}`; - async function getContent(fileName) { - try { - const response = await fetch(`${repositoryContentBaseUrl}/${fileName}`); - if (response.ok) { - return { fileName, body: Buffer.from(await response.arrayBuffer()) }; - } - else if (response.status === 404) { - return { fileName, body: undefined }; - } - else { - return { fileName, body: null }; - } - } - catch (e) { - return { fileName, body: null }; - } - } - const promises = contentFileNames.map(getContent); - console.log(extensionLabel); - const results = await Promise.all(promises); - for (const result of results) { - if (result.body) { - const extensionFolder = path_1.default.join(rootCG, extension.name); - fs_1.default.mkdirSync(extensionFolder, { recursive: true }); - fs_1.default.writeFileSync(path_1.default.join(extensionFolder, result.fileName), result.body); - console.log(` - ${result.fileName} ${ansi_colors_1.default.green('✔︎')}`); - } - else if (result.body === undefined) { - console.log(` - ${result.fileName} ${ansi_colors_1.default.yellow('⚠️')}`); - } - else { - console.log(` - ${result.fileName} ${ansi_colors_1.default.red('🛑')}`); - } - } - // Validation - if (!results.find(r => r.fileName === 'package.json')?.body) { - // throw new Error(`The "package.json" file could not be found for the built-in extension - ${extensionLabel}`); - } - if (!results.find(r => r.fileName === 'package-lock.json')?.body) { - // throw new Error(`The "package-lock.json" could not be found for the built-in extension - ${extensionLabel}`); - } -} -async function main() { - for (const extension of [...builtInExtensions, ...webBuiltInExtensions]) { - await downloadExtensionDetails(extension); - } -} -main().then(() => { - console.log(`Built-in extensions component data downloaded ${ansi_colors_1.default.green('✔︎')}`); - process.exit(0); -}, err => { - console.log(`Built-in extensions component data could not be downloaded ${ansi_colors_1.default.red('🛑')}`); - console.error(err); - process.exit(1); -}); -//# sourceMappingURL=builtInExtensionsCG.js.map \ No newline at end of file diff --git a/build/lib/builtInExtensionsCG.ts b/build/lib/builtInExtensionsCG.ts index 4628b365a2e1d..1c4ce609c3da5 100644 --- a/build/lib/builtInExtensionsCG.ts +++ b/build/lib/builtInExtensionsCG.ts @@ -7,13 +7,13 @@ import fs from 'fs'; import path from 'path'; import url from 'url'; import ansiColors from 'ansi-colors'; -import { IExtensionDefinition } from './builtInExtensions'; +import type { IExtensionDefinition } from './builtInExtensions.ts'; -const root = path.dirname(path.dirname(__dirname)); +const root = path.dirname(path.dirname(import.meta.dirname)); const rootCG = path.join(root, 'extensionsCG'); -const productjson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../product.json'), 'utf8')); -const builtInExtensions = productjson.builtInExtensions || []; -const webBuiltInExtensions = productjson.webBuiltInExtensions || []; +const productjson = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, '../../product.json'), 'utf8')); +const builtInExtensions = productjson.builtInExtensions as IExtensionDefinition[] || []; +const webBuiltInExtensions = productjson.webBuiltInExtensions as IExtensionDefinition[] || []; const token = process.env['GITHUB_TOKEN']; const contentBasePath = 'raw.githubusercontent.com'; diff --git a/build/lib/bundle.js b/build/lib/bundle.js deleted file mode 100644 index 382b648defbe6..0000000000000 --- a/build/lib/bundle.js +++ /dev/null @@ -1,62 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.removeAllTSBoilerplate = removeAllTSBoilerplate; -function removeAllTSBoilerplate(source) { - const seen = new Array(BOILERPLATE.length).fill(true, 0, BOILERPLATE.length); - return removeDuplicateTSBoilerplate(source, seen); -} -// Taken from typescript compiler => emitFiles -const BOILERPLATE = [ - { start: /^var __extends/, end: /^}\)\(\);$/ }, - { start: /^var __assign/, end: /^};$/ }, - { start: /^var __decorate/, end: /^};$/ }, - { start: /^var __metadata/, end: /^};$/ }, - { start: /^var __param/, end: /^};$/ }, - { start: /^var __awaiter/, end: /^};$/ }, - { start: /^var __generator/, end: /^};$/ }, - { start: /^var __createBinding/, end: /^}\)\);$/ }, - { start: /^var __setModuleDefault/, end: /^}\);$/ }, - { start: /^var __importStar/, end: /^};$/ }, - { start: /^var __addDisposableResource/, end: /^};$/ }, - { start: /^var __disposeResources/, end: /^}\);$/ }, -]; -function removeDuplicateTSBoilerplate(source, SEEN_BOILERPLATE = []) { - const lines = source.split(/\r\n|\n|\r/); - const newLines = []; - let IS_REMOVING_BOILERPLATE = false, END_BOILERPLATE; - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - if (IS_REMOVING_BOILERPLATE) { - newLines.push(''); - if (END_BOILERPLATE.test(line)) { - IS_REMOVING_BOILERPLATE = false; - } - } - else { - for (let j = 0; j < BOILERPLATE.length; j++) { - const boilerplate = BOILERPLATE[j]; - if (boilerplate.start.test(line)) { - if (SEEN_BOILERPLATE[j]) { - IS_REMOVING_BOILERPLATE = true; - END_BOILERPLATE = boilerplate.end; - } - else { - SEEN_BOILERPLATE[j] = true; - } - } - } - if (IS_REMOVING_BOILERPLATE) { - newLines.push(''); - } - else { - newLines.push(line); - } - } - } - return newLines.join('\n'); -} -//# sourceMappingURL=bundle.js.map \ No newline at end of file diff --git a/build/lib/compilation.js b/build/lib/compilation.js deleted file mode 100644 index 61a6d28b29fb4..0000000000000 --- a/build/lib/compilation.js +++ /dev/null @@ -1,340 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.watchApiProposalNamesTask = exports.compileApiProposalNamesTask = void 0; -exports.createCompile = createCompile; -exports.transpileTask = transpileTask; -exports.compileTask = compileTask; -exports.watchTask = watchTask; -const event_stream_1 = __importDefault(require("event-stream")); -const fs_1 = __importDefault(require("fs")); -const gulp_1 = __importDefault(require("gulp")); -const path_1 = __importDefault(require("path")); -const monacodts = __importStar(require("./monaco-api")); -const nls = __importStar(require("./nls")); -const reporter_1 = require("./reporter"); -const util = __importStar(require("./util")); -const fancy_log_1 = __importDefault(require("fancy-log")); -const ansi_colors_1 = __importDefault(require("ansi-colors")); -const os_1 = __importDefault(require("os")); -const vinyl_1 = __importDefault(require("vinyl")); -const task = __importStar(require("./task")); -const index_1 = require("./mangle/index"); -const ts = require("typescript"); -const watch = require('./watch'); -// --- gulp-tsb: compile and transpile -------------------------------- -const reporter = (0, reporter_1.createReporter)(); -function getTypeScriptCompilerOptions(src) { - const rootDir = path_1.default.join(__dirname, `../../${src}`); - const options = {}; - options.verbose = false; - options.sourceMap = true; - if (process.env['VSCODE_NO_SOURCEMAP']) { // To be used by developers in a hurry - options.sourceMap = false; - } - options.rootDir = rootDir; - options.baseUrl = rootDir; - options.sourceRoot = util.toFileUri(rootDir); - options.newLine = /\r\n/.test(fs_1.default.readFileSync(__filename, 'utf8')) ? 0 : 1; - return options; -} -function createCompile(src, { build, emitError, transpileOnly, preserveEnglish }) { - const tsb = require('./tsb'); - const sourcemaps = require('gulp-sourcemaps'); - const projectPath = path_1.default.join(__dirname, '../../', src, 'tsconfig.json'); - const overrideOptions = { ...getTypeScriptCompilerOptions(src), inlineSources: Boolean(build) }; - if (!build) { - overrideOptions.inlineSourceMap = true; - } - const compilation = tsb.create(projectPath, overrideOptions, { - verbose: false, - transpileOnly: Boolean(transpileOnly), - transpileWithEsbuild: typeof transpileOnly !== 'boolean' && transpileOnly.esbuild - }, err => reporter(err)); - function pipeline(token) { - const bom = require('gulp-bom'); - const tsFilter = util.filter(data => /\.ts$/.test(data.path)); - const isUtf8Test = (f) => /(\/|\\)test(\/|\\).*utf8/.test(f.path); - const isRuntimeJs = (f) => f.path.endsWith('.js') && !f.path.includes('fixtures'); - const noDeclarationsFilter = util.filter(data => !(/\.d\.ts$/.test(data.path))); - const input = event_stream_1.default.through(); - const output = input - .pipe(util.$if(isUtf8Test, bom())) // this is required to preserve BOM in test files that loose it otherwise - .pipe(util.$if(!build && isRuntimeJs, util.appendOwnPathSourceURL())) - .pipe(tsFilter) - .pipe(util.loadSourcemaps()) - .pipe(compilation(token)) - .pipe(noDeclarationsFilter) - .pipe(util.$if(build, nls.nls({ preserveEnglish }))) - .pipe(noDeclarationsFilter.restore) - .pipe(util.$if(!transpileOnly, sourcemaps.write('.', { - addComment: false, - includeContent: !!build, - sourceRoot: overrideOptions.sourceRoot - }))) - .pipe(tsFilter.restore) - .pipe(reporter.end(!!emitError)); - return event_stream_1.default.duplex(input, output); - } - pipeline.tsProjectSrc = () => { - return compilation.src({ base: src }); - }; - pipeline.projectPath = projectPath; - return pipeline; -} -function transpileTask(src, out, esbuild) { - const task = () => { - const transpile = createCompile(src, { build: false, emitError: true, transpileOnly: { esbuild }, preserveEnglish: false }); - const srcPipe = gulp_1.default.src(`${src}/**`, { base: `${src}` }); - return srcPipe - .pipe(transpile()) - .pipe(gulp_1.default.dest(out)); - }; - task.taskName = `transpile-${path_1.default.basename(src)}`; - return task; -} -function compileTask(src, out, build, options = {}) { - const task = () => { - if (os_1.default.totalmem() < 4_000_000_000) { - throw new Error('compilation requires 4GB of RAM'); - } - const compile = createCompile(src, { build, emitError: true, transpileOnly: false, preserveEnglish: !!options.preserveEnglish }); - const srcPipe = gulp_1.default.src(`${src}/**`, { base: `${src}` }); - const generator = new MonacoGenerator(false); - if (src === 'src') { - generator.execute(); - } - // mangle: TypeScript to TypeScript - let mangleStream = event_stream_1.default.through(); - if (build && !options.disableMangle) { - let ts2tsMangler = new index_1.Mangler(compile.projectPath, (...data) => (0, fancy_log_1.default)(ansi_colors_1.default.blue('[mangler]'), ...data), { mangleExports: true, manglePrivateFields: true }); - const newContentsByFileName = ts2tsMangler.computeNewFileContents(new Set(['saveState'])); - mangleStream = event_stream_1.default.through(async function write(data) { - const tsNormalPath = ts.normalizePath(data.path); - const newContents = (await newContentsByFileName).get(tsNormalPath); - if (newContents !== undefined) { - data.contents = Buffer.from(newContents.out); - data.sourceMap = newContents.sourceMap && JSON.parse(newContents.sourceMap); - } - this.push(data); - }, async function end() { - // free resources - (await newContentsByFileName).clear(); - this.push(null); - ts2tsMangler = undefined; - }); - } - return srcPipe - .pipe(mangleStream) - .pipe(generator.stream) - .pipe(compile()) - .pipe(gulp_1.default.dest(out)); - }; - task.taskName = `compile-${path_1.default.basename(src)}`; - return task; -} -function watchTask(out, build, srcPath = 'src') { - const task = () => { - const compile = createCompile(srcPath, { build, emitError: false, transpileOnly: false, preserveEnglish: false }); - const src = gulp_1.default.src(`${srcPath}/**`, { base: srcPath }); - const watchSrc = watch(`${srcPath}/**`, { base: srcPath, readDelay: 200 }); - const generator = new MonacoGenerator(true); - generator.execute(); - return watchSrc - .pipe(generator.stream) - .pipe(util.incremental(compile, src, true)) - .pipe(gulp_1.default.dest(out)); - }; - task.taskName = `watch-${path_1.default.basename(out)}`; - return task; -} -const REPO_SRC_FOLDER = path_1.default.join(__dirname, '../../src'); -class MonacoGenerator { - _isWatch; - stream; - _watchedFiles; - _fsProvider; - _declarationResolver; - constructor(isWatch) { - this._isWatch = isWatch; - this.stream = event_stream_1.default.through(); - this._watchedFiles = {}; - const onWillReadFile = (moduleId, filePath) => { - if (!this._isWatch) { - return; - } - if (this._watchedFiles[filePath]) { - return; - } - this._watchedFiles[filePath] = true; - fs_1.default.watchFile(filePath, () => { - this._declarationResolver.invalidateCache(moduleId); - this._executeSoon(); - }); - }; - this._fsProvider = new class extends monacodts.FSProvider { - readFileSync(moduleId, filePath) { - onWillReadFile(moduleId, filePath); - return super.readFileSync(moduleId, filePath); - } - }; - this._declarationResolver = new monacodts.DeclarationResolver(this._fsProvider); - if (this._isWatch) { - fs_1.default.watchFile(monacodts.RECIPE_PATH, () => { - this._executeSoon(); - }); - } - } - _executeSoonTimer = null; - _executeSoon() { - if (this._executeSoonTimer !== null) { - clearTimeout(this._executeSoonTimer); - this._executeSoonTimer = null; - } - this._executeSoonTimer = setTimeout(() => { - this._executeSoonTimer = null; - this.execute(); - }, 20); - } - _run() { - const r = monacodts.run3(this._declarationResolver); - if (!r && !this._isWatch) { - // The build must always be able to generate the monaco.d.ts - throw new Error(`monaco.d.ts generation error - Cannot continue`); - } - return r; - } - _log(message, ...rest) { - (0, fancy_log_1.default)(ansi_colors_1.default.cyan('[monaco.d.ts]'), message, ...rest); - } - execute() { - const startTime = Date.now(); - const result = this._run(); - if (!result) { - // nothing really changed - return; - } - if (result.isTheSame) { - return; - } - fs_1.default.writeFileSync(result.filePath, result.content); - fs_1.default.writeFileSync(path_1.default.join(REPO_SRC_FOLDER, 'vs/editor/common/standalone/standaloneEnums.ts'), result.enums); - this._log(`monaco.d.ts is changed - total time took ${Date.now() - startTime} ms`); - if (!this._isWatch) { - this.stream.emit('error', 'monaco.d.ts is no longer up to date. Please run gulp watch and commit the new file.'); - } - } -} -function generateApiProposalNames() { - let eol; - try { - const src = fs_1.default.readFileSync('src/vs/platform/extensions/common/extensionsApiProposals.ts', 'utf-8'); - const match = /\r?\n/m.exec(src); - eol = match ? match[0] : os_1.default.EOL; - } - catch { - eol = os_1.default.EOL; - } - const pattern = /vscode\.proposed\.([a-zA-Z\d]+)\.d\.ts$/; - const versionPattern = /^\s*\/\/\s*version\s*:\s*(\d+)\s*$/mi; - const proposals = new Map(); - const input = event_stream_1.default.through(); - const output = input - .pipe(util.filter((f) => pattern.test(f.path))) - .pipe(event_stream_1.default.through((f) => { - const name = path_1.default.basename(f.path); - const match = pattern.exec(name); - if (!match) { - return; - } - const proposalName = match[1]; - const contents = f.contents.toString('utf8'); - const versionMatch = versionPattern.exec(contents); - const version = versionMatch ? versionMatch[1] : undefined; - proposals.set(proposalName, { - proposal: `https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.${proposalName}.d.ts`, - version: version ? parseInt(version) : undefined - }); - }, function () { - const names = [...proposals.keys()].sort(); - const contents = [ - '/*---------------------------------------------------------------------------------------------', - ' * Copyright (c) Microsoft Corporation. All rights reserved.', - ' * Licensed under the MIT License. See License.txt in the project root for license information.', - ' *--------------------------------------------------------------------------------------------*/', - '', - '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', - '', - 'const _allApiProposals = {', - `${names.map(proposalName => { - const proposal = proposals.get(proposalName); - return `\t${proposalName}: {${eol}\t\tproposal: '${proposal.proposal}',${eol}${proposal.version ? `\t\tversion: ${proposal.version}${eol}` : ''}\t}`; - }).join(`,${eol}`)}`, - '};', - 'export const allApiProposals = Object.freeze<{ [proposalName: string]: Readonly<{ proposal: string; version?: number }> }>(_allApiProposals);', - 'export type ApiProposalName = keyof typeof _allApiProposals;', - '', - ].join(eol); - this.emit('data', new vinyl_1.default({ - path: 'vs/platform/extensions/common/extensionsApiProposals.ts', - contents: Buffer.from(contents) - })); - this.emit('end'); - })); - return event_stream_1.default.duplex(input, output); -} -const apiProposalNamesReporter = (0, reporter_1.createReporter)('api-proposal-names'); -exports.compileApiProposalNamesTask = task.define('compile-api-proposal-names', () => { - return gulp_1.default.src('src/vscode-dts/**') - .pipe(generateApiProposalNames()) - .pipe(gulp_1.default.dest('src')) - .pipe(apiProposalNamesReporter.end(true)); -}); -exports.watchApiProposalNamesTask = task.define('watch-api-proposal-names', () => { - const task = () => gulp_1.default.src('src/vscode-dts/**') - .pipe(generateApiProposalNames()) - .pipe(apiProposalNamesReporter.end(true)); - return watch('src/vscode-dts/**', { readDelay: 200 }) - .pipe(util.debounce(task)) - .pipe(gulp_1.default.dest('src')); -}); -//# sourceMappingURL=compilation.js.map \ No newline at end of file diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index a1de9f12dfdf9..948c6b4ef4f94 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -7,19 +7,22 @@ import es from 'event-stream'; import fs from 'fs'; import gulp from 'gulp'; import path from 'path'; -import * as monacodts from './monaco-api'; -import * as nls from './nls'; -import { createReporter } from './reporter'; -import * as util from './util'; +import * as monacodts from './monaco-api.ts'; +import * as nls from './nls.ts'; +import { createReporter } from './reporter.ts'; +import * as util from './util.ts'; import fancyLog from 'fancy-log'; import ansiColors from 'ansi-colors'; import os from 'os'; import File from 'vinyl'; -import * as task from './task'; -import { Mangler } from './mangle/index'; -import { RawSourceMap } from 'source-map'; -import ts = require('typescript'); -const watch = require('./watch'); +import * as task from './task.ts'; +import { Mangler } from './mangle/index.ts'; +import type { RawSourceMap } from 'source-map'; +import ts from 'typescript'; +import watch from './watch/index.ts'; +import bom from 'gulp-bom'; +import * as tsb from './tsb/index.ts'; +import sourcemaps from 'gulp-sourcemaps'; // --- gulp-tsb: compile and transpile -------------------------------- @@ -27,7 +30,7 @@ const watch = require('./watch'); const reporter = createReporter(); function getTypeScriptCompilerOptions(src: string): ts.CompilerOptions { - const rootDir = path.join(__dirname, `../../${src}`); + const rootDir = path.join(import.meta.dirname, `../../${src}`); const options: ts.CompilerOptions = {}; options.verbose = false; options.sourceMap = true; @@ -37,7 +40,7 @@ function getTypeScriptCompilerOptions(src: string): ts.CompilerOptions { options.rootDir = rootDir; options.baseUrl = rootDir; options.sourceRoot = util.toFileUri(rootDir); - options.newLine = /\r\n/.test(fs.readFileSync(__filename, 'utf8')) ? 0 : 1; + options.newLine = /\r\n/.test(fs.readFileSync(import.meta.filename, 'utf8')) ? 0 : 1; return options; } @@ -49,11 +52,7 @@ interface ICompileTaskOptions { } export function createCompile(src: string, { build, emitError, transpileOnly, preserveEnglish }: ICompileTaskOptions) { - const tsb = require('./tsb') as typeof import('./tsb'); - const sourcemaps = require('gulp-sourcemaps') as typeof import('gulp-sourcemaps'); - - - const projectPath = path.join(__dirname, '../../', src, 'tsconfig.json'); + const projectPath = path.join(import.meta.dirname, '../../', src, 'tsconfig.json'); const overrideOptions = { ...getTypeScriptCompilerOptions(src), inlineSources: Boolean(build) }; if (!build) { overrideOptions.inlineSourceMap = true; @@ -66,7 +65,6 @@ export function createCompile(src: string, { build, emitError, transpileOnly, pr }, err => reporter(err)); function pipeline(token?: util.ICancellationToken) { - const bom = require('gulp-bom') as typeof import('gulp-bom'); const tsFilter = util.filter(data => /\.ts$/.test(data.path)); const isUtf8Test = (f: File) => /(\/|\\)test(\/|\\).*utf8/.test(f.path); @@ -100,11 +98,11 @@ export function createCompile(src: string, { build, emitError, transpileOnly, pr return pipeline; } -export function transpileTask(src: string, out: string, esbuild: boolean): task.StreamTask { +export function transpileTask(src: string, out: string, esbuild?: boolean): task.StreamTask { const task = () => { - const transpile = createCompile(src, { build: false, emitError: true, transpileOnly: { esbuild }, preserveEnglish: false }); + const transpile = createCompile(src, { build: false, emitError: true, transpileOnly: { esbuild: !!esbuild }, preserveEnglish: false }); const srcPipe = gulp.src(`${src}/**`, { base: `${src}` }); return srcPipe @@ -134,11 +132,11 @@ export function compileTask(src: string, out: string, build: boolean, options: { // mangle: TypeScript to TypeScript let mangleStream = es.through(); if (build && !options.disableMangle) { - let ts2tsMangler = new Mangler(compile.projectPath, (...data) => fancyLog(ansiColors.blue('[mangler]'), ...data), { mangleExports: true, manglePrivateFields: true }); + let ts2tsMangler: Mangler | undefined = new Mangler(compile.projectPath, (...data) => fancyLog(ansiColors.blue('[mangler]'), ...data), { mangleExports: true, manglePrivateFields: true }); const newContentsByFileName = ts2tsMangler.computeNewFileContents(new Set(['saveState'])); mangleStream = es.through(async function write(data: File & { sourceMap?: RawSourceMap }) { type TypeScriptExt = typeof ts & { normalizePath(path: string): string }; - const tsNormalPath = (ts).normalizePath(data.path); + const tsNormalPath = (ts as TypeScriptExt).normalizePath(data.path); const newContents = (await newContentsByFileName).get(tsNormalPath); if (newContents !== undefined) { data.contents = Buffer.from(newContents.out); @@ -150,7 +148,7 @@ export function compileTask(src: string, out: string, build: boolean, options: { (await newContentsByFileName).clear(); this.push(null); - (ts2tsMangler) = undefined; + ts2tsMangler = undefined; }); } @@ -185,7 +183,7 @@ export function watchTask(out: string, build: boolean, srcPath: string = 'src'): return task; } -const REPO_SRC_FOLDER = path.join(__dirname, '../../src'); +const REPO_SRC_FOLDER = path.join(import.meta.dirname, '../../src'); class MonacoGenerator { private readonly _isWatch: boolean; @@ -249,7 +247,7 @@ class MonacoGenerator { return r; } - private _log(message: any, ...rest: any[]): void { + private _log(message: string, ...rest: unknown[]): void { fancyLog(ansiColors.cyan('[monaco.d.ts]'), message, ...rest); } @@ -301,7 +299,7 @@ function generateApiProposalNames() { const proposalName = match[1]; - const contents = f.contents.toString('utf8'); + const contents = f.contents!.toString('utf8'); const versionMatch = versionPattern.exec(contents); const version = versionMatch ? versionMatch[1] : undefined; diff --git a/build/lib/date.js b/build/lib/date.js deleted file mode 100644 index 1ed884fb7ee7a..0000000000000 --- a/build/lib/date.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.writeISODate = writeISODate; -exports.readISODate = readISODate; -const path_1 = __importDefault(require("path")); -const fs_1 = __importDefault(require("fs")); -const root = path_1.default.join(__dirname, '..', '..'); -/** - * Writes a `outDir/date` file with the contents of the build - * so that other tasks during the build process can use it and - * all use the same date. - */ -function writeISODate(outDir) { - const result = () => new Promise((resolve, _) => { - const outDirectory = path_1.default.join(root, outDir); - fs_1.default.mkdirSync(outDirectory, { recursive: true }); - const date = new Date().toISOString(); - fs_1.default.writeFileSync(path_1.default.join(outDirectory, 'date'), date, 'utf8'); - resolve(); - }); - result.taskName = 'build-date-file'; - return result; -} -function readISODate(outDir) { - const outDirectory = path_1.default.join(root, outDir); - return fs_1.default.readFileSync(path_1.default.join(outDirectory, 'date'), 'utf8'); -} -//# sourceMappingURL=date.js.map \ No newline at end of file diff --git a/build/lib/date.ts b/build/lib/date.ts index 8a9331789520a..9c20c9eeb22ab 100644 --- a/build/lib/date.ts +++ b/build/lib/date.ts @@ -6,7 +6,7 @@ import path from 'path'; import fs from 'fs'; -const root = path.join(__dirname, '..', '..'); +const root = path.join(import.meta.dirname, '..', '..'); /** * Writes a `outDir/date` file with the contents of the build diff --git a/build/lib/dependencies.js b/build/lib/dependencies.js deleted file mode 100644 index 04a09f98708af..0000000000000 --- a/build/lib/dependencies.js +++ /dev/null @@ -1,57 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getProductionDependencies = getProductionDependencies; -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const child_process_1 = __importDefault(require("child_process")); -const root = fs_1.default.realpathSync(path_1.default.dirname(path_1.default.dirname(__dirname))); -function getNpmProductionDependencies(folder) { - let raw; - try { - raw = child_process_1.default.execSync('npm ls --all --omit=dev --parseable', { cwd: folder, encoding: 'utf8', env: { ...process.env, NODE_ENV: 'production' }, stdio: [null, null, null] }); - } - catch (err) { - const regex = /^npm ERR! .*$/gm; - let match; - while (match = regex.exec(err.message)) { - if (/ELSPROBLEMS/.test(match[0])) { - continue; - } - else if (/invalid: xterm/.test(match[0])) { - continue; - } - else if (/A complete log of this run/.test(match[0])) { - continue; - } - else { - throw err; - } - } - raw = err.stdout; - } - return raw.split(/\r?\n/).filter(line => { - return !!line.trim() && path_1.default.relative(root, line) !== path_1.default.relative(root, folder); - }); -} -function getProductionDependencies(folderPath) { - const result = getNpmProductionDependencies(folderPath); - // Account for distro npm dependencies - const realFolderPath = fs_1.default.realpathSync(folderPath); - const relativeFolderPath = path_1.default.relative(root, realFolderPath); - const distroFolderPath = `${root}/.build/distro/npm/${relativeFolderPath}`; - if (fs_1.default.existsSync(distroFolderPath)) { - result.push(...getNpmProductionDependencies(distroFolderPath)); - } - return [...new Set(result)]; -} -if (require.main === module) { - console.log(JSON.stringify(getProductionDependencies(root), null, ' ')); -} -//# sourceMappingURL=dependencies.js.map \ No newline at end of file diff --git a/build/lib/dependencies.ts b/build/lib/dependencies.ts index a5bc70088a7fb..ed7cbfbef026d 100644 --- a/build/lib/dependencies.ts +++ b/build/lib/dependencies.ts @@ -6,7 +6,7 @@ import fs from 'fs'; import path from 'path'; import cp from 'child_process'; -const root = fs.realpathSync(path.dirname(path.dirname(__dirname))); +const root = fs.realpathSync(path.dirname(path.dirname(import.meta.dirname))); function getNpmProductionDependencies(folder: string): string[] { let raw: string; @@ -51,6 +51,6 @@ export function getProductionDependencies(folderPath: string): string[] { return [...new Set(result)]; } -if (require.main === module) { +if (import.meta.main) { console.log(JSON.stringify(getProductionDependencies(root), null, ' ')); } diff --git a/build/lib/electron.js b/build/lib/electron.js deleted file mode 100644 index 56992d8a7f71b..0000000000000 --- a/build/lib/electron.js +++ /dev/null @@ -1,258 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.config = void 0; -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const vinyl_fs_1 = __importDefault(require("vinyl-fs")); -const gulp_filter_1 = __importDefault(require("gulp-filter")); -const util = __importStar(require("./util")); -const getVersion_1 = require("./getVersion"); -function isDocumentSuffix(str) { - return str === 'document' || str === 'script' || str === 'file' || str === 'source code'; -} -const root = path_1.default.dirname(path_1.default.dirname(__dirname)); -const product = JSON.parse(fs_1.default.readFileSync(path_1.default.join(root, 'product.json'), 'utf8')); -const commit = (0, getVersion_1.getVersion)(root); -function createTemplate(input) { - return (params) => { - return input.replace(/<%=\s*([^\s]+)\s*%>/g, (match, key) => { - return params[key] || match; - }); - }; -} -const darwinCreditsTemplate = product.darwinCredits && createTemplate(fs_1.default.readFileSync(path_1.default.join(root, product.darwinCredits), 'utf8')); -/** - * Generate a `DarwinDocumentType` given a list of file extensions, an icon name, and an optional suffix or file type name. - * @param extensions A list of file extensions, such as `['bat', 'cmd']` - * @param icon A sentence-cased file type name that matches the lowercase name of a darwin icon resource. - * For example, `'HTML'` instead of `'html'`, or `'Java'` instead of `'java'`. - * This parameter is lowercased before it is used to reference an icon file. - * @param nameOrSuffix An optional suffix or a string to use as the file type. If a suffix is provided, - * it is used with the icon parameter to generate a file type string. If nothing is provided, - * `'document'` is used with the icon parameter to generate file type string. - * - * For example, if you call `darwinBundleDocumentType(..., 'HTML')`, the resulting file type is `"HTML document"`, - * and the `'html'` darwin icon is used. - * - * If you call `darwinBundleDocumentType(..., 'Javascript', 'file')`, the resulting file type is `"Javascript file"`. - * and the `'javascript'` darwin icon is used. - * - * If you call `darwinBundleDocumentType(..., 'bat', 'Windows command script')`, the file type is `"Windows command script"`, - * and the `'bat'` darwin icon is used. - */ -function darwinBundleDocumentType(extensions, icon, nameOrSuffix, utis) { - // If given a suffix, generate a name from it. If not given anything, default to 'document' - if (isDocumentSuffix(nameOrSuffix) || !nameOrSuffix) { - nameOrSuffix = icon.charAt(0).toUpperCase() + icon.slice(1) + ' ' + (nameOrSuffix ?? 'document'); - } - return { - name: nameOrSuffix, - role: 'Editor', - ostypes: ['TEXT', 'utxt', 'TUTX', '****'], - extensions, - iconFile: 'resources/darwin/' + icon.toLowerCase() + '.icns', - utis - }; -} -/** - * Generate several `DarwinDocumentType`s with unique names and a shared icon. - * @param types A map of file type names to their associated file extensions. - * @param icon A darwin icon resource to use. For example, `'HTML'` would refer to `resources/darwin/html.icns` - * - * Examples: - * ``` - * darwinBundleDocumentTypes({ 'C header file': 'h', 'C source code': 'c' },'c') - * darwinBundleDocumentTypes({ 'React source code': ['jsx', 'tsx'] }, 'react') - * ``` - */ -function darwinBundleDocumentTypes(types, icon) { - return Object.keys(types).map((name) => { - const extensions = types[name]; - return { - name, - role: 'Editor', - ostypes: ['TEXT', 'utxt', 'TUTX', '****'], - extensions: Array.isArray(extensions) ? extensions : [extensions], - iconFile: 'resources/darwin/' + icon + '.icns' - }; - }); -} -const { electronVersion, msBuildId } = util.getElectronVersion(); -exports.config = { - version: electronVersion, - tag: product.electronRepository ? `v${electronVersion}-${msBuildId}` : undefined, - productAppName: product.nameLong, - companyName: 'Microsoft Corporation', - copyright: 'Copyright (C) 2024 Microsoft. All rights reserved', - darwinIcon: 'resources/darwin/code.icns', - darwinBundleIdentifier: product.darwinBundleIdentifier, - darwinApplicationCategoryType: 'public.app-category.developer-tools', - darwinHelpBookFolder: 'VS Code HelpBook', - darwinHelpBookName: 'VS Code HelpBook', - darwinBundleDocumentTypes: [ - ...darwinBundleDocumentTypes({ 'C header file': 'h', 'C source code': 'c' }, 'c'), - ...darwinBundleDocumentTypes({ 'Git configuration file': ['gitattributes', 'gitconfig', 'gitignore'] }, 'config'), - ...darwinBundleDocumentTypes({ 'HTML template document': ['asp', 'aspx', 'cshtml', 'jshtm', 'jsp', 'phtml', 'shtml'] }, 'html'), - darwinBundleDocumentType(['bat', 'cmd'], 'bat', 'Windows command script'), - darwinBundleDocumentType(['bowerrc'], 'Bower'), - darwinBundleDocumentType(['config', 'editorconfig', 'ini', 'cfg'], 'config', 'Configuration file'), - darwinBundleDocumentType(['hh', 'hpp', 'hxx', 'h++'], 'cpp', 'C++ header file'), - darwinBundleDocumentType(['cc', 'cpp', 'cxx', 'c++'], 'cpp', 'C++ source code'), - darwinBundleDocumentType(['m'], 'default', 'Objective-C source code'), - darwinBundleDocumentType(['mm'], 'cpp', 'Objective-C++ source code'), - darwinBundleDocumentType(['cs', 'csx'], 'csharp', 'C# source code'), - darwinBundleDocumentType(['css'], 'css', 'CSS'), - darwinBundleDocumentType(['go'], 'go', 'Go source code'), - darwinBundleDocumentType(['htm', 'html', 'xhtml'], 'HTML'), - darwinBundleDocumentType(['jade'], 'Jade'), - darwinBundleDocumentType(['jav', 'java'], 'Java'), - darwinBundleDocumentType(['js', 'jscsrc', 'jshintrc', 'mjs', 'cjs'], 'Javascript', 'file'), - darwinBundleDocumentType(['json'], 'JSON'), - darwinBundleDocumentType(['less'], 'Less'), - darwinBundleDocumentType(['markdown', 'md', 'mdoc', 'mdown', 'mdtext', 'mdtxt', 'mdwn', 'mkd', 'mkdn'], 'Markdown'), - darwinBundleDocumentType(['php'], 'PHP', 'source code'), - darwinBundleDocumentType(['ps1', 'psd1', 'psm1'], 'Powershell', 'script'), - darwinBundleDocumentType(['py', 'pyi'], 'Python', 'script'), - darwinBundleDocumentType(['gemspec', 'rb', 'erb'], 'Ruby', 'source code'), - darwinBundleDocumentType(['scss', 'sass'], 'SASS', 'file'), - darwinBundleDocumentType(['sql'], 'SQL', 'script'), - darwinBundleDocumentType(['ts'], 'TypeScript', 'file'), - darwinBundleDocumentType(['tsx', 'jsx'], 'React', 'source code'), - darwinBundleDocumentType(['vue'], 'Vue', 'source code'), - darwinBundleDocumentType(['ascx', 'csproj', 'dtd', 'plist', 'wxi', 'wxl', 'wxs', 'xml', 'xaml'], 'XML'), - darwinBundleDocumentType(['eyaml', 'eyml', 'yaml', 'yml'], 'YAML'), - darwinBundleDocumentType([ - 'bash', 'bash_login', 'bash_logout', 'bash_profile', 'bashrc', - 'profile', 'rhistory', 'rprofile', 'sh', 'zlogin', 'zlogout', - 'zprofile', 'zsh', 'zshenv', 'zshrc' - ], 'Shell', 'script'), - // Default icon with specified names - ...darwinBundleDocumentTypes({ - 'Clojure source code': ['clj', 'cljs', 'cljx', 'clojure'], - 'VS Code workspace file': 'code-workspace', - 'CoffeeScript source code': 'coffee', - 'Comma Separated Values': 'csv', - 'CMake script': 'cmake', - 'Dart script': 'dart', - 'Diff file': 'diff', - 'Dockerfile': 'dockerfile', - 'Gradle file': 'gradle', - 'Groovy script': 'groovy', - 'Makefile': ['makefile', 'mk'], - 'Lua script': 'lua', - 'Pug document': 'pug', - 'Jupyter': 'ipynb', - 'Lockfile': 'lock', - 'Log file': 'log', - 'Plain Text File': 'txt', - 'Xcode project file': 'xcodeproj', - 'Xcode workspace file': 'xcworkspace', - 'Visual Basic script': 'vb', - 'R source code': 'r', - 'Rust source code': 'rs', - 'Restructured Text document': 'rst', - 'LaTeX document': ['tex', 'cls'], - 'F# source code': 'fs', - 'F# signature file': 'fsi', - 'F# script': ['fsx', 'fsscript'], - 'SVG document': ['svg'], - 'TOML document': 'toml', - 'Swift source code': 'swift', - }, 'default'), - // Default icon with default name - darwinBundleDocumentType([ - 'containerfile', 'ctp', 'dot', 'edn', 'handlebars', 'hbs', 'ml', 'mli', - 'pl', 'pl6', 'pm', 'pm6', 'pod', 'pp', 'properties', 'psgi', 'rt', 't' - ], 'default', product.nameLong + ' document'), - // Folder support () - darwinBundleDocumentType([], 'default', 'Folder', ['public.folder']) - ], - darwinBundleURLTypes: [{ - role: 'Viewer', - name: product.nameLong, - urlSchemes: [product.urlProtocol] - }], - darwinForceDarkModeSupport: true, - darwinCredits: darwinCreditsTemplate ? Buffer.from(darwinCreditsTemplate({ commit: commit, date: new Date().toISOString() })) : undefined, - linuxExecutableName: product.applicationName, - winIcon: 'resources/win32/code.ico', - token: process.env['GITHUB_TOKEN'], - repo: product.electronRepository || undefined, - validateChecksum: true, - checksumFile: path_1.default.join(root, 'build', 'checksums', 'electron.txt'), -}; -function getElectron(arch) { - return () => { - const electron = require('@vscode/gulp-electron'); - const json = require('gulp-json-editor'); - const electronOpts = { - ...exports.config, - platform: process.platform, - arch: arch === 'armhf' ? 'arm' : arch, - ffmpegChromium: false, - keepDefaultApp: true - }; - return vinyl_fs_1.default.src('package.json') - .pipe(json({ name: product.nameShort })) - .pipe(electron(electronOpts)) - .pipe((0, gulp_filter_1.default)(['**', '!**/app/package.json'])) - .pipe(vinyl_fs_1.default.dest('.build/electron')); - }; -} -async function main(arch = process.arch) { - const version = electronVersion; - const electronPath = path_1.default.join(root, '.build', 'electron'); - const versionFile = path_1.default.join(electronPath, 'version'); - const isUpToDate = fs_1.default.existsSync(versionFile) && fs_1.default.readFileSync(versionFile, 'utf8') === `${version}`; - if (!isUpToDate) { - await util.rimraf(electronPath)(); - await util.streamToPromise(getElectron(arch)()); - } -} -if (require.main === module) { - main(process.argv[2]).catch(err => { - console.error(err); - process.exit(1); - }); -} -//# sourceMappingURL=electron.js.map \ No newline at end of file diff --git a/build/lib/electron.ts b/build/lib/electron.ts index 3bb047dfceeb6..8cc36de49eace 100644 --- a/build/lib/electron.ts +++ b/build/lib/electron.ts @@ -7,8 +7,10 @@ import fs from 'fs'; import path from 'path'; import vfs from 'vinyl-fs'; import filter from 'gulp-filter'; -import * as util from './util'; -import { getVersion } from './getVersion'; +import * as util from './util.ts'; +import { getVersion } from './getVersion.ts'; +import electron from '@vscode/gulp-electron'; +import json from 'gulp-json-editor'; type DarwinDocumentSuffix = 'document' | 'script' | 'file' | 'source code'; type DarwinDocumentType = { @@ -24,7 +26,7 @@ function isDocumentSuffix(str?: string): str is DarwinDocumentSuffix { return str === 'document' || str === 'script' || str === 'file' || str === 'source code'; } -const root = path.dirname(path.dirname(__dirname)); +const root = path.dirname(path.dirname(import.meta.dirname)); const product = JSON.parse(fs.readFileSync(path.join(root, 'product.json'), 'utf8')); const commit = getVersion(root); @@ -104,7 +106,7 @@ export const config = { tag: product.electronRepository ? `v${electronVersion}-${msBuildId}` : undefined, productAppName: product.nameLong, companyName: 'Microsoft Corporation', - copyright: 'Copyright (C) 2024 Microsoft. All rights reserved', + copyright: 'Copyright (C) 2025 Microsoft. All rights reserved', darwinIcon: 'resources/darwin/code.icns', darwinBundleIdentifier: product.darwinBundleIdentifier, darwinApplicationCategoryType: 'public.app-category.developer-tools', @@ -205,9 +207,6 @@ export const config = { function getElectron(arch: string): () => NodeJS.ReadWriteStream { return () => { - const electron = require('@vscode/gulp-electron'); - const json = require('gulp-json-editor') as typeof import('gulp-json-editor'); - const electronOpts = { ...config, platform: process.platform, @@ -236,7 +235,7 @@ async function main(arch: string = process.arch): Promise { } } -if (require.main === module) { +if (import.meta.main) { main(process.argv[2]).catch(err => { console.error(err); process.exit(1); diff --git a/build/lib/extensions.js b/build/lib/extensions.js deleted file mode 100644 index 1d2f95299c85b..0000000000000 --- a/build/lib/extensions.js +++ /dev/null @@ -1,621 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.fromMarketplace = fromMarketplace; -exports.fromVsix = fromVsix; -exports.fromGithub = fromGithub; -exports.packageNonNativeLocalExtensionsStream = packageNonNativeLocalExtensionsStream; -exports.packageNativeLocalExtensionsStream = packageNativeLocalExtensionsStream; -exports.packageAllLocalExtensionsStream = packageAllLocalExtensionsStream; -exports.packageMarketplaceExtensionsStream = packageMarketplaceExtensionsStream; -exports.scanBuiltinExtensions = scanBuiltinExtensions; -exports.translatePackageJSON = translatePackageJSON; -exports.webpackExtensions = webpackExtensions; -exports.buildExtensionMedia = buildExtensionMedia; -const event_stream_1 = __importDefault(require("event-stream")); -const fs_1 = __importDefault(require("fs")); -const child_process_1 = __importDefault(require("child_process")); -const glob_1 = __importDefault(require("glob")); -const gulp_1 = __importDefault(require("gulp")); -const path_1 = __importDefault(require("path")); -const crypto_1 = __importDefault(require("crypto")); -const vinyl_1 = __importDefault(require("vinyl")); -const stats_1 = require("./stats"); -const util2 = __importStar(require("./util")); -const gulp_filter_1 = __importDefault(require("gulp-filter")); -const gulp_rename_1 = __importDefault(require("gulp-rename")); -const fancy_log_1 = __importDefault(require("fancy-log")); -const ansi_colors_1 = __importDefault(require("ansi-colors")); -const gulp_buffer_1 = __importDefault(require("gulp-buffer")); -const jsoncParser = __importStar(require("jsonc-parser")); -const dependencies_1 = require("./dependencies"); -const builtInExtensions_1 = require("./builtInExtensions"); -const getVersion_1 = require("./getVersion"); -const fetch_1 = require("./fetch"); -const vzip = require('gulp-vinyl-zip'); -const root = path_1.default.dirname(path_1.default.dirname(__dirname)); -const commit = (0, getVersion_1.getVersion)(root); -const sourceMappingURLBase = `https://main.vscode-cdn.net/sourcemaps/${commit}`; -function minifyExtensionResources(input) { - const jsonFilter = (0, gulp_filter_1.default)(['**/*.json', '**/*.code-snippets'], { restore: true }); - return input - .pipe(jsonFilter) - .pipe((0, gulp_buffer_1.default)()) - .pipe(event_stream_1.default.mapSync((f) => { - const errors = []; - const value = jsoncParser.parse(f.contents.toString('utf8'), errors, { allowTrailingComma: true }); - if (errors.length === 0) { - // file parsed OK => just stringify to drop whitespace and comments - f.contents = Buffer.from(JSON.stringify(value)); - } - return f; - })) - .pipe(jsonFilter.restore); -} -function updateExtensionPackageJSON(input, update) { - const packageJsonFilter = (0, gulp_filter_1.default)('extensions/*/package.json', { restore: true }); - return input - .pipe(packageJsonFilter) - .pipe((0, gulp_buffer_1.default)()) - .pipe(event_stream_1.default.mapSync((f) => { - const data = JSON.parse(f.contents.toString('utf8')); - f.contents = Buffer.from(JSON.stringify(update(data))); - return f; - })) - .pipe(packageJsonFilter.restore); -} -function fromLocal(extensionPath, forWeb, disableMangle) { - const esm = JSON.parse(fs_1.default.readFileSync(path_1.default.join(extensionPath, 'package.json'), 'utf8')).type === 'module'; - const webpackConfigFileName = forWeb - ? `extension-browser.webpack.config.${!esm ? 'js' : 'cjs'}` - : `extension.webpack.config.${!esm ? 'js' : 'cjs'}`; - const isWebPacked = fs_1.default.existsSync(path_1.default.join(extensionPath, webpackConfigFileName)); - let input = isWebPacked - ? fromLocalWebpack(extensionPath, webpackConfigFileName, disableMangle) - : fromLocalNormal(extensionPath); - if (isWebPacked) { - input = updateExtensionPackageJSON(input, (data) => { - delete data.scripts; - delete data.dependencies; - delete data.devDependencies; - if (data.main) { - data.main = data.main.replace('/out/', '/dist/'); - } - return data; - }); - } - return input; -} -function fromLocalWebpack(extensionPath, webpackConfigFileName, disableMangle) { - const vsce = require('@vscode/vsce'); - const webpack = require('webpack'); - const webpackGulp = require('webpack-stream'); - const result = event_stream_1.default.through(); - const packagedDependencies = []; - const packageJsonConfig = require(path_1.default.join(extensionPath, 'package.json')); - if (packageJsonConfig.dependencies) { - const webpackRootConfig = require(path_1.default.join(extensionPath, webpackConfigFileName)); - for (const key in webpackRootConfig.externals) { - if (key in packageJsonConfig.dependencies) { - packagedDependencies.push(key); - } - } - } - // TODO: add prune support based on packagedDependencies to vsce.PackageManager.Npm similar - // to vsce.PackageManager.Yarn. - // A static analysis showed there are no webpack externals that are dependencies of the current - // local extensions so we can use the vsce.PackageManager.None config to ignore dependencies list - // as a temporary workaround. - vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.None, packagedDependencies }).then(fileNames => { - const files = fileNames - .map(fileName => path_1.default.join(extensionPath, fileName)) - .map(filePath => new vinyl_1.default({ - path: filePath, - stat: fs_1.default.statSync(filePath), - base: extensionPath, - contents: fs_1.default.createReadStream(filePath) - })); - // check for a webpack configuration files, then invoke webpack - // and merge its output with the files stream. - const webpackConfigLocations = glob_1.default.sync(path_1.default.join(extensionPath, '**', webpackConfigFileName), { ignore: ['**/node_modules'] }); - const webpackStreams = webpackConfigLocations.flatMap(webpackConfigPath => { - const webpackDone = (err, stats) => { - (0, fancy_log_1.default)(`Bundled extension: ${ansi_colors_1.default.yellow(path_1.default.join(path_1.default.basename(extensionPath), path_1.default.relative(extensionPath, webpackConfigPath)))}...`); - if (err) { - result.emit('error', err); - } - const { compilation } = stats; - if (compilation.errors.length > 0) { - result.emit('error', compilation.errors.join('\n')); - } - if (compilation.warnings.length > 0) { - result.emit('error', compilation.warnings.join('\n')); - } - }; - const exportedConfig = require(webpackConfigPath); - return (Array.isArray(exportedConfig) ? exportedConfig : [exportedConfig]).map(config => { - const webpackConfig = { - ...config, - ...{ mode: 'production' } - }; - if (disableMangle) { - if (Array.isArray(config.module.rules)) { - for (const rule of config.module.rules) { - if (Array.isArray(rule.use)) { - for (const use of rule.use) { - if (String(use.loader).endsWith('mangle-loader.js')) { - use.options.disabled = true; - } - } - } - } - } - } - const relativeOutputPath = path_1.default.relative(extensionPath, webpackConfig.output.path); - return webpackGulp(webpackConfig, webpack, webpackDone) - .pipe(event_stream_1.default.through(function (data) { - data.stat = data.stat || {}; - data.base = extensionPath; - this.emit('data', data); - })) - .pipe(event_stream_1.default.through(function (data) { - // source map handling: - // * rewrite sourceMappingURL - // * save to disk so that upload-task picks this up - if (path_1.default.extname(data.basename) === '.js') { - const contents = data.contents.toString('utf8'); - data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { - return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path_1.default.basename(extensionPath)}/${relativeOutputPath}/${g1}`; - }), 'utf8'); - } - this.emit('data', data); - })); - }); - }); - event_stream_1.default.merge(...webpackStreams, event_stream_1.default.readArray(files)) - // .pipe(es.through(function (data) { - // // debug - // console.log('out', data.path, data.contents.length); - // this.emit('data', data); - // })) - .pipe(result); - }).catch(err => { - console.error(extensionPath); - console.error(packagedDependencies); - result.emit('error', err); - }); - return result.pipe((0, stats_1.createStatsStream)(path_1.default.basename(extensionPath))); -} -function fromLocalNormal(extensionPath) { - const vsce = require('@vscode/vsce'); - const result = event_stream_1.default.through(); - vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.Npm }) - .then(fileNames => { - const files = fileNames - .map(fileName => path_1.default.join(extensionPath, fileName)) - .map(filePath => new vinyl_1.default({ - path: filePath, - stat: fs_1.default.statSync(filePath), - base: extensionPath, - contents: fs_1.default.createReadStream(filePath) - })); - event_stream_1.default.readArray(files).pipe(result); - }) - .catch(err => result.emit('error', err)); - return result.pipe((0, stats_1.createStatsStream)(path_1.default.basename(extensionPath))); -} -const userAgent = 'VSCode Build'; -const baseHeaders = { - 'X-Market-Client-Id': 'VSCode Build', - 'User-Agent': userAgent, - 'X-Market-User-Id': '291C1CD0-051A-4123-9B4B-30D60EF52EE2', -}; -function fromMarketplace(serviceUrl, { name: extensionName, version, sha256, metadata }) { - const json = require('gulp-json-editor'); - const [publisher, name] = extensionName.split('.'); - const url = `${serviceUrl}/publishers/${publisher}/vsextensions/${name}/${version}/vspackage`; - (0, fancy_log_1.default)('Downloading extension:', ansi_colors_1.default.yellow(`${extensionName}@${version}`), '...'); - const packageJsonFilter = (0, gulp_filter_1.default)('package.json', { restore: true }); - return (0, fetch_1.fetchUrls)('', { - base: url, - nodeFetchOptions: { - headers: baseHeaders - }, - checksumSha256: sha256 - }) - .pipe(vzip.src()) - .pipe((0, gulp_filter_1.default)('extension/**')) - .pipe((0, gulp_rename_1.default)(p => p.dirname = p.dirname.replace(/^extension\/?/, ''))) - .pipe(packageJsonFilter) - .pipe((0, gulp_buffer_1.default)()) - .pipe(json({ __metadata: metadata })) - .pipe(packageJsonFilter.restore); -} -function fromVsix(vsixPath, { name: extensionName, version, sha256, metadata }) { - const json = require('gulp-json-editor'); - (0, fancy_log_1.default)('Using local VSIX for extension:', ansi_colors_1.default.yellow(`${extensionName}@${version}`), '...'); - const packageJsonFilter = (0, gulp_filter_1.default)('package.json', { restore: true }); - return gulp_1.default.src(vsixPath) - .pipe((0, gulp_buffer_1.default)()) - .pipe(event_stream_1.default.mapSync((f) => { - const hash = crypto_1.default.createHash('sha256'); - hash.update(f.contents); - const checksum = hash.digest('hex'); - if (checksum !== sha256) { - throw new Error(`Checksum mismatch for ${vsixPath} (expected ${sha256}, actual ${checksum}))`); - } - return f; - })) - .pipe(vzip.src()) - .pipe((0, gulp_filter_1.default)('extension/**')) - .pipe((0, gulp_rename_1.default)(p => p.dirname = p.dirname.replace(/^extension\/?/, ''))) - .pipe(packageJsonFilter) - .pipe((0, gulp_buffer_1.default)()) - .pipe(json({ __metadata: metadata })) - .pipe(packageJsonFilter.restore); -} -function fromGithub({ name, version, repo, sha256, metadata }) { - const json = require('gulp-json-editor'); - (0, fancy_log_1.default)('Downloading extension from GH:', ansi_colors_1.default.yellow(`${name}@${version}`), '...'); - const packageJsonFilter = (0, gulp_filter_1.default)('package.json', { restore: true }); - return (0, fetch_1.fetchGithub)(new URL(repo).pathname, { - version, - name: name => name.endsWith('.vsix'), - checksumSha256: sha256 - }) - .pipe((0, gulp_buffer_1.default)()) - .pipe(vzip.src()) - .pipe((0, gulp_filter_1.default)('extension/**')) - .pipe((0, gulp_rename_1.default)(p => p.dirname = p.dirname.replace(/^extension\/?/, ''))) - .pipe(packageJsonFilter) - .pipe((0, gulp_buffer_1.default)()) - .pipe(json({ __metadata: metadata })) - .pipe(packageJsonFilter.restore); -} -/** - * All extensions that are known to have some native component and thus must be built on the - * platform that is being built. - */ -const nativeExtensions = [ - 'microsoft-authentication', -]; -const excludedExtensions = [ - 'vscode-api-tests', - 'vscode-colorize-tests', - 'vscode-colorize-perf-tests', - 'vscode-test-resolver', - 'ms-vscode.node-debug', - 'ms-vscode.node-debug2', -]; -const marketplaceWebExtensionsExclude = new Set([ - 'ms-vscode.node-debug', - 'ms-vscode.node-debug2', - 'ms-vscode.js-debug-companion', - 'ms-vscode.js-debug', - 'ms-vscode.vscode-js-profile-table' -]); -const productJson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '../../product.json'), 'utf8')); -const builtInExtensions = productJson.builtInExtensions || []; -const webBuiltInExtensions = productJson.webBuiltInExtensions || []; -/** - * Loosely based on `getExtensionKind` from `src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts` - */ -function isWebExtension(manifest) { - if (Boolean(manifest.browser)) { - return true; - } - if (Boolean(manifest.main)) { - return false; - } - // neither browser nor main - if (typeof manifest.extensionKind !== 'undefined') { - const extensionKind = Array.isArray(manifest.extensionKind) ? manifest.extensionKind : [manifest.extensionKind]; - if (extensionKind.indexOf('web') >= 0) { - return true; - } - } - if (typeof manifest.contributes !== 'undefined') { - for (const id of ['debuggers', 'terminal', 'typescriptServerPlugins']) { - if (manifest.contributes.hasOwnProperty(id)) { - return false; - } - } - } - return true; -} -/** - * Package local extensions that are known to not have native dependencies. Mutually exclusive to {@link packageNativeLocalExtensionsStream}. - * @param forWeb build the extensions that have web targets - * @param disableMangle disable the mangler - * @returns a stream - */ -function packageNonNativeLocalExtensionsStream(forWeb, disableMangle) { - return doPackageLocalExtensionsStream(forWeb, disableMangle, false); -} -/** - * Package local extensions that are known to have native dependencies. Mutually exclusive to {@link packageNonNativeLocalExtensionsStream}. - * @note it's possible that the extension does not have native dependencies for the current platform, especially if building for the web, - * but we simplify the logic here by having a flat list of extensions (See {@link nativeExtensions}) that are known to have native - * dependencies on some platform and thus should be packaged on the platform that they are building for. - * @param forWeb build the extensions that have web targets - * @param disableMangle disable the mangler - * @returns a stream - */ -function packageNativeLocalExtensionsStream(forWeb, disableMangle) { - return doPackageLocalExtensionsStream(forWeb, disableMangle, true); -} -/** - * Package all the local extensions... both those that are known to have native dependencies and those that are not. - * @param forWeb build the extensions that have web targets - * @param disableMangle disable the mangler - * @returns a stream - */ -function packageAllLocalExtensionsStream(forWeb, disableMangle) { - return event_stream_1.default.merge([ - packageNonNativeLocalExtensionsStream(forWeb, disableMangle), - packageNativeLocalExtensionsStream(forWeb, disableMangle) - ]); -} -/** - * @param forWeb build the extensions that have web targets - * @param disableMangle disable the mangler - * @param native build the extensions that are marked as having native dependencies - */ -function doPackageLocalExtensionsStream(forWeb, disableMangle, native) { - const nativeExtensionsSet = new Set(nativeExtensions); - const localExtensionsDescriptions = (glob_1.default.sync('extensions/*/package.json') - .map(manifestPath => { - const absoluteManifestPath = path_1.default.join(root, manifestPath); - const extensionPath = path_1.default.dirname(path_1.default.join(root, manifestPath)); - const extensionName = path_1.default.basename(extensionPath); - return { name: extensionName, path: extensionPath, manifestPath: absoluteManifestPath }; - }) - .filter(({ name }) => native ? nativeExtensionsSet.has(name) : !nativeExtensionsSet.has(name)) - .filter(({ name }) => excludedExtensions.indexOf(name) === -1) - .filter(({ name }) => builtInExtensions.every(b => b.name !== name)) - .filter(({ manifestPath }) => (forWeb ? isWebExtension(require(manifestPath)) : true))); - const localExtensionsStream = minifyExtensionResources(event_stream_1.default.merge(...localExtensionsDescriptions.map(extension => { - return fromLocal(extension.path, forWeb, disableMangle) - .pipe((0, gulp_rename_1.default)(p => p.dirname = `extensions/${extension.name}/${p.dirname}`)); - }))); - let result; - if (forWeb) { - result = localExtensionsStream; - } - else { - // also include shared production node modules - const productionDependencies = (0, dependencies_1.getProductionDependencies)('extensions/'); - const dependenciesSrc = productionDependencies.map(d => path_1.default.relative(root, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat(); - result = event_stream_1.default.merge(localExtensionsStream, gulp_1.default.src(dependenciesSrc, { base: '.' }) - .pipe(util2.cleanNodeModules(path_1.default.join(root, 'build', '.moduleignore'))) - .pipe(util2.cleanNodeModules(path_1.default.join(root, 'build', `.moduleignore.${process.platform}`)))); - } - return (result - .pipe(util2.setExecutableBit(['**/*.sh']))); -} -function packageMarketplaceExtensionsStream(forWeb) { - const marketplaceExtensionsDescriptions = [ - ...builtInExtensions.filter(({ name }) => (forWeb ? !marketplaceWebExtensionsExclude.has(name) : true)), - ...(forWeb ? webBuiltInExtensions : []) - ]; - const marketplaceExtensionsStream = minifyExtensionResources(event_stream_1.default.merge(...marketplaceExtensionsDescriptions - .map(extension => { - const src = (0, builtInExtensions_1.getExtensionStream)(extension).pipe((0, gulp_rename_1.default)(p => p.dirname = `extensions/${p.dirname}`)); - return updateExtensionPackageJSON(src, (data) => { - delete data.scripts; - delete data.dependencies; - delete data.devDependencies; - return data; - }); - }))); - return (marketplaceExtensionsStream - .pipe(util2.setExecutableBit(['**/*.sh']))); -} -function scanBuiltinExtensions(extensionsRoot, exclude = []) { - const scannedExtensions = []; - try { - const extensionsFolders = fs_1.default.readdirSync(extensionsRoot); - for (const extensionFolder of extensionsFolders) { - if (exclude.indexOf(extensionFolder) >= 0) { - continue; - } - const packageJSONPath = path_1.default.join(extensionsRoot, extensionFolder, 'package.json'); - if (!fs_1.default.existsSync(packageJSONPath)) { - continue; - } - const packageJSON = JSON.parse(fs_1.default.readFileSync(packageJSONPath).toString('utf8')); - if (!isWebExtension(packageJSON)) { - continue; - } - const children = fs_1.default.readdirSync(path_1.default.join(extensionsRoot, extensionFolder)); - const packageNLSPath = children.filter(child => child === 'package.nls.json')[0]; - const packageNLS = packageNLSPath ? JSON.parse(fs_1.default.readFileSync(path_1.default.join(extensionsRoot, extensionFolder, packageNLSPath)).toString()) : undefined; - const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0]; - const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0]; - scannedExtensions.push({ - extensionPath: extensionFolder, - packageJSON, - packageNLS, - readmePath: readme ? path_1.default.join(extensionFolder, readme) : undefined, - changelogPath: changelog ? path_1.default.join(extensionFolder, changelog) : undefined, - }); - } - return scannedExtensions; - } - catch (ex) { - return scannedExtensions; - } -} -function translatePackageJSON(packageJSON, packageNLSPath) { - const CharCode_PC = '%'.charCodeAt(0); - const packageNls = JSON.parse(fs_1.default.readFileSync(packageNLSPath).toString()); - const translate = (obj) => { - for (const key in obj) { - const val = obj[key]; - if (Array.isArray(val)) { - val.forEach(translate); - } - else if (val && typeof val === 'object') { - translate(val); - } - else if (typeof val === 'string' && val.charCodeAt(0) === CharCode_PC && val.charCodeAt(val.length - 1) === CharCode_PC) { - const translated = packageNls[val.substr(1, val.length - 2)]; - if (translated) { - obj[key] = typeof translated === 'string' ? translated : (typeof translated.message === 'string' ? translated.message : val); - } - } - } - }; - translate(packageJSON); - return packageJSON; -} -const extensionsPath = path_1.default.join(root, 'extensions'); -// Additional projects to run esbuild on. These typically build code for webviews -const esbuildMediaScripts = [ - 'markdown-language-features/esbuild-notebook.js', - 'markdown-language-features/esbuild-preview.js', - 'markdown-math/esbuild.js', - 'notebook-renderers/esbuild.js', - 'ipynb/esbuild.js', - 'simple-browser/esbuild-preview.js', -]; -async function webpackExtensions(taskName, isWatch, webpackConfigLocations) { - const webpack = require('webpack'); - const webpackConfigs = []; - for (const { configPath, outputRoot } of webpackConfigLocations) { - const configOrFnOrArray = require(configPath); - function addConfig(configOrFnOrArray) { - for (const configOrFn of Array.isArray(configOrFnOrArray) ? configOrFnOrArray : [configOrFnOrArray]) { - const config = typeof configOrFn === 'function' ? configOrFn({}, {}) : configOrFn; - if (outputRoot) { - config.output.path = path_1.default.join(outputRoot, path_1.default.relative(path_1.default.dirname(configPath), config.output.path)); - } - webpackConfigs.push(config); - } - } - addConfig(configOrFnOrArray); - } - function reporter(fullStats) { - if (Array.isArray(fullStats.children)) { - for (const stats of fullStats.children) { - const outputPath = stats.outputPath; - if (outputPath) { - const relativePath = path_1.default.relative(extensionsPath, outputPath).replace(/\\/g, '/'); - const match = relativePath.match(/[^\/]+(\/server|\/client)?/); - (0, fancy_log_1.default)(`Finished ${ansi_colors_1.default.green(taskName)} ${ansi_colors_1.default.cyan(match[0])} with ${stats.errors.length} errors.`); - } - if (Array.isArray(stats.errors)) { - stats.errors.forEach((error) => { - fancy_log_1.default.error(error); - }); - } - if (Array.isArray(stats.warnings)) { - stats.warnings.forEach((warning) => { - fancy_log_1.default.warn(warning); - }); - } - } - } - } - return new Promise((resolve, reject) => { - if (isWatch) { - webpack(webpackConfigs).watch({}, (err, stats) => { - if (err) { - reject(); - } - else { - reporter(stats?.toJson()); - } - }); - } - else { - webpack(webpackConfigs).run((err, stats) => { - if (err) { - fancy_log_1.default.error(err); - reject(); - } - else { - reporter(stats?.toJson()); - resolve(); - } - }); - } - }); -} -async function esbuildExtensions(taskName, isWatch, scripts) { - function reporter(stdError, script) { - const matches = (stdError || '').match(/\> (.+): error: (.+)?/g); - (0, fancy_log_1.default)(`Finished ${ansi_colors_1.default.green(taskName)} ${script} with ${matches ? matches.length : 0} errors.`); - for (const match of matches || []) { - fancy_log_1.default.error(match); - } - } - const tasks = scripts.map(({ script, outputRoot }) => { - return new Promise((resolve, reject) => { - const args = [script]; - if (isWatch) { - args.push('--watch'); - } - if (outputRoot) { - args.push('--outputRoot', outputRoot); - } - const proc = child_process_1.default.execFile(process.argv[0], args, {}, (error, _stdout, stderr) => { - if (error) { - return reject(error); - } - reporter(stderr, script); - return resolve(); - }); - proc.stdout.on('data', (data) => { - (0, fancy_log_1.default)(`${ansi_colors_1.default.green(taskName)}: ${data.toString('utf8')}`); - }); - }); - }); - return Promise.all(tasks); -} -async function buildExtensionMedia(isWatch, outputRoot) { - return esbuildExtensions('esbuilding extension media', isWatch, esbuildMediaScripts.map(p => ({ - script: path_1.default.join(extensionsPath, p), - outputRoot: outputRoot ? path_1.default.join(root, outputRoot, path_1.default.dirname(p)) : undefined - }))); -} -//# sourceMappingURL=extensions.js.map \ No newline at end of file diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index b900802ed6a30..24462a3b26e21 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -12,8 +12,8 @@ import path from 'path'; import crypto from 'crypto'; import { Stream } from 'stream'; import File from 'vinyl'; -import { createStatsStream } from './stats'; -import * as util2 from './util'; +import { createStatsStream } from './stats.ts'; +import * as util2 from './util.ts'; import filter from 'gulp-filter'; import rename from 'gulp-rename'; import fancyLog from 'fancy-log'; @@ -21,13 +21,16 @@ import ansiColors from 'ansi-colors'; import buffer from 'gulp-buffer'; import * as jsoncParser from 'jsonc-parser'; import webpack from 'webpack'; -import { getProductionDependencies } from './dependencies'; -import { IExtensionDefinition, getExtensionStream } from './builtInExtensions'; -import { getVersion } from './getVersion'; -import { fetchUrls, fetchGithub } from './fetch'; -const vzip = require('gulp-vinyl-zip'); +import { getProductionDependencies } from './dependencies.ts'; +import { type IExtensionDefinition, getExtensionStream } from './builtInExtensions.ts'; +import { getVersion } from './getVersion.ts'; +import { fetchUrls, fetchGithub } from './fetch.ts'; +import vzip from 'gulp-vinyl-zip'; -const root = path.dirname(path.dirname(__dirname)); +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); + +const root = path.dirname(path.dirname(import.meta.dirname)); const commit = getVersion(root); const sourceMappingURLBase = `https://main.vscode-cdn.net/sourcemaps/${commit}`; @@ -38,7 +41,7 @@ function minifyExtensionResources(input: Stream): Stream { .pipe(buffer()) .pipe(es.mapSync((f: File) => { const errors: jsoncParser.ParseError[] = []; - const value = jsoncParser.parse(f.contents.toString('utf8'), errors, { allowTrailingComma: true }); + const value = jsoncParser.parse(f.contents!.toString('utf8'), errors, { allowTrailingComma: true }); if (errors.length === 0) { // file parsed OK => just stringify to drop whitespace and comments f.contents = Buffer.from(JSON.stringify(value)); @@ -54,7 +57,7 @@ function updateExtensionPackageJSON(input: Stream, update: (data: any) => any): .pipe(packageJsonFilter) .pipe(buffer()) .pipe(es.mapSync((f: File) => { - const data = JSON.parse(f.contents.toString('utf8')); + const data = JSON.parse(f.contents!.toString('utf8')); f.contents = Buffer.from(JSON.stringify(update(data))); return f; })) @@ -63,11 +66,9 @@ function updateExtensionPackageJSON(input: Stream, update: (data: any) => any): function fromLocal(extensionPath: string, forWeb: boolean, disableMangle: boolean): Stream { - const esm = JSON.parse(fs.readFileSync(path.join(extensionPath, 'package.json'), 'utf8')).type === 'module'; - const webpackConfigFileName = forWeb - ? `extension-browser.webpack.config.${!esm ? 'js' : 'cjs'}` - : `extension.webpack.config.${!esm ? 'js' : 'cjs'}`; + ? `extension-browser.webpack.config.js` + : `extension.webpack.config.js`; const isWebPacked = fs.existsSync(path.join(extensionPath, webpackConfigFileName)); let input = isWebPacked @@ -99,7 +100,7 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string, const packagedDependencies: string[] = []; const packageJsonConfig = require(path.join(extensionPath, 'package.json')); if (packageJsonConfig.dependencies) { - const webpackRootConfig = require(path.join(extensionPath, webpackConfigFileName)); + const webpackRootConfig = require(path.join(extensionPath, webpackConfigFileName)).default; for (const key in webpackRootConfig.externals) { if (key in packageJsonConfig.dependencies) { packagedDependencies.push(key); @@ -119,19 +120,18 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string, path: filePath, stat: fs.statSync(filePath), base: extensionPath, - contents: fs.createReadStream(filePath) as any + contents: fs.createReadStream(filePath) })); // check for a webpack configuration files, then invoke webpack // and merge its output with the files stream. - const webpackConfigLocations = (glob.sync( + const webpackConfigLocations = (glob.sync( path.join(extensionPath, '**', webpackConfigFileName), { ignore: ['**/node_modules'] } - )); - + ) as string[]); const webpackStreams = webpackConfigLocations.flatMap(webpackConfigPath => { - const webpackDone = (err: any, stats: any) => { + const webpackDone = (err: Error | undefined, stats: any) => { fancyLog(`Bundled extension: ${ansiColors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`); if (err) { result.emit('error', err); @@ -145,7 +145,7 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string, } }; - const exportedConfig = require(webpackConfigPath); + const exportedConfig = require(webpackConfigPath).default; return (Array.isArray(exportedConfig) ? exportedConfig : [exportedConfig]).map(config => { const webpackConfig = { ...config, @@ -177,7 +177,7 @@ function fromLocalWebpack(extensionPath: string, webpackConfigFileName: string, // * rewrite sourceMappingURL // * save to disk so that upload-task picks this up if (path.extname(data.basename) === '.js') { - const contents = (data.contents).toString('utf8'); + const contents = (data.contents as Buffer).toString('utf8'); data.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, function (_m, g1) { return `\n//# sourceMappingURL=${sourceMappingURLBase}/extensions/${path.basename(extensionPath)}/${relativeOutputPath}/${g1}`; }), 'utf8'); @@ -217,7 +217,7 @@ function fromLocalNormal(extensionPath: string): Stream { path: filePath, stat: fs.statSync(filePath), base: extensionPath, - contents: fs.createReadStream(filePath) as any + contents: fs.createReadStream(filePath) })); es.readArray(files).pipe(result); @@ -335,7 +335,7 @@ const marketplaceWebExtensionsExclude = new Set([ 'ms-vscode.vscode-js-profile-table' ]); -const productJson = JSON.parse(fs.readFileSync(path.join(__dirname, '../../product.json'), 'utf8')); +const productJson = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, '../../product.json'), 'utf8')); const builtInExtensions: IExtensionDefinition[] = productJson.builtInExtensions || []; const webBuiltInExtensions: IExtensionDefinition[] = productJson.webBuiltInExtensions || []; @@ -419,7 +419,7 @@ export function packageAllLocalExtensionsStream(forWeb: boolean, disableMangle: function doPackageLocalExtensionsStream(forWeb: boolean, disableMangle: boolean, native: boolean): Stream { const nativeExtensionsSet = new Set(nativeExtensions); const localExtensionsDescriptions = ( - (glob.sync('extensions/*/package.json')) + (glob.sync('extensions/*/package.json') as string[]) .map(manifestPath => { const absoluteManifestPath = path.join(root, manifestPath); const extensionPath = path.dirname(path.join(root, manifestPath)); @@ -561,12 +561,13 @@ const extensionsPath = path.join(root, 'extensions'); // Additional projects to run esbuild on. These typically build code for webviews const esbuildMediaScripts = [ - 'markdown-language-features/esbuild-notebook.js', - 'markdown-language-features/esbuild-preview.js', - 'markdown-math/esbuild.js', - 'notebook-renderers/esbuild.js', - 'ipynb/esbuild.js', - 'simple-browser/esbuild-preview.js', + 'ipynb/esbuild.mjs', + 'markdown-language-features/esbuild-notebook.mjs', + 'markdown-language-features/esbuild-preview.mjs', + 'markdown-math/esbuild.mjs', + 'mermaid-chat-features/esbuild-chat-webview.mjs', + 'notebook-renderers/esbuild.mjs', + 'simple-browser/esbuild-preview.mjs', ]; export async function webpackExtensions(taskName: string, isWatch: boolean, webpackConfigLocations: { configPath: string; outputRoot?: string }[]) { @@ -575,7 +576,7 @@ export async function webpackExtensions(taskName: string, isWatch: boolean, webp const webpackConfigs: webpack.Configuration[] = []; for (const { configPath, outputRoot } of webpackConfigLocations) { - const configOrFnOrArray = require(configPath); + const configOrFnOrArray = require(configPath).default; function addConfig(configOrFnOrArray: webpack.Configuration | ((env: unknown, args: unknown) => webpack.Configuration) | webpack.Configuration[]) { for (const configOrFn of Array.isArray(configOrFnOrArray) ? configOrFnOrArray : [configOrFnOrArray]) { const config = typeof configOrFn === 'function' ? configOrFn({}, {}) : configOrFn; @@ -587,6 +588,7 @@ export async function webpackExtensions(taskName: string, isWatch: boolean, webp } addConfig(configOrFnOrArray); } + function reporter(fullStats: any) { if (Array.isArray(fullStats.children)) { for (const stats of fullStats.children) { diff --git a/build/lib/fetch.js b/build/lib/fetch.js deleted file mode 100644 index 9f2b974b7ac5a..0000000000000 --- a/build/lib/fetch.js +++ /dev/null @@ -1,141 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.fetchUrls = fetchUrls; -exports.fetchUrl = fetchUrl; -exports.fetchGithub = fetchGithub; -const event_stream_1 = __importDefault(require("event-stream")); -const vinyl_1 = __importDefault(require("vinyl")); -const fancy_log_1 = __importDefault(require("fancy-log")); -const ansi_colors_1 = __importDefault(require("ansi-colors")); -const crypto_1 = __importDefault(require("crypto")); -const through2_1 = __importDefault(require("through2")); -function fetchUrls(urls, options) { - if (options === undefined) { - options = {}; - } - if (typeof options.base !== 'string' && options.base !== null) { - options.base = '/'; - } - if (!Array.isArray(urls)) { - urls = [urls]; - } - return event_stream_1.default.readArray(urls).pipe(event_stream_1.default.map((data, cb) => { - const url = [options.base, data].join(''); - fetchUrl(url, options).then(file => { - cb(undefined, file); - }, error => { - cb(error); - }); - })); -} -async function fetchUrl(url, options, retries = 10, retryDelay = 1000) { - const verbose = !!options.verbose || !!process.env['CI'] || !!process.env['BUILD_ARTIFACTSTAGINGDIRECTORY'] || !!process.env['GITHUB_WORKSPACE']; - try { - let startTime = 0; - if (verbose) { - (0, fancy_log_1.default)(`Start fetching ${ansi_colors_1.default.magenta(url)}${retries !== 10 ? ` (${10 - retries} retry)` : ''}`); - startTime = new Date().getTime(); - } - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 30 * 1000); - try { - const response = await fetch(url, { - ...options.nodeFetchOptions, - signal: controller.signal /* Typings issue with lib.dom.d.ts */ - }); - if (verbose) { - (0, fancy_log_1.default)(`Fetch completed: Status ${response.status}. Took ${ansi_colors_1.default.magenta(`${new Date().getTime() - startTime} ms`)}`); - } - if (response.ok && (response.status >= 200 && response.status < 300)) { - const contents = Buffer.from(await response.arrayBuffer()); - if (options.checksumSha256) { - const actualSHA256Checksum = crypto_1.default.createHash('sha256').update(contents).digest('hex'); - if (actualSHA256Checksum !== options.checksumSha256) { - throw new Error(`Checksum mismatch for ${ansi_colors_1.default.cyan(url)} (expected ${options.checksumSha256}, actual ${actualSHA256Checksum}))`); - } - else if (verbose) { - (0, fancy_log_1.default)(`Verified SHA256 checksums match for ${ansi_colors_1.default.cyan(url)}`); - } - } - else if (verbose) { - (0, fancy_log_1.default)(`Skipping checksum verification for ${ansi_colors_1.default.cyan(url)} because no expected checksum was provided`); - } - if (verbose) { - (0, fancy_log_1.default)(`Fetched response body buffer: ${ansi_colors_1.default.magenta(`${contents.byteLength} bytes`)}`); - } - return new vinyl_1.default({ - cwd: '/', - base: options.base, - path: url, - contents - }); - } - let err = `Request ${ansi_colors_1.default.magenta(url)} failed with status code: ${response.status}`; - if (response.status === 403) { - err += ' (you may be rate limited)'; - } - throw new Error(err); - } - finally { - clearTimeout(timeout); - } - } - catch (e) { - if (verbose) { - (0, fancy_log_1.default)(`Fetching ${ansi_colors_1.default.cyan(url)} failed: ${e}`); - } - if (retries > 0) { - await new Promise(resolve => setTimeout(resolve, retryDelay)); - return fetchUrl(url, options, retries - 1, retryDelay); - } - throw e; - } -} -const ghApiHeaders = { - Accept: 'application/vnd.github.v3+json', - 'User-Agent': 'VSCode Build', -}; -if (process.env.GITHUB_TOKEN) { - ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64'); -} -const ghDownloadHeaders = { - ...ghApiHeaders, - Accept: 'application/octet-stream', -}; -/** - * @param repo for example `Microsoft/vscode` - * @param version for example `16.17.1` - must be a valid releases tag - * @param assetName for example (name) => name === `win-x64-node.exe` - must be an asset that exists - * @returns a stream with the asset as file - */ -function fetchGithub(repo, options) { - return fetchUrls(`/repos/${repo.replace(/^\/|\/$/g, '')}/releases/tags/v${options.version}`, { - base: 'https://api.github.com', - verbose: options.verbose, - nodeFetchOptions: { headers: ghApiHeaders } - }).pipe(through2_1.default.obj(async function (file, _enc, callback) { - const assetFilter = typeof options.name === 'string' ? (name) => name === options.name : options.name; - const asset = JSON.parse(file.contents.toString()).assets.find((a) => assetFilter(a.name)); - if (!asset) { - return callback(new Error(`Could not find asset in release of ${repo} @ ${options.version}`)); - } - try { - callback(null, await fetchUrl(asset.url, { - nodeFetchOptions: { headers: ghDownloadHeaders }, - verbose: options.verbose, - checksumSha256: options.checksumSha256 - })); - } - catch (error) { - callback(error); - } - })); -} -//# sourceMappingURL=fetch.js.map \ No newline at end of file diff --git a/build/lib/fetch.ts b/build/lib/fetch.ts index f09b53e121c5b..970887b3e5565 100644 --- a/build/lib/fetch.ts +++ b/build/lib/fetch.ts @@ -54,7 +54,7 @@ export async function fetchUrl(url: string, options: IFetchOptions, retries = 10 try { const response = await fetch(url, { ...options.nodeFetchOptions, - signal: controller.signal as any /* Typings issue with lib.dom.d.ts */ + signal: controller.signal }); if (verbose) { log(`Fetch completed: Status ${response.status}. Took ${ansiColors.magenta(`${new Date().getTime() - startTime} ms`)}`); diff --git a/build/lib/formatter.js b/build/lib/formatter.js deleted file mode 100644 index 1085ea8f4889c..0000000000000 --- a/build/lib/formatter.js +++ /dev/null @@ -1,79 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.format = format; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const typescript_1 = __importDefault(require("typescript")); -class LanguageServiceHost { - files = {}; - addFile(fileName, text) { - this.files[fileName] = typescript_1.default.ScriptSnapshot.fromString(text); - } - fileExists(path) { - return !!this.files[path]; - } - readFile(path) { - return this.files[path]?.getText(0, this.files[path].getLength()); - } - // for ts.LanguageServiceHost - getCompilationSettings = () => typescript_1.default.getDefaultCompilerOptions(); - getScriptFileNames = () => Object.keys(this.files); - getScriptVersion = (_fileName) => '0'; - getScriptSnapshot = (fileName) => this.files[fileName]; - getCurrentDirectory = () => process.cwd(); - getDefaultLibFileName = (options) => typescript_1.default.getDefaultLibFilePath(options); -} -const defaults = { - baseIndentSize: 0, - indentSize: 4, - tabSize: 4, - indentStyle: typescript_1.default.IndentStyle.Smart, - newLineCharacter: '\r\n', - convertTabsToSpaces: false, - insertSpaceAfterCommaDelimiter: true, - insertSpaceAfterSemicolonInForStatements: true, - insertSpaceBeforeAndAfterBinaryOperators: true, - insertSpaceAfterConstructor: false, - insertSpaceAfterKeywordsInControlFlowStatements: true, - insertSpaceAfterFunctionKeywordForAnonymousFunctions: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false, - insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true, - insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, - insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, - insertSpaceAfterTypeAssertion: false, - insertSpaceBeforeFunctionParenthesis: false, - placeOpenBraceOnNewLineForFunctions: false, - placeOpenBraceOnNewLineForControlBlocks: false, - insertSpaceBeforeTypeAnnotation: false, -}; -const getOverrides = (() => { - let value; - return () => { - value ??= JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '..', '..', 'tsfmt.json'), 'utf8')); - return value; - }; -})(); -function format(fileName, text) { - const host = new LanguageServiceHost(); - host.addFile(fileName, text); - const languageService = typescript_1.default.createLanguageService(host); - const edits = languageService.getFormattingEditsForDocument(fileName, { ...defaults, ...getOverrides() }); - edits - .sort((a, b) => a.span.start - b.span.start) - .reverse() - .forEach(edit => { - const head = text.slice(0, edit.span.start); - const tail = text.slice(edit.span.start + edit.span.length); - text = `${head}${edit.newText}${tail}`; - }); - return text; -} -//# sourceMappingURL=formatter.js.map \ No newline at end of file diff --git a/build/lib/formatter.ts b/build/lib/formatter.ts index 993722e5f924a..09c1de929bab3 100644 --- a/build/lib/formatter.ts +++ b/build/lib/formatter.ts @@ -59,7 +59,7 @@ const defaults: ts.FormatCodeSettings = { const getOverrides = (() => { let value: ts.FormatCodeSettings | undefined; return () => { - value ??= JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'tsfmt.json'), 'utf8')); + value ??= JSON.parse(fs.readFileSync(path.join(import.meta.dirname, '..', '..', 'tsfmt.json'), 'utf8')); return value; }; })(); diff --git a/build/lib/getVersion.js b/build/lib/getVersion.js deleted file mode 100644 index 7606c17ab14f7..0000000000000 --- a/build/lib/getVersion.js +++ /dev/null @@ -1,49 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getVersion = getVersion; -const git = __importStar(require("./git")); -function getVersion(root) { - let version = process.env['BUILD_SOURCEVERSION']; - if (!version || !/^[0-9a-f]{40}$/i.test(version.trim())) { - version = git.getVersion(root); - } - return version; -} -//# sourceMappingURL=getVersion.js.map \ No newline at end of file diff --git a/build/lib/getVersion.ts b/build/lib/getVersion.ts index 2fddb309f83e7..1dc4600dadf9d 100644 --- a/build/lib/getVersion.ts +++ b/build/lib/getVersion.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as git from './git'; +import * as git from './git.ts'; export function getVersion(root: string): string | undefined { let version = process.env['BUILD_SOURCEVERSION']; diff --git a/build/lib/git.js b/build/lib/git.js deleted file mode 100644 index 30de97ed6e369..0000000000000 --- a/build/lib/git.js +++ /dev/null @@ -1,57 +0,0 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getVersion = getVersion; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const path_1 = __importDefault(require("path")); -const fs_1 = __importDefault(require("fs")); -/** - * Returns the sha1 commit version of a repository or undefined in case of failure. - */ -function getVersion(repo) { - const git = path_1.default.join(repo, '.git'); - const headPath = path_1.default.join(git, 'HEAD'); - let head; - try { - head = fs_1.default.readFileSync(headPath, 'utf8').trim(); - } - catch (e) { - return undefined; - } - if (/^[0-9a-f]{40}$/i.test(head)) { - return head; - } - const refMatch = /^ref: (.*)$/.exec(head); - if (!refMatch) { - return undefined; - } - const ref = refMatch[1]; - const refPath = path_1.default.join(git, ref); - try { - return fs_1.default.readFileSync(refPath, 'utf8').trim(); - } - catch (e) { - // noop - } - const packedRefsPath = path_1.default.join(git, 'packed-refs'); - let refsRaw; - try { - refsRaw = fs_1.default.readFileSync(packedRefsPath, 'utf8').trim(); - } - catch (e) { - return undefined; - } - const refsRegex = /^([0-9a-f]{40})\s+(.+)$/gm; - let refsMatch; - const refs = {}; - while (refsMatch = refsRegex.exec(refsRaw)) { - refs[refsMatch[2]] = refsMatch[1]; - } - return refs[ref]; -} -//# sourceMappingURL=git.js.map \ No newline at end of file diff --git a/build/lib/i18n.js b/build/lib/i18n.js deleted file mode 100644 index 1d3bfb901b86d..0000000000000 --- a/build/lib/i18n.js +++ /dev/null @@ -1,784 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.EXTERNAL_EXTENSIONS = exports.XLF = exports.Line = exports.extraLanguages = exports.defaultLanguages = void 0; -exports.processNlsFiles = processNlsFiles; -exports.getResource = getResource; -exports.createXlfFilesForCoreBundle = createXlfFilesForCoreBundle; -exports.createXlfFilesForExtensions = createXlfFilesForExtensions; -exports.createXlfFilesForIsl = createXlfFilesForIsl; -exports.prepareI18nPackFiles = prepareI18nPackFiles; -exports.prepareIslFiles = prepareIslFiles; -const path_1 = __importDefault(require("path")); -const fs_1 = __importDefault(require("fs")); -const event_stream_1 = require("event-stream"); -const gulp_merge_json_1 = __importDefault(require("gulp-merge-json")); -const vinyl_1 = __importDefault(require("vinyl")); -const xml2js_1 = __importDefault(require("xml2js")); -const gulp_1 = __importDefault(require("gulp")); -const fancy_log_1 = __importDefault(require("fancy-log")); -const ansi_colors_1 = __importDefault(require("ansi-colors")); -const iconv_lite_umd_1 = __importDefault(require("@vscode/iconv-lite-umd")); -const l10n_dev_1 = require("@vscode/l10n-dev"); -const REPO_ROOT_PATH = path_1.default.join(__dirname, '../..'); -function log(message, ...rest) { - (0, fancy_log_1.default)(ansi_colors_1.default.green('[i18n]'), message, ...rest); -} -exports.defaultLanguages = [ - { id: 'zh-tw', folderName: 'cht', translationId: 'zh-hant' }, - { id: 'zh-cn', folderName: 'chs', translationId: 'zh-hans' }, - { id: 'ja', folderName: 'jpn' }, - { id: 'ko', folderName: 'kor' }, - { id: 'de', folderName: 'deu' }, - { id: 'fr', folderName: 'fra' }, - { id: 'es', folderName: 'esn' }, - { id: 'ru', folderName: 'rus' }, - { id: 'it', folderName: 'ita' } -]; -// languages requested by the community to non-stable builds -exports.extraLanguages = [ - { id: 'pt-br', folderName: 'ptb' }, - { id: 'hu', folderName: 'hun' }, - { id: 'tr', folderName: 'trk' } -]; -var LocalizeInfo; -(function (LocalizeInfo) { - function is(value) { - const candidate = value; - return candidate && typeof candidate.key === 'string' && (candidate.comment === undefined || (Array.isArray(candidate.comment) && candidate.comment.every(element => typeof element === 'string'))); - } - LocalizeInfo.is = is; -})(LocalizeInfo || (LocalizeInfo = {})); -var BundledFormat; -(function (BundledFormat) { - function is(value) { - if (value === undefined) { - return false; - } - const candidate = value; - const length = Object.keys(value).length; - return length === 3 && !!candidate.keys && !!candidate.messages && !!candidate.bundles; - } - BundledFormat.is = is; -})(BundledFormat || (BundledFormat = {})); -var NLSKeysFormat; -(function (NLSKeysFormat) { - function is(value) { - if (value === undefined) { - return false; - } - const candidate = value; - return Array.isArray(candidate) && Array.isArray(candidate[1]); - } - NLSKeysFormat.is = is; -})(NLSKeysFormat || (NLSKeysFormat = {})); -class Line { - buffer = []; - constructor(indent = 0) { - if (indent > 0) { - this.buffer.push(new Array(indent + 1).join(' ')); - } - } - append(value) { - this.buffer.push(value); - return this; - } - toString() { - return this.buffer.join(''); - } -} -exports.Line = Line; -class TextModel { - _lines; - constructor(contents) { - this._lines = contents.split(/\r\n|\r|\n/); - } - get lines() { - return this._lines; - } -} -class XLF { - project; - buffer; - files; - numberOfMessages; - constructor(project) { - this.project = project; - this.buffer = []; - this.files = Object.create(null); - this.numberOfMessages = 0; - } - toString() { - this.appendHeader(); - const files = Object.keys(this.files).sort(); - for (const file of files) { - this.appendNewLine(``, 2); - const items = this.files[file].sort((a, b) => { - return a.id < b.id ? -1 : a.id > b.id ? 1 : 0; - }); - for (const item of items) { - this.addStringItem(file, item); - } - this.appendNewLine(''); - } - this.appendFooter(); - return this.buffer.join('\r\n'); - } - addFile(original, keys, messages) { - if (keys.length === 0) { - console.log('No keys in ' + original); - return; - } - if (keys.length !== messages.length) { - throw new Error(`Unmatching keys(${keys.length}) and messages(${messages.length}).`); - } - this.numberOfMessages += keys.length; - this.files[original] = []; - const existingKeys = new Set(); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - let realKey; - let comment; - if (typeof key === 'string') { - realKey = key; - comment = undefined; - } - else if (LocalizeInfo.is(key)) { - realKey = key.key; - if (key.comment && key.comment.length > 0) { - comment = key.comment.map(comment => encodeEntities(comment)).join('\r\n'); - } - } - if (!realKey || existingKeys.has(realKey)) { - continue; - } - existingKeys.add(realKey); - const message = encodeEntities(messages[i]); - this.files[original].push({ id: realKey, message: message, comment: comment }); - } - } - addStringItem(file, item) { - if (!item.id || item.message === undefined || item.message === null) { - throw new Error(`No item ID or value specified: ${JSON.stringify(item)}. File: ${file}`); - } - if (item.message.length === 0) { - log(`Item with id ${item.id} in file ${file} has an empty message.`); - } - this.appendNewLine(``, 4); - this.appendNewLine(`${item.message}`, 6); - if (item.comment) { - this.appendNewLine(`${item.comment}`, 6); - } - this.appendNewLine('', 4); - } - appendHeader() { - this.appendNewLine('', 0); - this.appendNewLine('', 0); - } - appendFooter() { - this.appendNewLine('', 0); - } - appendNewLine(content, indent) { - const line = new Line(indent); - line.append(content); - this.buffer.push(line.toString()); - } - static parse = function (xlfString) { - return new Promise((resolve, reject) => { - const parser = new xml2js_1.default.Parser(); - const files = []; - parser.parseString(xlfString, function (err, result) { - if (err) { - reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`)); - } - const fileNodes = result['xliff']['file']; - if (!fileNodes) { - reject(new Error(`XLF parsing error: XLIFF file does not contain "xliff" or "file" node(s) required for parsing.`)); - } - fileNodes.forEach((file) => { - const name = file.$.original; - if (!name) { - reject(new Error(`XLF parsing error: XLIFF file node does not contain original attribute to determine the original location of the resource file.`)); - } - const language = file.$['target-language']; - if (!language) { - reject(new Error(`XLF parsing error: XLIFF file node does not contain target-language attribute to determine translated language.`)); - } - const messages = {}; - const transUnits = file.body[0]['trans-unit']; - if (transUnits) { - transUnits.forEach((unit) => { - const key = unit.$.id; - if (!unit.target) { - return; // No translation available - } - let val = unit.target[0]; - if (typeof val !== 'string') { - // We allow empty source values so support them for translations as well. - val = val._ ? val._ : ''; - } - if (!key) { - reject(new Error(`XLF parsing error: trans-unit ${JSON.stringify(unit, undefined, 0)} defined in file ${name} is missing the ID attribute.`)); - return; - } - messages[key] = decodeEntities(val); - }); - files.push({ messages, name, language: language.toLowerCase() }); - } - }); - resolve(files); - }); - }); - }; -} -exports.XLF = XLF; -function sortLanguages(languages) { - return languages.sort((a, b) => { - return a.id < b.id ? -1 : (a.id > b.id ? 1 : 0); - }); -} -function stripComments(content) { - // Copied from stripComments.js - // - // First group matches a double quoted string - // Second group matches a single quoted string - // Third group matches a multi line comment - // Forth group matches a single line comment - // Fifth group matches a trailing comma - const regexp = /("[^"\\]*(?:\\.[^"\\]*)*")|('[^'\\]*(?:\\.[^'\\]*)*')|(\/\*[^\/\*]*(?:(?:\*|\/)[^\/\*]*)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))|(,\s*[}\]])/g; - const result = content.replace(regexp, (match, _m1, _m2, m3, m4, m5) => { - // Only one of m1, m2, m3, m4, m5 matches - if (m3) { - // A block comment. Replace with nothing - return ''; - } - else if (m4) { - // Since m4 is a single line comment is is at least of length 2 (e.g. //) - // If it ends in \r?\n then keep it. - const length = m4.length; - if (m4[length - 1] === '\n') { - return m4[length - 2] === '\r' ? '\r\n' : '\n'; - } - else { - return ''; - } - } - else if (m5) { - // Remove the trailing comma - return match.substring(1); - } - else { - // We match a string - return match; - } - }); - return result; -} -function processCoreBundleFormat(base, fileHeader, languages, json, emitter) { - const languageDirectory = path_1.default.join(REPO_ROOT_PATH, '..', 'vscode-loc', 'i18n'); - if (!fs_1.default.existsSync(languageDirectory)) { - log(`No VS Code localization repository found. Looking at ${languageDirectory}`); - log(`To bundle translations please check out the vscode-loc repository as a sibling of the vscode repository.`); - } - const sortedLanguages = sortLanguages(languages); - sortedLanguages.forEach((language) => { - if (process.env['VSCODE_BUILD_VERBOSE']) { - log(`Generating nls bundles for: ${language.id}`); - } - const languageFolderName = language.translationId || language.id; - const i18nFile = path_1.default.join(languageDirectory, `vscode-language-pack-${languageFolderName}`, 'translations', 'main.i18n.json'); - let allMessages; - if (fs_1.default.existsSync(i18nFile)) { - const content = stripComments(fs_1.default.readFileSync(i18nFile, 'utf8')); - allMessages = JSON.parse(content); - } - let nlsIndex = 0; - const nlsResult = []; - for (const [moduleId, nlsKeys] of json) { - const moduleTranslations = allMessages?.contents[moduleId]; - for (const nlsKey of nlsKeys) { - nlsResult.push(moduleTranslations?.[nlsKey]); // pushing `undefined` is fine, as we keep english strings as fallback for monaco editor in the build - nlsIndex++; - } - } - emitter.queue(new vinyl_1.default({ - contents: Buffer.from(`${fileHeader} -globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(nlsResult)}; -globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(language.id)};`), - base, - path: `${base}/nls.messages.${language.id}.js` - })); - }); -} -function processNlsFiles(opts) { - return (0, event_stream_1.through)(function (file) { - const fileName = path_1.default.basename(file.path); - if (fileName === 'nls.keys.json') { - try { - const contents = file.contents.toString('utf8'); - const json = JSON.parse(contents); - if (NLSKeysFormat.is(json)) { - processCoreBundleFormat(file.base, opts.fileHeader, opts.languages, json, this); - } - } - catch (error) { - this.emit('error', `Failed to read component file: ${error}`); - } - } - this.queue(file); - }); -} -const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench', extensionsProject = 'vscode-extensions', setupProject = 'vscode-setup', serverProject = 'vscode-server'; -function getResource(sourceFile) { - let resource; - if (/^vs\/platform/.test(sourceFile)) { - return { name: 'vs/platform', project: editorProject }; - } - else if (/^vs\/editor\/contrib/.test(sourceFile)) { - return { name: 'vs/editor/contrib', project: editorProject }; - } - else if (/^vs\/editor/.test(sourceFile)) { - return { name: 'vs/editor', project: editorProject }; - } - else if (/^vs\/base/.test(sourceFile)) { - return { name: 'vs/base', project: editorProject }; - } - else if (/^vs\/code/.test(sourceFile)) { - return { name: 'vs/code', project: workbenchProject }; - } - else if (/^vs\/server/.test(sourceFile)) { - return { name: 'vs/server', project: serverProject }; - } - else if (/^vs\/workbench\/contrib/.test(sourceFile)) { - resource = sourceFile.split('/', 4).join('/'); - return { name: resource, project: workbenchProject }; - } - else if (/^vs\/workbench\/services/.test(sourceFile)) { - resource = sourceFile.split('/', 4).join('/'); - return { name: resource, project: workbenchProject }; - } - else if (/^vs\/workbench/.test(sourceFile)) { - return { name: 'vs/workbench', project: workbenchProject }; - } - throw new Error(`Could not identify the XLF bundle for ${sourceFile}`); -} -function createXlfFilesForCoreBundle() { - return (0, event_stream_1.through)(function (file) { - const basename = path_1.default.basename(file.path); - if (basename === 'nls.metadata.json') { - if (file.isBuffer()) { - const xlfs = Object.create(null); - const json = JSON.parse(file.contents.toString('utf8')); - for (const coreModule in json.keys) { - const projectResource = getResource(coreModule); - const resource = projectResource.name; - const project = projectResource.project; - const keys = json.keys[coreModule]; - const messages = json.messages[coreModule]; - if (keys.length !== messages.length) { - this.emit('error', `There is a mismatch between keys and messages in ${file.relative} for module ${coreModule}`); - return; - } - else { - let xlf = xlfs[resource]; - if (!xlf) { - xlf = new XLF(project); - xlfs[resource] = xlf; - } - xlf.addFile(`src/${coreModule}`, keys, messages); - } - } - for (const resource in xlfs) { - const xlf = xlfs[resource]; - const filePath = `${xlf.project}/${resource.replace(/\//g, '_')}.xlf`; - const xlfFile = new vinyl_1.default({ - path: filePath, - contents: Buffer.from(xlf.toString(), 'utf8') - }); - this.queue(xlfFile); - } - } - else { - this.emit('error', new Error(`File ${file.relative} is not using a buffer content`)); - return; - } - } - else { - this.emit('error', new Error(`File ${file.relative} is not a core meta data file.`)); - return; - } - }); -} -function createL10nBundleForExtension(extensionFolderName, prefixWithBuildFolder) { - const prefix = prefixWithBuildFolder ? '.build/' : ''; - return gulp_1.default - .src([ - // For source code of extensions - `${prefix}extensions/${extensionFolderName}/{src,client,server}/**/*.{ts,tsx}`, - // // For any dependencies pulled in (think vscode-css-languageservice or @vscode/emmet-helper) - `${prefix}extensions/${extensionFolderName}/**/node_modules/{@vscode,vscode-*}/**/*.{js,jsx}`, - // // For any dependencies pulled in that bundle @vscode/l10n. They needed to export the bundle - `${prefix}extensions/${extensionFolderName}/**/bundle.l10n.json`, - ]) - .pipe((0, event_stream_1.map)(function (data, callback) { - const file = data; - if (!file.isBuffer()) { - // Not a buffer so we drop it - callback(); - return; - } - const extension = path_1.default.extname(file.relative); - if (extension !== '.json') { - const contents = file.contents.toString('utf8'); - (0, l10n_dev_1.getL10nJson)([{ contents, extension }]) - .then((json) => { - callback(undefined, new vinyl_1.default({ - path: `extensions/${extensionFolderName}/bundle.l10n.json`, - contents: Buffer.from(JSON.stringify(json), 'utf8') - })); - }) - .catch((err) => { - callback(new Error(`File ${file.relative} threw an error when parsing: ${err}`)); - }); - // signal pause? - return false; - } - // for bundle.l10n.jsons - let bundleJson; - try { - bundleJson = JSON.parse(file.contents.toString('utf8')); - } - catch (err) { - callback(new Error(`File ${file.relative} threw an error when parsing: ${err}`)); - return; - } - // some validation of the bundle.l10n.json format - for (const key in bundleJson) { - if (typeof bundleJson[key] !== 'string' && - (typeof bundleJson[key].message !== 'string' || !Array.isArray(bundleJson[key].comment))) { - callback(new Error(`Invalid bundle.l10n.json file. The value for key ${key} is not in the expected format.`)); - return; - } - } - callback(undefined, file); - })) - .pipe((0, gulp_merge_json_1.default)({ - fileName: `extensions/${extensionFolderName}/bundle.l10n.json`, - jsonSpace: '', - concatArrays: true - })); -} -exports.EXTERNAL_EXTENSIONS = [ - 'ms-vscode.js-debug', - 'ms-vscode.js-debug-companion', - 'ms-vscode.vscode-js-profile-table', -]; -function createXlfFilesForExtensions() { - let counter = 0; - let folderStreamEnded = false; - let folderStreamEndEmitted = false; - return (0, event_stream_1.through)(function (extensionFolder) { - const folderStream = this; - const stat = fs_1.default.statSync(extensionFolder.path); - if (!stat.isDirectory()) { - return; - } - const extensionFolderName = path_1.default.basename(extensionFolder.path); - if (extensionFolderName === 'node_modules') { - return; - } - // Get extension id and use that as the id - const manifest = fs_1.default.readFileSync(path_1.default.join(extensionFolder.path, 'package.json'), 'utf-8'); - const manifestJson = JSON.parse(manifest); - const extensionId = manifestJson.publisher + '.' + manifestJson.name; - counter++; - let _l10nMap; - function getL10nMap() { - if (!_l10nMap) { - _l10nMap = new Map(); - } - return _l10nMap; - } - (0, event_stream_1.merge)(gulp_1.default.src([`.build/extensions/${extensionFolderName}/package.nls.json`, `.build/extensions/${extensionFolderName}/**/nls.metadata.json`], { allowEmpty: true }), createL10nBundleForExtension(extensionFolderName, exports.EXTERNAL_EXTENSIONS.includes(extensionId))).pipe((0, event_stream_1.through)(function (file) { - if (file.isBuffer()) { - const buffer = file.contents; - const basename = path_1.default.basename(file.path); - if (basename === 'package.nls.json') { - const json = JSON.parse(buffer.toString('utf8')); - getL10nMap().set(`extensions/${extensionId}/package`, json); - } - else if (basename === 'nls.metadata.json') { - const json = JSON.parse(buffer.toString('utf8')); - const relPath = path_1.default.relative(`.build/extensions/${extensionFolderName}`, path_1.default.dirname(file.path)); - for (const file in json) { - const fileContent = json[file]; - const info = Object.create(null); - for (let i = 0; i < fileContent.messages.length; i++) { - const message = fileContent.messages[i]; - const { key, comment } = LocalizeInfo.is(fileContent.keys[i]) - ? fileContent.keys[i] - : { key: fileContent.keys[i], comment: undefined }; - info[key] = comment ? { message, comment } : message; - } - getL10nMap().set(`extensions/${extensionId}/${relPath}/${file}`, info); - } - } - else if (basename === 'bundle.l10n.json') { - const json = JSON.parse(buffer.toString('utf8')); - getL10nMap().set(`extensions/${extensionId}/bundle`, json); - } - else { - this.emit('error', new Error(`${file.path} is not a valid extension nls file`)); - return; - } - } - }, function () { - if (_l10nMap?.size > 0) { - const xlfFile = new vinyl_1.default({ - path: path_1.default.join(extensionsProject, extensionId + '.xlf'), - contents: Buffer.from((0, l10n_dev_1.getL10nXlf)(_l10nMap), 'utf8') - }); - folderStream.queue(xlfFile); - } - this.queue(null); - counter--; - if (counter === 0 && folderStreamEnded && !folderStreamEndEmitted) { - folderStreamEndEmitted = true; - folderStream.queue(null); - } - })); - }, function () { - folderStreamEnded = true; - if (counter === 0) { - folderStreamEndEmitted = true; - this.queue(null); - } - }); -} -function createXlfFilesForIsl() { - return (0, event_stream_1.through)(function (file) { - let projectName, resourceFile; - if (path_1.default.basename(file.path) === 'messages.en.isl') { - projectName = setupProject; - resourceFile = 'messages.xlf'; - } - else { - throw new Error(`Unknown input file ${file.path}`); - } - const xlf = new XLF(projectName), keys = [], messages = []; - const model = new TextModel(file.contents.toString()); - let inMessageSection = false; - model.lines.forEach(line => { - if (line.length === 0) { - return; - } - const firstChar = line.charAt(0); - switch (firstChar) { - case ';': - // Comment line; - return; - case '[': - inMessageSection = '[Messages]' === line || '[CustomMessages]' === line; - return; - } - if (!inMessageSection) { - return; - } - const sections = line.split('='); - if (sections.length !== 2) { - throw new Error(`Badly formatted message found: ${line}`); - } - else { - const key = sections[0]; - const value = sections[1]; - if (key.length > 0 && value.length > 0) { - keys.push(key); - messages.push(value); - } - } - }); - const originalPath = file.path.substring(file.cwd.length + 1, file.path.split('.')[0].length).replace(/\\/g, '/'); - xlf.addFile(originalPath, keys, messages); - // Emit only upon all ISL files combined into single XLF instance - const newFilePath = path_1.default.join(projectName, resourceFile); - const xlfFile = new vinyl_1.default({ path: newFilePath, contents: Buffer.from(xlf.toString(), 'utf-8') }); - this.queue(xlfFile); - }); -} -function createI18nFile(name, messages) { - const result = Object.create(null); - result[''] = [ - '--------------------------------------------------------------------------------------------', - 'Copyright (c) Microsoft Corporation. All rights reserved.', - 'Licensed under the MIT License. See License.txt in the project root for license information.', - '--------------------------------------------------------------------------------------------', - 'Do not edit this file. It is machine generated.' - ]; - for (const key of Object.keys(messages)) { - result[key] = messages[key]; - } - let content = JSON.stringify(result, null, '\t'); - if (process.platform === 'win32') { - content = content.replace(/\n/g, '\r\n'); - } - return new vinyl_1.default({ - path: path_1.default.join(name + '.i18n.json'), - contents: Buffer.from(content, 'utf8') - }); -} -const i18nPackVersion = '1.0.0'; -function getRecordFromL10nJsonFormat(l10nJsonFormat) { - const record = {}; - for (const key of Object.keys(l10nJsonFormat).sort()) { - const value = l10nJsonFormat[key]; - record[key] = typeof value === 'string' ? value : value.message; - } - return record; -} -function prepareI18nPackFiles(resultingTranslationPaths) { - const parsePromises = []; - const mainPack = { version: i18nPackVersion, contents: {} }; - const extensionsPacks = {}; - const errors = []; - return (0, event_stream_1.through)(function (xlf) { - let project = path_1.default.basename(path_1.default.dirname(path_1.default.dirname(xlf.relative))); - // strip `-new` since vscode-extensions-loc uses the `-new` suffix to indicate that it's from the new loc pipeline - const resource = path_1.default.basename(path_1.default.basename(xlf.relative, '.xlf'), '-new'); - if (exports.EXTERNAL_EXTENSIONS.find(e => e === resource)) { - project = extensionsProject; - } - const contents = xlf.contents.toString(); - log(`Found ${project}: ${resource}`); - const parsePromise = (0, l10n_dev_1.getL10nFilesFromXlf)(contents); - parsePromises.push(parsePromise); - parsePromise.then(resolvedFiles => { - resolvedFiles.forEach(file => { - const path = file.name; - const firstSlash = path.indexOf('/'); - if (project === extensionsProject) { - // resource will be the extension id - let extPack = extensionsPacks[resource]; - if (!extPack) { - extPack = extensionsPacks[resource] = { version: i18nPackVersion, contents: {} }; - } - // remove 'extensions/extensionId/' segment - const secondSlash = path.indexOf('/', firstSlash + 1); - extPack.contents[path.substring(secondSlash + 1)] = getRecordFromL10nJsonFormat(file.messages); - } - else { - mainPack.contents[path.substring(firstSlash + 1)] = getRecordFromL10nJsonFormat(file.messages); - } - }); - }).catch(reason => { - errors.push(reason); - }); - }, function () { - Promise.all(parsePromises) - .then(() => { - if (errors.length > 0) { - throw errors; - } - const translatedMainFile = createI18nFile('./main', mainPack); - resultingTranslationPaths.push({ id: 'vscode', resourceName: 'main.i18n.json' }); - this.queue(translatedMainFile); - for (const extensionId in extensionsPacks) { - const translatedExtFile = createI18nFile(`extensions/${extensionId}`, extensionsPacks[extensionId]); - this.queue(translatedExtFile); - resultingTranslationPaths.push({ id: extensionId, resourceName: `extensions/${extensionId}.i18n.json` }); - } - this.queue(null); - }) - .catch((reason) => { - this.emit('error', reason); - }); - }); -} -function prepareIslFiles(language, innoSetupConfig) { - const parsePromises = []; - return (0, event_stream_1.through)(function (xlf) { - const stream = this; - const parsePromise = XLF.parse(xlf.contents.toString()); - parsePromises.push(parsePromise); - parsePromise.then(resolvedFiles => { - resolvedFiles.forEach(file => { - const translatedFile = createIslFile(file.name, file.messages, language, innoSetupConfig); - stream.queue(translatedFile); - }); - }).catch(reason => { - this.emit('error', reason); - }); - }, function () { - Promise.all(parsePromises) - .then(() => { this.queue(null); }) - .catch(reason => { - this.emit('error', reason); - }); - }); -} -function createIslFile(name, messages, language, innoSetup) { - const content = []; - let originalContent; - if (path_1.default.basename(name) === 'Default') { - originalContent = new TextModel(fs_1.default.readFileSync(name + '.isl', 'utf8')); - } - else { - originalContent = new TextModel(fs_1.default.readFileSync(name + '.en.isl', 'utf8')); - } - originalContent.lines.forEach(line => { - if (line.length > 0) { - const firstChar = line.charAt(0); - if (firstChar === '[' || firstChar === ';') { - content.push(line); - } - else { - const sections = line.split('='); - const key = sections[0]; - let translated = line; - if (key) { - const translatedMessage = messages[key]; - if (translatedMessage) { - translated = `${key}=${translatedMessage}`; - } - } - content.push(translated); - } - } - }); - const basename = path_1.default.basename(name); - const filePath = `${basename}.${language.id}.isl`; - const encoded = iconv_lite_umd_1.default.encode(Buffer.from(content.join('\r\n'), 'utf8').toString(), innoSetup.codePage); - return new vinyl_1.default({ - path: filePath, - contents: Buffer.from(encoded), - }); -} -function encodeEntities(value) { - const result = []; - for (let i = 0; i < value.length; i++) { - const ch = value[i]; - switch (ch) { - case '<': - result.push('<'); - break; - case '>': - result.push('>'); - break; - case '&': - result.push('&'); - break; - default: - result.push(ch); - } - } - return result.join(''); -} -function decodeEntities(value) { - return value.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); -} -//# sourceMappingURL=i18n.js.map \ No newline at end of file diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index 96468033719ca..8ebcb1f177b06 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -5,8 +5,7 @@ import path from 'path'; import fs from 'fs'; - -import { map, merge, through, ThroughStream } from 'event-stream'; +import eventStream from 'event-stream'; import jsonMerge from 'gulp-merge-json'; import File from 'vinyl'; import xml2js from 'xml2js'; @@ -14,11 +13,11 @@ import gulp from 'gulp'; import fancyLog from 'fancy-log'; import ansiColors from 'ansi-colors'; import iconv from '@vscode/iconv-lite-umd'; -import { l10nJsonFormat, getL10nXlf, l10nJsonDetails, getL10nFilesFromXlf, getL10nJson } from '@vscode/l10n-dev'; +import { type l10nJsonFormat, getL10nXlf, type l10nJsonDetails, getL10nFilesFromXlf, getL10nJson } from '@vscode/l10n-dev'; -const REPO_ROOT_PATH = path.join(__dirname, '../..'); +const REPO_ROOT_PATH = path.join(import.meta.dirname, '../..'); -function log(message: any, ...rest: any[]): void { +function log(message: any, ...rest: unknown[]): void { fancyLog(ansiColors.green('[i18n]'), message, ...rest); } @@ -44,11 +43,12 @@ export const defaultLanguages: Language[] = [ { id: 'it', folderName: 'ita' } ]; -// languages requested by the community to non-stable builds +// languages requested by the community export const extraLanguages: Language[] = [ { id: 'pt-br', folderName: 'ptb' }, - { id: 'hu', folderName: 'hun' }, - { id: 'tr', folderName: 'trk' } + { id: 'tr', folderName: 'trk' }, + { id: 'cs' }, + { id: 'pl' } ]; interface Item { @@ -67,11 +67,9 @@ interface LocalizeInfo { comment: string[]; } -module LocalizeInfo { - export function is(value: any): value is LocalizeInfo { - const candidate = value as LocalizeInfo; - return candidate && typeof candidate.key === 'string' && (candidate.comment === undefined || (Array.isArray(candidate.comment) && candidate.comment.every(element => typeof element === 'string'))); - } +function isLocalizeInfo(value: unknown): value is LocalizeInfo { + const candidate = value as LocalizeInfo; + return candidate && typeof candidate.key === 'string' && (candidate.comment === undefined || (Array.isArray(candidate.comment) && candidate.comment.every(element => typeof element === 'string'))); } interface BundledFormat { @@ -80,30 +78,15 @@ interface BundledFormat { bundles: Record; } -module BundledFormat { - export function is(value: any): value is BundledFormat { - if (value === undefined) { - return false; - } - - const candidate = value as BundledFormat; - const length = Object.keys(value).length; - - return length === 3 && !!candidate.keys && !!candidate.messages && !!candidate.bundles; - } -} - type NLSKeysFormat = [string /* module ID */, string[] /* keys */]; -module NLSKeysFormat { - export function is(value: any): value is NLSKeysFormat { - if (value === undefined) { - return false; - } - - const candidate = value as NLSKeysFormat; - return Array.isArray(candidate) && Array.isArray(candidate[1]); +function isNLSKeysFormat(value: unknown): value is NLSKeysFormat { + if (value === undefined) { + return false; } + + const candidate = value as NLSKeysFormat; + return Array.isArray(candidate) && Array.isArray(candidate[1]); } interface BundledExtensionFormat { @@ -157,8 +140,10 @@ export class XLF { private buffer: string[]; private files: Record; public numberOfMessages: number; + public project: string; - constructor(public project: string) { + constructor(project: string) { + this.project = project; this.buffer = []; this.files = Object.create(null); this.numberOfMessages = 0; @@ -200,7 +185,7 @@ export class XLF { if (typeof key === 'string') { realKey = key; comment = undefined; - } else if (LocalizeInfo.is(key)) { + } else if (isLocalizeInfo(key)) { realKey = key.key; if (key.comment && key.comment.length > 0) { comment = key.comment.map(comment => encodeEntities(comment)).join('\r\n'); @@ -254,7 +239,7 @@ export class XLF { const files: { messages: Record; name: string; language: string }[] = []; - parser.parseString(xlfString, function (err: any, result: any) { + parser.parseString(xlfString, function (err: Error | undefined, result: any) { if (err) { reject(new Error(`XLF parsing error: Failed to parse XLIFF string. ${err}`)); } @@ -344,7 +329,7 @@ function stripComments(content: string): string { return result; } -function processCoreBundleFormat(base: string, fileHeader: string, languages: Language[], json: NLSKeysFormat, emitter: ThroughStream) { +function processCoreBundleFormat(base: string, fileHeader: string, languages: Language[], json: NLSKeysFormat, emitter: eventStream.ThroughStream) { const languageDirectory = path.join(REPO_ROOT_PATH, '..', 'vscode-loc', 'i18n'); if (!fs.existsSync(languageDirectory)) { log(`No VS Code localization repository found. Looking at ${languageDirectory}`); @@ -384,14 +369,14 @@ globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(language.id)};`), }); } -export function processNlsFiles(opts: { out: string; fileHeader: string; languages: Language[] }): ThroughStream { - return through(function (this: ThroughStream, file: File) { +export function processNlsFiles(opts: { out: string; fileHeader: string; languages: Language[] }): eventStream.ThroughStream { + return eventStream.through(function (this: eventStream.ThroughStream, file: File) { const fileName = path.basename(file.path); if (fileName === 'nls.keys.json') { try { - const contents = file.contents.toString('utf8'); + const contents = file.contents!.toString('utf8'); const json = JSON.parse(contents); - if (NLSKeysFormat.is(json)) { + if (isNLSKeysFormat(json)) { processCoreBundleFormat(file.base, opts.fileHeader, opts.languages, json, this); } } catch (error) { @@ -437,8 +422,8 @@ export function getResource(sourceFile: string): Resource { } -export function createXlfFilesForCoreBundle(): ThroughStream { - return through(function (this: ThroughStream, file: File) { +export function createXlfFilesForCoreBundle(): eventStream.ThroughStream { + return eventStream.through(function (this: eventStream.ThroughStream, file: File) { const basename = path.basename(file.path); if (basename === 'nls.metadata.json') { if (file.isBuffer()) { @@ -494,7 +479,7 @@ function createL10nBundleForExtension(extensionFolderName: string, prefixWithBui // // For any dependencies pulled in that bundle @vscode/l10n. They needed to export the bundle `${prefix}extensions/${extensionFolderName}/**/bundle.l10n.json`, ]) - .pipe(map(function (data, callback) { + .pipe(eventStream.map(function (data, callback) { const file = data as File; if (!file.isBuffer()) { // Not a buffer so we drop it @@ -553,11 +538,11 @@ export const EXTERNAL_EXTENSIONS = [ 'ms-vscode.vscode-js-profile-table', ]; -export function createXlfFilesForExtensions(): ThroughStream { +export function createXlfFilesForExtensions(): eventStream.ThroughStream { let counter: number = 0; let folderStreamEnded: boolean = false; let folderStreamEndEmitted: boolean = false; - return through(function (this: ThroughStream, extensionFolder: File) { + return eventStream.through(function (this: eventStream.ThroughStream, extensionFolder: File) { const folderStream = this; const stat = fs.statSync(extensionFolder.path); if (!stat.isDirectory()) { @@ -580,10 +565,10 @@ export function createXlfFilesForExtensions(): ThroughStream { } return _l10nMap; } - merge( + eventStream.merge( gulp.src([`.build/extensions/${extensionFolderName}/package.nls.json`, `.build/extensions/${extensionFolderName}/**/nls.metadata.json`], { allowEmpty: true }), createL10nBundleForExtension(extensionFolderName, EXTERNAL_EXTENSIONS.includes(extensionId)) - ).pipe(through(function (file: File) { + ).pipe(eventStream.through(function (file: File) { if (file.isBuffer()) { const buffer: Buffer = file.contents as Buffer; const basename = path.basename(file.path); @@ -598,7 +583,7 @@ export function createXlfFilesForExtensions(): ThroughStream { const info: l10nJsonFormat = Object.create(null); for (let i = 0; i < fileContent.messages.length; i++) { const message = fileContent.messages[i]; - const { key, comment } = LocalizeInfo.is(fileContent.keys[i]) + const { key, comment } = isLocalizeInfo(fileContent.keys[i]) ? fileContent.keys[i] as LocalizeInfo : { key: fileContent.keys[i] as string, comment: undefined }; @@ -638,8 +623,8 @@ export function createXlfFilesForExtensions(): ThroughStream { }); } -export function createXlfFilesForIsl(): ThroughStream { - return through(function (this: ThroughStream, file: File) { +export function createXlfFilesForIsl(): eventStream.ThroughStream { + return eventStream.through(function (this: eventStream.ThroughStream, file: File) { let projectName: string, resourceFile: string; if (path.basename(file.path) === 'messages.en.isl') { @@ -653,7 +638,7 @@ export function createXlfFilesForIsl(): ThroughStream { keys: string[] = [], messages: string[] = []; - const model = new TextModel(file.contents.toString()); + const model = new TextModel(file.contents!.toString()); let inMessageSection = false; model.lines.forEach(line => { if (line.length === 0) { @@ -744,15 +729,15 @@ export function prepareI18nPackFiles(resultingTranslationPaths: TranslationPath[ const parsePromises: Promise[] = []; const mainPack: I18nPack = { version: i18nPackVersion, contents: {} }; const extensionsPacks: Record = {}; - const errors: any[] = []; - return through(function (this: ThroughStream, xlf: File) { + const errors: unknown[] = []; + return eventStream.through(function (this: eventStream.ThroughStream, xlf: File) { let project = path.basename(path.dirname(path.dirname(xlf.relative))); // strip `-new` since vscode-extensions-loc uses the `-new` suffix to indicate that it's from the new loc pipeline const resource = path.basename(path.basename(xlf.relative, '.xlf'), '-new'); if (EXTERNAL_EXTENSIONS.find(e => e === resource)) { project = extensionsProject; } - const contents = xlf.contents.toString(); + const contents = xlf.contents!.toString(); log(`Found ${project}: ${resource}`); const parsePromise = getL10nFilesFromXlf(contents); parsePromises.push(parsePromise); @@ -803,12 +788,12 @@ export function prepareI18nPackFiles(resultingTranslationPaths: TranslationPath[ }); } -export function prepareIslFiles(language: Language, innoSetupConfig: InnoSetup): ThroughStream { +export function prepareIslFiles(language: Language, innoSetupConfig: InnoSetup): eventStream.ThroughStream { const parsePromises: Promise[] = []; - return through(function (this: ThroughStream, xlf: File) { + return eventStream.through(function (this: eventStream.ThroughStream, xlf: File) { const stream = this; - const parsePromise = XLF.parse(xlf.contents.toString()); + const parsePromise = XLF.parse(xlf.contents!.toString()); parsePromises.push(parsePromise); parsePromise.then( resolvedFiles => { diff --git a/build/lib/inlineMeta.js b/build/lib/inlineMeta.js deleted file mode 100644 index 3b473ae091e16..0000000000000 --- a/build/lib/inlineMeta.js +++ /dev/null @@ -1,51 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.inlineMeta = inlineMeta; -const event_stream_1 = __importDefault(require("event-stream")); -const path_1 = require("path"); -const packageJsonMarkerId = 'BUILD_INSERT_PACKAGE_CONFIGURATION'; -// TODO in order to inline `product.json`, more work is -// needed to ensure that we cover all cases where modifications -// are done to the product configuration during build. There are -// at least 2 more changes that kick in very late: -// - a `darwinUniversalAssetId` is added in`create-universal-app.ts` -// - a `target` is added in `gulpfile.vscode.win32.js` -// const productJsonMarkerId = 'BUILD_INSERT_PRODUCT_CONFIGURATION'; -function inlineMeta(result, ctx) { - return result.pipe(event_stream_1.default.through(function (file) { - if (matchesFile(file, ctx)) { - let content = file.contents.toString(); - let markerFound = false; - const packageMarker = `${packageJsonMarkerId}:"${packageJsonMarkerId}"`; // this needs to be the format after esbuild has processed the file (e.g. double quotes) - if (content.includes(packageMarker)) { - content = content.replace(packageMarker, JSON.stringify(JSON.parse(ctx.packageJsonFn())).slice(1, -1) /* trim braces */); - markerFound = true; - } - // const productMarker = `${productJsonMarkerId}:"${productJsonMarkerId}"`; // this needs to be the format after esbuild has processed the file (e.g. double quotes) - // if (content.includes(productMarker)) { - // content = content.replace(productMarker, JSON.stringify(JSON.parse(ctx.productJsonFn())).slice(1, -1) /* trim braces */); - // markerFound = true; - // } - if (markerFound) { - file.contents = Buffer.from(content); - } - } - this.emit('data', file); - })); -} -function matchesFile(file, ctx) { - for (const targetPath of ctx.targetPaths) { - if (file.basename === (0, path_1.basename)(targetPath)) { // TODO would be nicer to figure out root relative path to not match on false positives - return true; - } - } - return false; -} -//# sourceMappingURL=inlineMeta.js.map \ No newline at end of file diff --git a/build/lib/inlineMeta.ts b/build/lib/inlineMeta.ts index 2a0db13d06e2d..530e1ec30ea5c 100644 --- a/build/lib/inlineMeta.ts +++ b/build/lib/inlineMeta.ts @@ -26,7 +26,7 @@ const packageJsonMarkerId = 'BUILD_INSERT_PACKAGE_CONFIGURATION'; export function inlineMeta(result: NodeJS.ReadWriteStream, ctx: IInlineMetaContext): NodeJS.ReadWriteStream { return result.pipe(es.through(function (file: File) { if (matchesFile(file, ctx)) { - let content = file.contents.toString(); + let content = file.contents!.toString(); let markerFound = false; const packageMarker = `${packageJsonMarkerId}:"${packageJsonMarkerId}"`; // this needs to be the format after esbuild has processed the file (e.g. double quotes) diff --git a/build/lib/mangle/index.js b/build/lib/mangle/index.js deleted file mode 100644 index fa729052f7c4b..0000000000000 --- a/build/lib/mangle/index.js +++ /dev/null @@ -1,661 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Mangler = void 0; -const node_v8_1 = __importDefault(require("node:v8")); -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const process_1 = require("process"); -const source_map_1 = require("source-map"); -const typescript_1 = __importDefault(require("typescript")); -const url_1 = require("url"); -const workerpool_1 = __importDefault(require("workerpool")); -const staticLanguageServiceHost_1 = require("./staticLanguageServiceHost"); -const buildfile = require('../../buildfile'); -class ShortIdent { - prefix; - static _keywords = new Set(['await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', - 'default', 'delete', 'do', 'else', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if', - 'import', 'in', 'instanceof', 'let', 'new', 'null', 'return', 'static', 'super', 'switch', 'this', 'throw', - 'true', 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield']); - static _alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890$_'.split(''); - _value = 0; - constructor(prefix) { - this.prefix = prefix; - } - next(isNameTaken) { - const candidate = this.prefix + ShortIdent.convert(this._value); - this._value++; - if (ShortIdent._keywords.has(candidate) || /^[_0-9]/.test(candidate) || isNameTaken?.(candidate)) { - // try again - return this.next(isNameTaken); - } - return candidate; - } - static convert(n) { - const base = this._alphabet.length; - let result = ''; - do { - const rest = n % base; - result += this._alphabet[rest]; - n = (n / base) | 0; - } while (n > 0); - return result; - } -} -var FieldType; -(function (FieldType) { - FieldType[FieldType["Public"] = 0] = "Public"; - FieldType[FieldType["Protected"] = 1] = "Protected"; - FieldType[FieldType["Private"] = 2] = "Private"; -})(FieldType || (FieldType = {})); -class ClassData { - fileName; - node; - fields = new Map(); - replacements; - parent; - children; - constructor(fileName, node) { - // analyse all fields (properties and methods). Find usages of all protected and - // private ones and keep track of all public ones (to prevent naming collisions) - this.fileName = fileName; - this.node = node; - const candidates = []; - for (const member of node.members) { - if (typescript_1.default.isMethodDeclaration(member)) { - // method `foo() {}` - candidates.push(member); - } - else if (typescript_1.default.isPropertyDeclaration(member)) { - // property `foo = 234` - candidates.push(member); - } - else if (typescript_1.default.isGetAccessor(member)) { - // getter: `get foo() { ... }` - candidates.push(member); - } - else if (typescript_1.default.isSetAccessor(member)) { - // setter: `set foo() { ... }` - candidates.push(member); - } - else if (typescript_1.default.isConstructorDeclaration(member)) { - // constructor-prop:`constructor(private foo) {}` - for (const param of member.parameters) { - if (hasModifier(param, typescript_1.default.SyntaxKind.PrivateKeyword) - || hasModifier(param, typescript_1.default.SyntaxKind.ProtectedKeyword) - || hasModifier(param, typescript_1.default.SyntaxKind.PublicKeyword) - || hasModifier(param, typescript_1.default.SyntaxKind.ReadonlyKeyword)) { - candidates.push(param); - } - } - } - } - for (const member of candidates) { - const ident = ClassData._getMemberName(member); - if (!ident) { - continue; - } - const type = ClassData._getFieldType(member); - this.fields.set(ident, { type, pos: member.name.getStart() }); - } - } - static _getMemberName(node) { - if (!node.name) { - return undefined; - } - const { name } = node; - let ident = name.getText(); - if (name.kind === typescript_1.default.SyntaxKind.ComputedPropertyName) { - if (name.expression.kind !== typescript_1.default.SyntaxKind.StringLiteral) { - // unsupported: [Symbol.foo] or [abc + 'field'] - return; - } - // ['foo'] - ident = name.expression.getText().slice(1, -1); - } - return ident; - } - static _getFieldType(node) { - if (hasModifier(node, typescript_1.default.SyntaxKind.PrivateKeyword)) { - return 2 /* FieldType.Private */; - } - else if (hasModifier(node, typescript_1.default.SyntaxKind.ProtectedKeyword)) { - return 1 /* FieldType.Protected */; - } - else { - return 0 /* FieldType.Public */; - } - } - static _shouldMangle(type) { - return type === 2 /* FieldType.Private */ - || type === 1 /* FieldType.Protected */; - } - static makeImplicitPublicActuallyPublic(data, reportViolation) { - // TS-HACK - // A subtype can make an inherited protected field public. To prevent accidential - // mangling of public fields we mark the original (protected) fields as public... - for (const [name, info] of data.fields) { - if (info.type !== 0 /* FieldType.Public */) { - continue; - } - let parent = data.parent; - while (parent) { - if (parent.fields.get(name)?.type === 1 /* FieldType.Protected */) { - const parentPos = parent.node.getSourceFile().getLineAndCharacterOfPosition(parent.fields.get(name).pos); - const infoPos = data.node.getSourceFile().getLineAndCharacterOfPosition(info.pos); - reportViolation(name, `'${name}' from ${parent.fileName}:${parentPos.line + 1}`, `${data.fileName}:${infoPos.line + 1}`); - parent.fields.get(name).type = 0 /* FieldType.Public */; - } - parent = parent.parent; - } - } - } - static fillInReplacement(data) { - if (data.replacements) { - // already done - return; - } - // fill in parents first - if (data.parent) { - ClassData.fillInReplacement(data.parent); - } - data.replacements = new Map(); - const isNameTaken = (name) => { - // locally taken - if (data._isNameTaken(name)) { - return true; - } - // parents - let parent = data.parent; - while (parent) { - if (parent._isNameTaken(name)) { - return true; - } - parent = parent.parent; - } - // children - if (data.children) { - const stack = [...data.children]; - while (stack.length) { - const node = stack.pop(); - if (node._isNameTaken(name)) { - return true; - } - if (node.children) { - stack.push(...node.children); - } - } - } - return false; - }; - const identPool = new ShortIdent(''); - for (const [name, info] of data.fields) { - if (ClassData._shouldMangle(info.type)) { - const shortName = identPool.next(isNameTaken); - data.replacements.set(name, shortName); - } - } - } - // a name is taken when a field that doesn't get mangled exists or - // when the name is already in use for replacement - _isNameTaken(name) { - if (this.fields.has(name) && !ClassData._shouldMangle(this.fields.get(name).type)) { - // public field - return true; - } - if (this.replacements) { - for (const shortName of this.replacements.values()) { - if (shortName === name) { - // replaced already (happens wih super types) - return true; - } - } - } - if (isNameTakenInFile(this.node, name)) { - return true; - } - return false; - } - lookupShortName(name) { - let value = this.replacements.get(name); - let parent = this.parent; - while (parent) { - if (parent.replacements.has(name) && parent.fields.get(name)?.type === 1 /* FieldType.Protected */) { - value = parent.replacements.get(name) ?? value; - } - parent = parent.parent; - } - return value; - } - // --- parent chaining - addChild(child) { - this.children ??= []; - this.children.push(child); - child.parent = this; - } -} -function isNameTakenInFile(node, name) { - const identifiers = node.getSourceFile().identifiers; - if (identifiers instanceof Map) { - if (identifiers.has(name)) { - return true; - } - } - return false; -} -const skippedExportMangledFiles = [ - // Monaco - 'editorCommon', - 'editorOptions', - 'editorZoom', - 'standaloneEditor', - 'standaloneEnums', - 'standaloneLanguages', - // Generated - 'extensionsApiProposals', - // Module passed around as type - 'pfs', - // entry points - ...[ - buildfile.workerEditor, - buildfile.workerExtensionHost, - buildfile.workerNotebook, - buildfile.workerLanguageDetection, - buildfile.workerLocalFileSearch, - buildfile.workerProfileAnalysis, - buildfile.workerOutputLinks, - buildfile.workerBackgroundTokenization, - buildfile.workbenchDesktop, - buildfile.workbenchWeb, - buildfile.code, - buildfile.codeWeb - ].flat().map(x => x.name), -]; -const skippedExportMangledProjects = [ - // Test projects - 'vscode-api-tests', - // These projects use webpack to dynamically rewrite imports, which messes up our mangling - 'configuration-editing', - 'microsoft-authentication', - 'github-authentication', - 'html-language-features/server', -]; -const skippedExportMangledSymbols = [ - // Don't mangle extension entry points - 'activate', - 'deactivate', -]; -class DeclarationData { - fileName; - node; - replacementName; - constructor(fileName, node, fileIdents) { - this.fileName = fileName; - this.node = node; - // Todo: generate replacement names based on usage count, with more used names getting shorter identifiers - this.replacementName = fileIdents.next(); - } - getLocations(service) { - if (typescript_1.default.isVariableDeclaration(this.node)) { - // If the const aliases any types, we need to rename those too - const definitionResult = service.getDefinitionAndBoundSpan(this.fileName, this.node.name.getStart()); - if (definitionResult?.definitions && definitionResult.definitions.length > 1) { - return definitionResult.definitions.map(x => ({ fileName: x.fileName, offset: x.textSpan.start })); - } - } - return [{ - fileName: this.fileName, - offset: this.node.name.getStart() - }]; - } - shouldMangle(newName) { - const currentName = this.node.name.getText(); - if (currentName.startsWith('$') || skippedExportMangledSymbols.includes(currentName)) { - return false; - } - // New name is longer the existing one :'( - if (newName.length >= currentName.length) { - return false; - } - // Don't mangle functions we've explicitly opted out - if (this.node.getFullText().includes('@skipMangle')) { - return false; - } - return true; - } -} -/** - * TypeScript2TypeScript transformer that mangles all private and protected fields - * - * 1. Collect all class fields (properties, methods) - * 2. Collect all sub and super-type relations between classes - * 3. Compute replacement names for each field - * 4. Lookup rename locations for these fields - * 5. Prepare and apply edits - */ -class Mangler { - projectPath; - log; - config; - allClassDataByKey = new Map(); - allExportedSymbols = new Set(); - renameWorkerPool; - constructor(projectPath, log = () => { }, config) { - this.projectPath = projectPath; - this.log = log; - this.config = config; - this.renameWorkerPool = workerpool_1.default.pool(path_1.default.join(__dirname, 'renameWorker.js'), { - maxWorkers: 4, - minWorkers: 'max' - }); - } - async computeNewFileContents(strictImplicitPublicHandling) { - const service = typescript_1.default.createLanguageService(new staticLanguageServiceHost_1.StaticLanguageServiceHost(this.projectPath)); - // STEP: - // - Find all classes and their field info. - // - Find exported symbols. - const fileIdents = new ShortIdent('$'); - const visit = (node) => { - if (this.config.manglePrivateFields) { - if (typescript_1.default.isClassDeclaration(node) || typescript_1.default.isClassExpression(node)) { - const anchor = node.name ?? node; - const key = `${node.getSourceFile().fileName}|${anchor.getStart()}`; - if (this.allClassDataByKey.has(key)) { - throw new Error('DUPE?'); - } - this.allClassDataByKey.set(key, new ClassData(node.getSourceFile().fileName, node)); - } - } - if (this.config.mangleExports) { - // Find exported classes, functions, and vars - if (( - // Exported class - typescript_1.default.isClassDeclaration(node) - && hasModifier(node, typescript_1.default.SyntaxKind.ExportKeyword) - && node.name) || ( - // Exported function - typescript_1.default.isFunctionDeclaration(node) - && typescript_1.default.isSourceFile(node.parent) - && hasModifier(node, typescript_1.default.SyntaxKind.ExportKeyword) - && node.name && node.body // On named function and not on the overload - ) || ( - // Exported variable - typescript_1.default.isVariableDeclaration(node) - && hasModifier(node.parent.parent, typescript_1.default.SyntaxKind.ExportKeyword) // Variable statement is exported - && typescript_1.default.isSourceFile(node.parent.parent.parent)) - // Disabled for now because we need to figure out how to handle - // enums that are used in monaco or extHost interfaces. - /* || ( - // Exported enum - ts.isEnumDeclaration(node) - && ts.isSourceFile(node.parent) - && hasModifier(node, ts.SyntaxKind.ExportKeyword) - && !hasModifier(node, ts.SyntaxKind.ConstKeyword) // Don't bother mangling const enums because these are inlined - && node.name - */ - ) { - if (isInAmbientContext(node)) { - return; - } - this.allExportedSymbols.add(new DeclarationData(node.getSourceFile().fileName, node, fileIdents)); - } - } - typescript_1.default.forEachChild(node, visit); - }; - for (const file of service.getProgram().getSourceFiles()) { - if (!file.isDeclarationFile) { - typescript_1.default.forEachChild(file, visit); - } - } - this.log(`Done collecting. Classes: ${this.allClassDataByKey.size}. Exported symbols: ${this.allExportedSymbols.size}`); - // STEP: connect sub and super-types - const setupParents = (data) => { - const extendsClause = data.node.heritageClauses?.find(h => h.token === typescript_1.default.SyntaxKind.ExtendsKeyword); - if (!extendsClause) { - // no EXTENDS-clause - return; - } - const info = service.getDefinitionAtPosition(data.fileName, extendsClause.types[0].expression.getEnd()); - if (!info || info.length === 0) { - // throw new Error('SUPER type not found'); - return; - } - if (info.length !== 1) { - // inherits from declared/library type - return; - } - const [definition] = info; - const key = `${definition.fileName}|${definition.textSpan.start}`; - const parent = this.allClassDataByKey.get(key); - if (!parent) { - // throw new Error(`SUPER type not found: ${key}`); - return; - } - parent.addChild(data); - }; - for (const data of this.allClassDataByKey.values()) { - setupParents(data); - } - // STEP: make implicit public (actually protected) field really public - const violations = new Map(); - let violationsCauseFailure = false; - for (const data of this.allClassDataByKey.values()) { - ClassData.makeImplicitPublicActuallyPublic(data, (name, what, why) => { - const arr = violations.get(what); - if (arr) { - arr.push(why); - } - else { - violations.set(what, [why]); - } - if (strictImplicitPublicHandling && !strictImplicitPublicHandling.has(name)) { - violationsCauseFailure = true; - } - }); - } - for (const [why, whys] of violations) { - this.log(`WARN: ${why} became PUBLIC because of: ${whys.join(' , ')}`); - } - if (violationsCauseFailure) { - const message = 'Protected fields have been made PUBLIC. This hurts minification and is therefore not allowed. Review the WARN messages further above'; - this.log(`ERROR: ${message}`); - throw new Error(message); - } - // STEP: compute replacement names for each class - for (const data of this.allClassDataByKey.values()) { - ClassData.fillInReplacement(data); - } - this.log(`Done creating class replacements`); - // STEP: prepare rename edits - this.log(`Starting prepare rename edits`); - const editsByFile = new Map(); - const appendEdit = (fileName, edit) => { - const edits = editsByFile.get(fileName); - if (!edits) { - editsByFile.set(fileName, [edit]); - } - else { - edits.push(edit); - } - }; - const appendRename = (newText, loc) => { - appendEdit(loc.fileName, { - newText: (loc.prefixText || '') + newText + (loc.suffixText || ''), - offset: loc.textSpan.start, - length: loc.textSpan.length - }); - }; - const renameResults = []; - const queueRename = (fileName, pos, newName) => { - renameResults.push(Promise.resolve(this.renameWorkerPool.exec('findRenameLocations', [this.projectPath, fileName, pos])) - .then((locations) => ({ newName, locations }))); - }; - for (const data of this.allClassDataByKey.values()) { - if (hasModifier(data.node, typescript_1.default.SyntaxKind.DeclareKeyword)) { - continue; - } - fields: for (const [name, info] of data.fields) { - if (!ClassData._shouldMangle(info.type)) { - continue fields; - } - // TS-HACK: protected became public via 'some' child - // and because of that we might need to ignore this now - let parent = data.parent; - while (parent) { - if (parent.fields.get(name)?.type === 0 /* FieldType.Public */) { - continue fields; - } - parent = parent.parent; - } - const newName = data.lookupShortName(name); - queueRename(data.fileName, info.pos, newName); - } - } - for (const data of this.allExportedSymbols.values()) { - if (data.fileName.endsWith('.d.ts') - || skippedExportMangledProjects.some(proj => data.fileName.includes(proj)) - || skippedExportMangledFiles.some(file => data.fileName.endsWith(file + '.ts'))) { - continue; - } - if (!data.shouldMangle(data.replacementName)) { - continue; - } - const newText = data.replacementName; - for (const { fileName, offset } of data.getLocations(service)) { - queueRename(fileName, offset, newText); - } - } - await Promise.all(renameResults).then((result) => { - for (const { newName, locations } of result) { - for (const loc of locations) { - appendRename(newName, loc); - } - } - }); - await this.renameWorkerPool.terminate(); - this.log(`Done preparing edits: ${editsByFile.size} files`); - // STEP: apply all rename edits (per file) - const result = new Map(); - let savedBytes = 0; - for (const item of service.getProgram().getSourceFiles()) { - const { mapRoot, sourceRoot } = service.getProgram().getCompilerOptions(); - const projectDir = path_1.default.dirname(this.projectPath); - const sourceMapRoot = mapRoot ?? (0, url_1.pathToFileURL)(sourceRoot ?? projectDir).toString(); - // source maps - let generator; - let newFullText; - const edits = editsByFile.get(item.fileName); - if (!edits) { - // just copy - newFullText = item.getFullText(); - } - else { - // source map generator - const relativeFileName = normalize(path_1.default.relative(projectDir, item.fileName)); - const mappingsByLine = new Map(); - // apply renames - edits.sort((a, b) => b.offset - a.offset); - const characters = item.getFullText().split(''); - let lastEdit; - for (const edit of edits) { - if (lastEdit && lastEdit.offset === edit.offset) { - // - if (lastEdit.length !== edit.length || lastEdit.newText !== edit.newText) { - this.log('ERROR: Overlapping edit', item.fileName, edit.offset, edits); - throw new Error('OVERLAPPING edit'); - } - else { - continue; - } - } - lastEdit = edit; - const mangledName = characters.splice(edit.offset, edit.length, edit.newText).join(''); - savedBytes += mangledName.length - edit.newText.length; - // source maps - const pos = item.getLineAndCharacterOfPosition(edit.offset); - let mappings = mappingsByLine.get(pos.line); - if (!mappings) { - mappings = []; - mappingsByLine.set(pos.line, mappings); - } - mappings.unshift({ - source: relativeFileName, - original: { line: pos.line + 1, column: pos.character }, - generated: { line: pos.line + 1, column: pos.character }, - name: mangledName - }, { - source: relativeFileName, - original: { line: pos.line + 1, column: pos.character + edit.length }, - generated: { line: pos.line + 1, column: pos.character + edit.newText.length }, - }); - } - // source map generation, make sure to get mappings per line correct - generator = new source_map_1.SourceMapGenerator({ file: path_1.default.basename(item.fileName), sourceRoot: sourceMapRoot }); - generator.setSourceContent(relativeFileName, item.getFullText()); - for (const [, mappings] of mappingsByLine) { - let lineDelta = 0; - for (const mapping of mappings) { - generator.addMapping({ - ...mapping, - generated: { line: mapping.generated.line, column: mapping.generated.column - lineDelta } - }); - lineDelta += mapping.original.column - mapping.generated.column; - } - } - newFullText = characters.join(''); - } - result.set(item.fileName, { out: newFullText, sourceMap: generator?.toString() }); - } - service.dispose(); - this.renameWorkerPool.terminate(); - this.log(`Done: ${savedBytes / 1000}kb saved, memory-usage: ${JSON.stringify(node_v8_1.default.getHeapStatistics())}`); - return result; - } -} -exports.Mangler = Mangler; -// --- ast utils -function hasModifier(node, kind) { - const modifiers = typescript_1.default.canHaveModifiers(node) ? typescript_1.default.getModifiers(node) : undefined; - return Boolean(modifiers?.find(mode => mode.kind === kind)); -} -function isInAmbientContext(node) { - for (let p = node.parent; p; p = p.parent) { - if (typescript_1.default.isModuleDeclaration(p)) { - return true; - } - } - return false; -} -function normalize(path) { - return path.replace(/\\/g, '/'); -} -async function _run() { - const root = path_1.default.join(__dirname, '..', '..', '..'); - const projectBase = path_1.default.join(root, 'src'); - const projectPath = path_1.default.join(projectBase, 'tsconfig.json'); - const newProjectBase = path_1.default.join(path_1.default.dirname(projectBase), path_1.default.basename(projectBase) + '2'); - fs_1.default.cpSync(projectBase, newProjectBase, { recursive: true }); - const mangler = new Mangler(projectPath, console.log, { - mangleExports: true, - manglePrivateFields: true, - }); - for (const [fileName, contents] of await mangler.computeNewFileContents(new Set(['saveState']))) { - const newFilePath = path_1.default.join(newProjectBase, path_1.default.relative(projectBase, fileName)); - await fs_1.default.promises.mkdir(path_1.default.dirname(newFilePath), { recursive: true }); - await fs_1.default.promises.writeFile(newFilePath, contents.out); - if (contents.sourceMap) { - await fs_1.default.promises.writeFile(newFilePath + '.map', contents.sourceMap); - } - } -} -if (__filename === process_1.argv[1]) { - _run(); -} -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/build/lib/mangle/index.ts b/build/lib/mangle/index.ts index 2edc27ff55afe..e53c58d32ebc7 100644 --- a/build/lib/mangle/index.ts +++ b/build/lib/mangle/index.ts @@ -6,13 +6,12 @@ import v8 from 'node:v8'; import fs from 'fs'; import path from 'path'; -import { argv } from 'process'; -import { Mapping, SourceMapGenerator } from 'source-map'; +import { type Mapping, SourceMapGenerator } from 'source-map'; import ts from 'typescript'; import { pathToFileURL } from 'url'; import workerpool from 'workerpool'; -import { StaticLanguageServiceHost } from './staticLanguageServiceHost'; -const buildfile = require('../../buildfile'); +import { StaticLanguageServiceHost } from './staticLanguageServiceHost.ts'; +import * as buildfile from '../../buildfile.ts'; class ShortIdent { @@ -24,10 +23,13 @@ class ShortIdent { private static _alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890$_'.split(''); private _value = 0; + private readonly prefix: string; constructor( - private readonly prefix: string - ) { } + prefix: string + ) { + this.prefix = prefix; + } next(isNameTaken?: (name: string) => boolean): string { const candidate = this.prefix + ShortIdent.convert(this._value); @@ -51,11 +53,12 @@ class ShortIdent { } } -const enum FieldType { - Public, - Protected, - Private -} +const FieldType = Object.freeze({ + Public: 0, + Protected: 1, + Private: 2 +}); +type FieldType = typeof FieldType[keyof typeof FieldType]; class ClassData { @@ -66,10 +69,15 @@ class ClassData { parent: ClassData | undefined; children: ClassData[] | undefined; + readonly fileName: string; + readonly node: ts.ClassDeclaration | ts.ClassExpression; + constructor( - readonly fileName: string, - readonly node: ts.ClassDeclaration | ts.ClassExpression, + fileName: string, + node: ts.ClassDeclaration | ts.ClassExpression, ) { + this.fileName = fileName; + this.node = node; // analyse all fields (properties and methods). Find usages of all protected and // private ones and keep track of all public ones (to prevent naming collisions) @@ -269,8 +277,14 @@ class ClassData { } } +declare module 'typescript' { + interface SourceFile { + identifiers?: Map; + } +} + function isNameTakenInFile(node: ts.Node, name: string): boolean { - const identifiers = (node.getSourceFile()).identifiers; + const identifiers = node.getSourceFile().identifiers; if (identifiers instanceof Map) { if (identifiers.has(name)) { return true; @@ -332,12 +346,16 @@ const skippedExportMangledSymbols = [ class DeclarationData { readonly replacementName: string; + readonly fileName: string; + readonly node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.EnumDeclaration | ts.VariableDeclaration; constructor( - readonly fileName: string, - readonly node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.EnumDeclaration | ts.VariableDeclaration, + fileName: string, + node: ts.FunctionDeclaration | ts.ClassDeclaration | ts.EnumDeclaration | ts.VariableDeclaration, fileIdents: ShortIdent, ) { + this.fileName = fileName; + this.node = node; // Todo: generate replacement names based on usage count, with more used names getting shorter identifiers this.replacementName = fileIdents.next(); } @@ -398,13 +416,20 @@ export class Mangler { private readonly renameWorkerPool: workerpool.WorkerPool; + private readonly projectPath: string; + private readonly log: typeof console.log; + private readonly config: { readonly manglePrivateFields: boolean; readonly mangleExports: boolean }; + constructor( - private readonly projectPath: string, - private readonly log: typeof console.log = () => { }, - private readonly config: { readonly manglePrivateFields: boolean; readonly mangleExports: boolean }, + projectPath: string, + log: typeof console.log = () => { }, + config: { readonly manglePrivateFields: boolean; readonly mangleExports: boolean }, ) { + this.projectPath = projectPath; + this.log = log; + this.config = config; - this.renameWorkerPool = workerpool.pool(path.join(__dirname, 'renameWorker.js'), { + this.renameWorkerPool = workerpool.pool(path.join(import.meta.dirname, 'renameWorker.ts'), { maxWorkers: 4, minWorkers: 'max' }); @@ -747,7 +772,7 @@ function normalize(path: string): string { } async function _run() { - const root = path.join(__dirname, '..', '..', '..'); + const root = path.join(import.meta.dirname, '..', '..', '..'); const projectBase = path.join(root, 'src'); const projectPath = path.join(projectBase, 'tsconfig.json'); const newProjectBase = path.join(path.dirname(projectBase), path.basename(projectBase) + '2'); @@ -768,6 +793,6 @@ async function _run() { } } -if (__filename === argv[1]) { +if (import.meta.main) { _run(); } diff --git a/build/lib/mangle/renameWorker.js b/build/lib/mangle/renameWorker.js deleted file mode 100644 index 8bd59a4e2d552..0000000000000 --- a/build/lib/mangle/renameWorker.js +++ /dev/null @@ -1,25 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const typescript_1 = __importDefault(require("typescript")); -const workerpool_1 = __importDefault(require("workerpool")); -const staticLanguageServiceHost_1 = require("./staticLanguageServiceHost"); -let service; -function findRenameLocations(projectPath, fileName, position) { - if (!service) { - service = typescript_1.default.createLanguageService(new staticLanguageServiceHost_1.StaticLanguageServiceHost(projectPath)); - } - return service.findRenameLocations(fileName, position, false, false, { - providePrefixAndSuffixTextForRename: true, - }) ?? []; -} -workerpool_1.default.worker({ - findRenameLocations -}); -//# sourceMappingURL=renameWorker.js.map \ No newline at end of file diff --git a/build/lib/mangle/renameWorker.ts b/build/lib/mangle/renameWorker.ts index 0cce5677593c5..b7bfb53939836 100644 --- a/build/lib/mangle/renameWorker.ts +++ b/build/lib/mangle/renameWorker.ts @@ -5,7 +5,7 @@ import ts from 'typescript'; import workerpool from 'workerpool'; -import { StaticLanguageServiceHost } from './staticLanguageServiceHost'; +import { StaticLanguageServiceHost } from './staticLanguageServiceHost.ts'; let service: ts.LanguageService | undefined; diff --git a/build/lib/mangle/staticLanguageServiceHost.js b/build/lib/mangle/staticLanguageServiceHost.js deleted file mode 100644 index 7777888dd06a7..0000000000000 --- a/build/lib/mangle/staticLanguageServiceHost.js +++ /dev/null @@ -1,68 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.StaticLanguageServiceHost = void 0; -const typescript_1 = __importDefault(require("typescript")); -const path_1 = __importDefault(require("path")); -class StaticLanguageServiceHost { - projectPath; - _cmdLine; - _scriptSnapshots = new Map(); - constructor(projectPath) { - this.projectPath = projectPath; - const existingOptions = {}; - const parsed = typescript_1.default.readConfigFile(projectPath, typescript_1.default.sys.readFile); - if (parsed.error) { - throw parsed.error; - } - this._cmdLine = typescript_1.default.parseJsonConfigFileContent(parsed.config, typescript_1.default.sys, path_1.default.dirname(projectPath), existingOptions); - if (this._cmdLine.errors.length > 0) { - throw parsed.error; - } - } - getCompilationSettings() { - return this._cmdLine.options; - } - getScriptFileNames() { - return this._cmdLine.fileNames; - } - getScriptVersion(_fileName) { - return '1'; - } - getProjectVersion() { - return '1'; - } - getScriptSnapshot(fileName) { - let result = this._scriptSnapshots.get(fileName); - if (result === undefined) { - const content = typescript_1.default.sys.readFile(fileName); - if (content === undefined) { - return undefined; - } - result = typescript_1.default.ScriptSnapshot.fromString(content); - this._scriptSnapshots.set(fileName, result); - } - return result; - } - getCurrentDirectory() { - return path_1.default.dirname(this.projectPath); - } - getDefaultLibFileName(options) { - return typescript_1.default.getDefaultLibFilePath(options); - } - directoryExists = typescript_1.default.sys.directoryExists; - getDirectories = typescript_1.default.sys.getDirectories; - fileExists = typescript_1.default.sys.fileExists; - readFile = typescript_1.default.sys.readFile; - readDirectory = typescript_1.default.sys.readDirectory; - // this is necessary to make source references work. - realpath = typescript_1.default.sys.realpath; -} -exports.StaticLanguageServiceHost = StaticLanguageServiceHost; -//# sourceMappingURL=staticLanguageServiceHost.js.map \ No newline at end of file diff --git a/build/lib/mangle/staticLanguageServiceHost.ts b/build/lib/mangle/staticLanguageServiceHost.ts index b41b4e5213362..4fcf107f716b8 100644 --- a/build/lib/mangle/staticLanguageServiceHost.ts +++ b/build/lib/mangle/staticLanguageServiceHost.ts @@ -10,8 +10,10 @@ export class StaticLanguageServiceHost implements ts.LanguageServiceHost { private readonly _cmdLine: ts.ParsedCommandLine; private readonly _scriptSnapshots: Map = new Map(); + readonly projectPath: string; - constructor(readonly projectPath: string) { + constructor(projectPath: string) { + this.projectPath = projectPath; const existingOptions: Partial = {}; const parsed = ts.readConfigFile(projectPath, ts.sys.readFile); if (parsed.error) { diff --git a/build/lib/monaco-api.js b/build/lib/monaco-api.js deleted file mode 100644 index 84cc556cb6253..0000000000000 --- a/build/lib/monaco-api.js +++ /dev/null @@ -1,630 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.DeclarationResolver = exports.FSProvider = exports.RECIPE_PATH = void 0; -exports.run3 = run3; -exports.execute = execute; -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const fancy_log_1 = __importDefault(require("fancy-log")); -const ansi_colors_1 = __importDefault(require("ansi-colors")); -const dtsv = '3'; -const tsfmt = require('../../tsfmt.json'); -const SRC = path_1.default.join(__dirname, '../../src'); -exports.RECIPE_PATH = path_1.default.join(__dirname, '../monaco/monaco.d.ts.recipe'); -const DECLARATION_PATH = path_1.default.join(__dirname, '../../src/vs/monaco.d.ts'); -function logErr(message, ...rest) { - (0, fancy_log_1.default)(ansi_colors_1.default.yellow(`[monaco.d.ts]`), message, ...rest); -} -function isDeclaration(ts, a) { - return (a.kind === ts.SyntaxKind.InterfaceDeclaration - || a.kind === ts.SyntaxKind.EnumDeclaration - || a.kind === ts.SyntaxKind.ClassDeclaration - || a.kind === ts.SyntaxKind.TypeAliasDeclaration - || a.kind === ts.SyntaxKind.FunctionDeclaration - || a.kind === ts.SyntaxKind.ModuleDeclaration); -} -function visitTopLevelDeclarations(ts, sourceFile, visitor) { - let stop = false; - const visit = (node) => { - if (stop) { - return; - } - switch (node.kind) { - case ts.SyntaxKind.InterfaceDeclaration: - case ts.SyntaxKind.EnumDeclaration: - case ts.SyntaxKind.ClassDeclaration: - case ts.SyntaxKind.VariableStatement: - case ts.SyntaxKind.TypeAliasDeclaration: - case ts.SyntaxKind.FunctionDeclaration: - case ts.SyntaxKind.ModuleDeclaration: - stop = visitor(node); - } - if (stop) { - return; - } - ts.forEachChild(node, visit); - }; - visit(sourceFile); -} -function getAllTopLevelDeclarations(ts, sourceFile) { - const all = []; - visitTopLevelDeclarations(ts, sourceFile, (node) => { - if (node.kind === ts.SyntaxKind.InterfaceDeclaration || node.kind === ts.SyntaxKind.ClassDeclaration || node.kind === ts.SyntaxKind.ModuleDeclaration) { - const interfaceDeclaration = node; - const triviaStart = interfaceDeclaration.pos; - const triviaEnd = interfaceDeclaration.name.pos; - const triviaText = getNodeText(sourceFile, { pos: triviaStart, end: triviaEnd }); - if (triviaText.indexOf('@internal') === -1) { - all.push(node); - } - } - else { - const nodeText = getNodeText(sourceFile, node); - if (nodeText.indexOf('@internal') === -1) { - all.push(node); - } - } - return false /*continue*/; - }); - return all; -} -function getTopLevelDeclaration(ts, sourceFile, typeName) { - let result = null; - visitTopLevelDeclarations(ts, sourceFile, (node) => { - if (isDeclaration(ts, node) && node.name) { - if (node.name.text === typeName) { - result = node; - return true /*stop*/; - } - return false /*continue*/; - } - // node is ts.VariableStatement - if (getNodeText(sourceFile, node).indexOf(typeName) >= 0) { - result = node; - return true /*stop*/; - } - return false /*continue*/; - }); - return result; -} -function getNodeText(sourceFile, node) { - return sourceFile.getFullText().substring(node.pos, node.end); -} -function hasModifier(modifiers, kind) { - if (modifiers) { - for (let i = 0; i < modifiers.length; i++) { - const mod = modifiers[i]; - if (mod.kind === kind) { - return true; - } - } - } - return false; -} -function isStatic(ts, member) { - if (ts.canHaveModifiers(member)) { - return hasModifier(ts.getModifiers(member), ts.SyntaxKind.StaticKeyword); - } - return false; -} -function isDefaultExport(ts, declaration) { - return (hasModifier(declaration.modifiers, ts.SyntaxKind.DefaultKeyword) - && hasModifier(declaration.modifiers, ts.SyntaxKind.ExportKeyword)); -} -function getMassagedTopLevelDeclarationText(ts, sourceFile, declaration, importName, usage, enums) { - let result = getNodeText(sourceFile, declaration); - if (declaration.kind === ts.SyntaxKind.InterfaceDeclaration || declaration.kind === ts.SyntaxKind.ClassDeclaration) { - const interfaceDeclaration = declaration; - const staticTypeName = (isDefaultExport(ts, interfaceDeclaration) - ? `${importName}.default` - : `${importName}.${declaration.name.text}`); - let instanceTypeName = staticTypeName; - const typeParametersCnt = (interfaceDeclaration.typeParameters ? interfaceDeclaration.typeParameters.length : 0); - if (typeParametersCnt > 0) { - const arr = []; - for (let i = 0; i < typeParametersCnt; i++) { - arr.push('any'); - } - instanceTypeName = `${instanceTypeName}<${arr.join(',')}>`; - } - const members = interfaceDeclaration.members; - members.forEach((member) => { - try { - const memberText = getNodeText(sourceFile, member); - if (memberText.indexOf('@internal') >= 0 || memberText.indexOf('private') >= 0) { - result = result.replace(memberText, ''); - } - else { - const memberName = member.name.text; - const memberAccess = (memberName.indexOf('.') >= 0 ? `['${memberName}']` : `.${memberName}`); - if (isStatic(ts, member)) { - usage.push(`a = ${staticTypeName}${memberAccess};`); - } - else { - usage.push(`a = (<${instanceTypeName}>b)${memberAccess};`); - } - } - } - catch (err) { - // life.. - } - }); - } - result = result.replace(/export default /g, 'export '); - result = result.replace(/export declare /g, 'export '); - result = result.replace(/declare /g, ''); - const lines = result.split(/\r\n|\r|\n/); - for (let i = 0; i < lines.length; i++) { - if (/\s*\*/.test(lines[i])) { - // very likely a comment - continue; - } - lines[i] = lines[i].replace(/"/g, '\''); - } - result = lines.join('\n'); - if (declaration.kind === ts.SyntaxKind.EnumDeclaration) { - result = result.replace(/const enum/, 'enum'); - enums.push({ - enumName: declaration.name.getText(sourceFile), - text: result - }); - } - return result; -} -function format(ts, text, endl) { - const REALLY_FORMAT = false; - text = preformat(text, endl); - if (!REALLY_FORMAT) { - return text; - } - // Parse the source text - const sourceFile = ts.createSourceFile('file.ts', text, ts.ScriptTarget.Latest, /*setParentPointers*/ true); - // Get the formatting edits on the input sources - const edits = ts.formatting.formatDocument(sourceFile, getRuleProvider(tsfmt), tsfmt); - // Apply the edits on the input code - return applyEdits(text, edits); - function countParensCurly(text) { - let cnt = 0; - for (let i = 0; i < text.length; i++) { - if (text.charAt(i) === '(' || text.charAt(i) === '{') { - cnt++; - } - if (text.charAt(i) === ')' || text.charAt(i) === '}') { - cnt--; - } - } - return cnt; - } - function repeatStr(s, cnt) { - let r = ''; - for (let i = 0; i < cnt; i++) { - r += s; - } - return r; - } - function preformat(text, endl) { - const lines = text.split(endl); - let inComment = false; - let inCommentDeltaIndent = 0; - let indent = 0; - for (let i = 0; i < lines.length; i++) { - let line = lines[i].replace(/\s$/, ''); - let repeat = false; - let lineIndent = 0; - do { - repeat = false; - if (line.substring(0, 4) === ' ') { - line = line.substring(4); - lineIndent++; - repeat = true; - } - if (line.charAt(0) === '\t') { - line = line.substring(1); - lineIndent++; - repeat = true; - } - } while (repeat); - if (line.length === 0) { - continue; - } - if (inComment) { - if (/\*\//.test(line)) { - inComment = false; - } - lines[i] = repeatStr('\t', lineIndent + inCommentDeltaIndent) + line; - continue; - } - if (/\/\*/.test(line)) { - inComment = true; - inCommentDeltaIndent = indent - lineIndent; - lines[i] = repeatStr('\t', indent) + line; - continue; - } - const cnt = countParensCurly(line); - let shouldUnindentAfter = false; - let shouldUnindentBefore = false; - if (cnt < 0) { - if (/[({]/.test(line)) { - shouldUnindentAfter = true; - } - else { - shouldUnindentBefore = true; - } - } - else if (cnt === 0) { - shouldUnindentBefore = /^\}/.test(line); - } - let shouldIndentAfter = false; - if (cnt > 0) { - shouldIndentAfter = true; - } - else if (cnt === 0) { - shouldIndentAfter = /{$/.test(line); - } - if (shouldUnindentBefore) { - indent--; - } - lines[i] = repeatStr('\t', indent) + line; - if (shouldUnindentAfter) { - indent--; - } - if (shouldIndentAfter) { - indent++; - } - } - return lines.join(endl); - } - function getRuleProvider(options) { - // Share this between multiple formatters using the same options. - // This represents the bulk of the space the formatter uses. - return ts.formatting.getFormatContext(options); - } - function applyEdits(text, edits) { - // Apply edits in reverse on the existing text - let result = text; - for (let i = edits.length - 1; i >= 0; i--) { - const change = edits[i]; - const head = result.slice(0, change.span.start); - const tail = result.slice(change.span.start + change.span.length); - result = head + change.newText + tail; - } - return result; - } -} -function createReplacerFromDirectives(directives) { - return (str) => { - for (let i = 0; i < directives.length; i++) { - str = str.replace(directives[i][0], directives[i][1]); - } - return str; - }; -} -function createReplacer(data) { - data = data || ''; - const rawDirectives = data.split(';'); - const directives = []; - rawDirectives.forEach((rawDirective) => { - if (rawDirective.length === 0) { - return; - } - const pieces = rawDirective.split('=>'); - let findStr = pieces[0]; - const replaceStr = pieces[1]; - findStr = findStr.replace(/[\-\\\{\}\*\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&'); - findStr = '\\b' + findStr + '\\b'; - directives.push([new RegExp(findStr, 'g'), replaceStr]); - }); - return createReplacerFromDirectives(directives); -} -function generateDeclarationFile(ts, recipe, sourceFileGetter) { - const endl = /\r\n/.test(recipe) ? '\r\n' : '\n'; - const lines = recipe.split(endl); - const result = []; - let usageCounter = 0; - const usageImports = []; - const usage = []; - let failed = false; - usage.push(`var a: any;`); - usage.push(`var b: any;`); - const generateUsageImport = (moduleId) => { - const importName = 'm' + (++usageCounter); - usageImports.push(`import * as ${importName} from './${moduleId.replace(/\.d\.ts$/, '')}';`); - return importName; - }; - const enums = []; - let version = null; - lines.forEach(line => { - if (failed) { - return; - } - const m0 = line.match(/^\/\/dtsv=(\d+)$/); - if (m0) { - version = m0[1]; - } - const m1 = line.match(/^\s*#include\(([^;)]*)(;[^)]*)?\)\:(.*)$/); - if (m1) { - const moduleId = m1[1]; - const sourceFile = sourceFileGetter(moduleId); - if (!sourceFile) { - logErr(`While handling ${line}`); - logErr(`Cannot find ${moduleId}`); - failed = true; - return; - } - const importName = generateUsageImport(moduleId); - const replacer = createReplacer(m1[2]); - const typeNames = m1[3].split(/,/); - typeNames.forEach((typeName) => { - typeName = typeName.trim(); - if (typeName.length === 0) { - return; - } - const declaration = getTopLevelDeclaration(ts, sourceFile, typeName); - if (!declaration) { - logErr(`While handling ${line}`); - logErr(`Cannot find ${typeName}`); - failed = true; - return; - } - result.push(replacer(getMassagedTopLevelDeclarationText(ts, sourceFile, declaration, importName, usage, enums))); - }); - return; - } - const m2 = line.match(/^\s*#includeAll\(([^;)]*)(;[^)]*)?\)\:(.*)$/); - if (m2) { - const moduleId = m2[1]; - const sourceFile = sourceFileGetter(moduleId); - if (!sourceFile) { - logErr(`While handling ${line}`); - logErr(`Cannot find ${moduleId}`); - failed = true; - return; - } - const importName = generateUsageImport(moduleId); - const replacer = createReplacer(m2[2]); - const typeNames = m2[3].split(/,/); - const typesToExcludeMap = {}; - const typesToExcludeArr = []; - typeNames.forEach((typeName) => { - typeName = typeName.trim(); - if (typeName.length === 0) { - return; - } - typesToExcludeMap[typeName] = true; - typesToExcludeArr.push(typeName); - }); - getAllTopLevelDeclarations(ts, sourceFile).forEach((declaration) => { - if (isDeclaration(ts, declaration) && declaration.name) { - if (typesToExcludeMap[declaration.name.text]) { - return; - } - } - else { - // node is ts.VariableStatement - const nodeText = getNodeText(sourceFile, declaration); - for (let i = 0; i < typesToExcludeArr.length; i++) { - if (nodeText.indexOf(typesToExcludeArr[i]) >= 0) { - return; - } - } - } - result.push(replacer(getMassagedTopLevelDeclarationText(ts, sourceFile, declaration, importName, usage, enums))); - }); - return; - } - result.push(line); - }); - if (failed) { - return null; - } - if (version !== dtsv) { - if (!version) { - logErr(`gulp watch restart required. 'monaco.d.ts.recipe' is written before versioning was introduced.`); - } - else { - logErr(`gulp watch restart required. 'monaco.d.ts.recipe' v${version} does not match runtime v${dtsv}.`); - } - return null; - } - let resultTxt = result.join(endl); - resultTxt = resultTxt.replace(/\bURI\b/g, 'Uri'); - resultTxt = resultTxt.replace(/\bEvent { - if (e1.enumName < e2.enumName) { - return -1; - } - if (e1.enumName > e2.enumName) { - return 1; - } - return 0; - }); - let resultEnums = [ - '/*---------------------------------------------------------------------------------------------', - ' * Copyright (c) Microsoft Corporation. All rights reserved.', - ' * Licensed under the MIT License. See License.txt in the project root for license information.', - ' *--------------------------------------------------------------------------------------------*/', - '', - '// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.', - '' - ].concat(enums.map(e => e.text)).join(endl); - resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl); - resultEnums = format(ts, resultEnums, endl); - resultEnums = resultEnums.split(/\r\n|\n|\r/).join(endl); - return { - result: resultTxt, - usageContent: `${usageImports.join('\n')}\n\n${usage.join('\n')}`, - enums: resultEnums - }; -} -function _run(ts, sourceFileGetter) { - const recipe = fs_1.default.readFileSync(exports.RECIPE_PATH).toString(); - const t = generateDeclarationFile(ts, recipe, sourceFileGetter); - if (!t) { - return null; - } - const result = t.result; - const usageContent = t.usageContent; - const enums = t.enums; - const currentContent = fs_1.default.readFileSync(DECLARATION_PATH).toString(); - const one = currentContent.replace(/\r\n/gm, '\n'); - const other = result.replace(/\r\n/gm, '\n'); - const isTheSame = (one === other); - return { - content: result, - usageContent: usageContent, - enums: enums, - filePath: DECLARATION_PATH, - isTheSame - }; -} -class FSProvider { - existsSync(filePath) { - return fs_1.default.existsSync(filePath); - } - statSync(filePath) { - return fs_1.default.statSync(filePath); - } - readFileSync(_moduleId, filePath) { - return fs_1.default.readFileSync(filePath); - } -} -exports.FSProvider = FSProvider; -class CacheEntry { - sourceFile; - mtime; - constructor(sourceFile, mtime) { - this.sourceFile = sourceFile; - this.mtime = mtime; - } -} -class DeclarationResolver { - _fsProvider; - ts; - _sourceFileCache; - constructor(_fsProvider) { - this._fsProvider = _fsProvider; - this.ts = require('typescript'); - this._sourceFileCache = Object.create(null); - } - invalidateCache(moduleId) { - this._sourceFileCache[moduleId] = null; - } - getDeclarationSourceFile(moduleId) { - if (this._sourceFileCache[moduleId]) { - // Since we cannot trust file watching to invalidate the cache, check also the mtime - const fileName = this._getFileName(moduleId); - const mtime = this._fsProvider.statSync(fileName).mtime.getTime(); - if (this._sourceFileCache[moduleId].mtime !== mtime) { - this._sourceFileCache[moduleId] = null; - } - } - if (!this._sourceFileCache[moduleId]) { - this._sourceFileCache[moduleId] = this._getDeclarationSourceFile(moduleId); - } - return this._sourceFileCache[moduleId] ? this._sourceFileCache[moduleId].sourceFile : null; - } - _getFileName(moduleId) { - if (/\.d\.ts$/.test(moduleId)) { - return path_1.default.join(SRC, moduleId); - } - return path_1.default.join(SRC, `${moduleId}.ts`); - } - _getDeclarationSourceFile(moduleId) { - const fileName = this._getFileName(moduleId); - if (!this._fsProvider.existsSync(fileName)) { - return null; - } - const mtime = this._fsProvider.statSync(fileName).mtime.getTime(); - if (/\.d\.ts$/.test(moduleId)) { - // const mtime = this._fsProvider.statFileSync() - const fileContents = this._fsProvider.readFileSync(moduleId, fileName).toString(); - return new CacheEntry(this.ts.createSourceFile(fileName, fileContents, this.ts.ScriptTarget.ES5), mtime); - } - const fileContents = this._fsProvider.readFileSync(moduleId, fileName).toString(); - const fileMap = { - 'file.ts': fileContents - }; - const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, {}, fileMap, {})); - const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text; - return new CacheEntry(this.ts.createSourceFile(fileName, text, this.ts.ScriptTarget.ES5), mtime); - } -} -exports.DeclarationResolver = DeclarationResolver; -function run3(resolver) { - const sourceFileGetter = (moduleId) => resolver.getDeclarationSourceFile(moduleId); - return _run(resolver.ts, sourceFileGetter); -} -class TypeScriptLanguageServiceHost { - _ts; - _libs; - _files; - _compilerOptions; - constructor(ts, libs, files, compilerOptions) { - this._ts = ts; - this._libs = libs; - this._files = files; - this._compilerOptions = compilerOptions; - } - // --- language service host --------------- - getCompilationSettings() { - return this._compilerOptions; - } - getScriptFileNames() { - return ([] - .concat(Object.keys(this._libs)) - .concat(Object.keys(this._files))); - } - getScriptVersion(_fileName) { - return '1'; - } - getProjectVersion() { - return '1'; - } - getScriptSnapshot(fileName) { - if (this._files.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files[fileName]); - } - else if (this._libs.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs[fileName]); - } - else { - return this._ts.ScriptSnapshot.fromString(''); - } - } - getScriptKind(_fileName) { - return this._ts.ScriptKind.TS; - } - getCurrentDirectory() { - return ''; - } - getDefaultLibFileName(_options) { - return 'defaultLib:es5'; - } - isDefaultLibFileName(fileName) { - return fileName === this.getDefaultLibFileName(this._compilerOptions); - } - readFile(path, _encoding) { - return this._files[path] || this._libs[path]; - } - fileExists(path) { - return path in this._files || path in this._libs; - } -} -function execute() { - const r = run3(new DeclarationResolver(new FSProvider())); - if (!r) { - throw new Error(`monaco.d.ts generation error - Cannot continue`); - } - return r; -} -//# sourceMappingURL=monaco-api.js.map \ No newline at end of file diff --git a/build/lib/monaco-api.ts b/build/lib/monaco-api.ts index 5dc9a04266c51..fa6c2a28c9144 100644 --- a/build/lib/monaco-api.ts +++ b/build/lib/monaco-api.ts @@ -4,20 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import fs from 'fs'; -import type * as ts from 'typescript'; import path from 'path'; import fancyLog from 'fancy-log'; import ansiColors from 'ansi-colors'; +import { type IFileMap, TypeScriptLanguageServiceHost } from './typeScriptLanguageServiceHost.ts'; +import ts from 'typescript'; -const dtsv = '3'; +import tsfmt from '../../tsfmt.json' with { type: 'json' }; -const tsfmt = require('../../tsfmt.json'); +const dtsv = '3'; -const SRC = path.join(__dirname, '../../src'); -export const RECIPE_PATH = path.join(__dirname, '../monaco/monaco.d.ts.recipe'); -const DECLARATION_PATH = path.join(__dirname, '../../src/vs/monaco.d.ts'); +const SRC = path.join(import.meta.dirname, '../../src'); +export const RECIPE_PATH = path.join(import.meta.dirname, '../monaco/monaco.d.ts.recipe'); +const DECLARATION_PATH = path.join(import.meta.dirname, '../../src/vs/monaco.d.ts'); -function logErr(message: any, ...rest: any[]): void { +function logErr(message: any, ...rest: unknown[]): void { fancyLog(ansiColors.yellow(`[monaco.d.ts]`), message, ...rest); } @@ -53,7 +54,7 @@ function visitTopLevelDeclarations(ts: typeof import('typescript'), sourceFile: case ts.SyntaxKind.TypeAliasDeclaration: case ts.SyntaxKind.FunctionDeclaration: case ts.SyntaxKind.ModuleDeclaration: - stop = visitor(node); + stop = visitor(node as TSTopLevelDeclare); } if (stop) { @@ -70,7 +71,7 @@ function getAllTopLevelDeclarations(ts: typeof import('typescript'), sourceFile: const all: TSTopLevelDeclare[] = []; visitTopLevelDeclarations(ts, sourceFile, (node) => { if (node.kind === ts.SyntaxKind.InterfaceDeclaration || node.kind === ts.SyntaxKind.ClassDeclaration || node.kind === ts.SyntaxKind.ModuleDeclaration) { - const interfaceDeclaration = node; + const interfaceDeclaration = node as ts.InterfaceDeclaration; const triviaStart = interfaceDeclaration.pos; const triviaEnd = interfaceDeclaration.name.pos; const triviaText = getNodeText(sourceFile, { pos: triviaStart, end: triviaEnd }); @@ -144,7 +145,7 @@ function isDefaultExport(ts: typeof import('typescript'), declaration: ts.Interf function getMassagedTopLevelDeclarationText(ts: typeof import('typescript'), sourceFile: ts.SourceFile, declaration: TSTopLevelDeclare, importName: string, usage: string[], enums: IEnumEntry[]): string { let result = getNodeText(sourceFile, declaration); if (declaration.kind === ts.SyntaxKind.InterfaceDeclaration || declaration.kind === ts.SyntaxKind.ClassDeclaration) { - const interfaceDeclaration = declaration; + const interfaceDeclaration = declaration as ts.InterfaceDeclaration | ts.ClassDeclaration; const staticTypeName = ( isDefaultExport(ts, interfaceDeclaration) @@ -169,7 +170,7 @@ function getMassagedTopLevelDeclarationText(ts: typeof import('typescript'), sou if (memberText.indexOf('@internal') >= 0 || memberText.indexOf('private') >= 0) { result = result.replace(memberText, ''); } else { - const memberName = (member.name).text; + const memberName = (member.name as ts.Identifier | ts.StringLiteral).text; const memberAccess = (memberName.indexOf('.') >= 0 ? `['${memberName}']` : `.${memberName}`); if (isStatic(ts, member)) { usage.push(`a = ${staticTypeName}${memberAccess};`); @@ -206,7 +207,14 @@ function getMassagedTopLevelDeclarationText(ts: typeof import('typescript'), sou return result; } -function format(ts: typeof import('typescript'), text: string, endl: string): string { +interface Formatting { + getFormatContext(options: ts.FormatCodeSettings): TContext; + formatDocument(file: ts.SourceFile, ruleProvider: TContext, options: ts.FormatCodeSettings): ts.TextChange[]; +} + +type Typescript = typeof import('typescript') & { readonly formatting: Formatting }; + +function format(ts: Typescript, text: string, endl: string): string { const REALLY_FORMAT = false; text = preformat(text, endl); @@ -218,7 +226,7 @@ function format(ts: typeof import('typescript'), text: string, endl: string): st const sourceFile = ts.createSourceFile('file.ts', text, ts.ScriptTarget.Latest, /*setParentPointers*/ true); // Get the formatting edits on the input sources - const edits = (ts).formatting.formatDocument(sourceFile, getRuleProvider(tsfmt), tsfmt); + const edits = ts.formatting.formatDocument(sourceFile, getRuleProvider(tsfmt), tsfmt); // Apply the edits on the input code return applyEdits(text, edits); @@ -324,7 +332,8 @@ function format(ts: typeof import('typescript'), text: string, endl: string): st function getRuleProvider(options: ts.FormatCodeSettings) { // Share this between multiple formatters using the same options. // This represents the bulk of the space the formatter uses. - return (ts as any).formatting.getFormatContext(options); + + return ts.formatting.getFormatContext(options); } function applyEdits(text: string, edits: ts.TextChange[]): string { @@ -380,7 +389,7 @@ interface IEnumEntry { text: string; } -function generateDeclarationFile(ts: typeof import('typescript'), recipe: string, sourceFileGetter: SourceFileGetter): ITempResult | null { +function generateDeclarationFile(ts: Typescript, recipe: string, sourceFileGetter: SourceFileGetter): ITempResult | null { const endl = /\r\n/.test(recipe) ? '\r\n' : '\n'; const lines = recipe.split(endl); @@ -397,7 +406,7 @@ function generateDeclarationFile(ts: typeof import('typescript'), recipe: string const generateUsageImport = (moduleId: string) => { const importName = 'm' + (++usageCounter); - usageImports.push(`import * as ${importName} from './${moduleId.replace(/\.d\.ts$/, '')}';`); + usageImports.push(`import * as ${importName} from './${moduleId}';`); return importName; }; @@ -555,7 +564,7 @@ export interface IMonacoDeclarationResult { isTheSame: boolean; } -function _run(ts: typeof import('typescript'), sourceFileGetter: SourceFileGetter): IMonacoDeclarationResult | null { +function _run(ts: Typescript, sourceFileGetter: SourceFileGetter): IMonacoDeclarationResult | null { const recipe = fs.readFileSync(RECIPE_PATH).toString(); const t = generateDeclarationFile(ts, recipe, sourceFileGetter); if (!t) { @@ -593,19 +602,27 @@ export class FSProvider { } class CacheEntry { + public readonly sourceFile: ts.SourceFile; + public readonly mtime: number; + constructor( - public readonly sourceFile: ts.SourceFile, - public readonly mtime: number - ) { } + sourceFile: ts.SourceFile, + mtime: number + ) { + this.sourceFile = sourceFile; + this.mtime = mtime; + } } export class DeclarationResolver { public readonly ts: typeof import('typescript'); private _sourceFileCache: { [moduleId: string]: CacheEntry | null }; + private readonly _fsProvider: FSProvider; - constructor(private readonly _fsProvider: FSProvider) { - this.ts = require('typescript') as typeof import('typescript'); + constructor(fsProvider: FSProvider) { + this._fsProvider = fsProvider; + this.ts = ts; this._sourceFileCache = Object.create(null); } @@ -632,6 +649,9 @@ export class DeclarationResolver { if (/\.d\.ts$/.test(moduleId)) { return path.join(SRC, moduleId); } + if (/\.js$/.test(moduleId)) { + return path.join(SRC, moduleId.replace(/\.js$/, '.ts')); + } return path.join(SRC, `${moduleId}.ts`); } @@ -650,10 +670,10 @@ export class DeclarationResolver { ); } const fileContents = this._fsProvider.readFileSync(moduleId, fileName).toString(); - const fileMap: IFileMap = { - 'file.ts': fileContents - }; - const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, {}, fileMap, {})); + const fileMap: IFileMap = new Map([ + ['file.ts', fileContents] + ]); + const service = this.ts.createLanguageService(new TypeScriptLanguageServiceHost(this.ts, fileMap, {})); const text = service.getEmitOutput('file.ts', true, true).outputFiles[0].text; return new CacheEntry( this.ts.createSourceFile(fileName, text, this.ts.ScriptTarget.ES5), @@ -664,76 +684,10 @@ export class DeclarationResolver { export function run3(resolver: DeclarationResolver): IMonacoDeclarationResult | null { const sourceFileGetter = (moduleId: string) => resolver.getDeclarationSourceFile(moduleId); - return _run(resolver.ts, sourceFileGetter); + return _run(resolver.ts as Typescript, sourceFileGetter); } - - -interface ILibMap { [libName: string]: string } -interface IFileMap { [fileName: string]: string } - -class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { - - private readonly _ts: typeof import('typescript'); - private readonly _libs: ILibMap; - private readonly _files: IFileMap; - private readonly _compilerOptions: ts.CompilerOptions; - - constructor(ts: typeof import('typescript'), libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) { - this._ts = ts; - this._libs = libs; - this._files = files; - this._compilerOptions = compilerOptions; - } - - // --- language service host --------------- - - getCompilationSettings(): ts.CompilerOptions { - return this._compilerOptions; - } - getScriptFileNames(): string[] { - return ( - ([] as string[]) - .concat(Object.keys(this._libs)) - .concat(Object.keys(this._files)) - ); - } - getScriptVersion(_fileName: string): string { - return '1'; - } - getProjectVersion(): string { - return '1'; - } - getScriptSnapshot(fileName: string): ts.IScriptSnapshot { - if (this._files.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files[fileName]); - } else if (this._libs.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs[fileName]); - } else { - return this._ts.ScriptSnapshot.fromString(''); - } - } - getScriptKind(_fileName: string): ts.ScriptKind { - return this._ts.ScriptKind.TS; - } - getCurrentDirectory(): string { - return ''; - } - getDefaultLibFileName(_options: ts.CompilerOptions): string { - return 'defaultLib:es5'; - } - isDefaultLibFileName(fileName: string): boolean { - return fileName === this.getDefaultLibFileName(this._compilerOptions); - } - readFile(path: string, _encoding?: string): string | undefined { - return this._files[path] || this._libs[path]; - } - fileExists(path: string): boolean { - return path in this._files || path in this._libs; - } -} - export function execute(): IMonacoDeclarationResult { const r = run3(new DeclarationResolver(new FSProvider())); if (!r) { diff --git a/build/lib/nls.js b/build/lib/nls.js deleted file mode 100644 index 12e60a36ec99f..0000000000000 --- a/build/lib/nls.js +++ /dev/null @@ -1,411 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.nls = nls; -const lazy_js_1 = __importDefault(require("lazy.js")); -const event_stream_1 = require("event-stream"); -const vinyl_1 = __importDefault(require("vinyl")); -const source_map_1 = __importDefault(require("source-map")); -const path_1 = __importDefault(require("path")); -const gulp_sort_1 = __importDefault(require("gulp-sort")); -var CollectStepResult; -(function (CollectStepResult) { - CollectStepResult[CollectStepResult["Yes"] = 0] = "Yes"; - CollectStepResult[CollectStepResult["YesAndRecurse"] = 1] = "YesAndRecurse"; - CollectStepResult[CollectStepResult["No"] = 2] = "No"; - CollectStepResult[CollectStepResult["NoAndRecurse"] = 3] = "NoAndRecurse"; -})(CollectStepResult || (CollectStepResult = {})); -function collect(ts, node, fn) { - const result = []; - function loop(node) { - const stepResult = fn(node); - if (stepResult === CollectStepResult.Yes || stepResult === CollectStepResult.YesAndRecurse) { - result.push(node); - } - if (stepResult === CollectStepResult.YesAndRecurse || stepResult === CollectStepResult.NoAndRecurse) { - ts.forEachChild(node, loop); - } - } - loop(node); - return result; -} -function clone(object) { - const result = {}; - for (const id in object) { - result[id] = object[id]; - } - return result; -} -/** - * Returns a stream containing the patched JavaScript and source maps. - */ -function nls(options) { - let base; - const input = (0, event_stream_1.through)(); - const output = input - .pipe((0, gulp_sort_1.default)()) // IMPORTANT: to ensure stable NLS metadata generation, we must sort the files because NLS messages are globally extracted and indexed across all files - .pipe((0, event_stream_1.through)(function (f) { - if (!f.sourceMap) { - return this.emit('error', new Error(`File ${f.relative} does not have sourcemaps.`)); - } - let source = f.sourceMap.sources[0]; - if (!source) { - return this.emit('error', new Error(`File ${f.relative} does not have a source in the source map.`)); - } - const root = f.sourceMap.sourceRoot; - if (root) { - source = path_1.default.join(root, source); - } - const typescript = f.sourceMap.sourcesContent[0]; - if (!typescript) { - return this.emit('error', new Error(`File ${f.relative} does not have the original content in the source map.`)); - } - base = f.base; - this.emit('data', _nls.patchFile(f, typescript, options)); - }, function () { - for (const file of [ - new vinyl_1.default({ - contents: Buffer.from(JSON.stringify({ - keys: _nls.moduleToNLSKeys, - messages: _nls.moduleToNLSMessages, - }, null, '\t')), - base, - path: `${base}/nls.metadata.json` - }), - new vinyl_1.default({ - contents: Buffer.from(JSON.stringify(_nls.allNLSMessages)), - base, - path: `${base}/nls.messages.json` - }), - new vinyl_1.default({ - contents: Buffer.from(JSON.stringify(_nls.allNLSModulesAndKeys)), - base, - path: `${base}/nls.keys.json` - }), - new vinyl_1.default({ - contents: Buffer.from(`/*--------------------------------------------------------- - * Copyright (C) Microsoft Corporation. All rights reserved. - *--------------------------------------------------------*/ -globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(_nls.allNLSMessages)};`), - base, - path: `${base}/nls.messages.js` - }) - ]) { - this.emit('data', file); - } - this.emit('end'); - })); - return (0, event_stream_1.duplex)(input, output); -} -function isImportNode(ts, node) { - return node.kind === ts.SyntaxKind.ImportDeclaration || node.kind === ts.SyntaxKind.ImportEqualsDeclaration; -} -var _nls; -(function (_nls) { - _nls.moduleToNLSKeys = {}; - _nls.moduleToNLSMessages = {}; - _nls.allNLSMessages = []; - _nls.allNLSModulesAndKeys = []; - let allNLSMessagesIndex = 0; - function fileFrom(file, contents, path = file.path) { - return new vinyl_1.default({ - contents: Buffer.from(contents), - base: file.base, - cwd: file.cwd, - path: path - }); - } - function mappedPositionFrom(source, lc) { - return { source, line: lc.line + 1, column: lc.character }; - } - function lcFrom(position) { - return { line: position.line - 1, character: position.column }; - } - class SingleFileServiceHost { - options; - filename; - file; - lib; - constructor(ts, options, filename, contents) { - this.options = options; - this.filename = filename; - this.file = ts.ScriptSnapshot.fromString(contents); - this.lib = ts.ScriptSnapshot.fromString(''); - } - getCompilationSettings = () => this.options; - getScriptFileNames = () => [this.filename]; - getScriptVersion = () => '1'; - getScriptSnapshot = (name) => name === this.filename ? this.file : this.lib; - getCurrentDirectory = () => ''; - getDefaultLibFileName = () => 'lib.d.ts'; - readFile(path, _encoding) { - if (path === this.filename) { - return this.file.getText(0, this.file.getLength()); - } - return undefined; - } - fileExists(path) { - return path === this.filename; - } - } - function isCallExpressionWithinTextSpanCollectStep(ts, textSpan, node) { - if (!ts.textSpanContainsTextSpan({ start: node.pos, length: node.end - node.pos }, textSpan)) { - return CollectStepResult.No; - } - return node.kind === ts.SyntaxKind.CallExpression ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse; - } - function analyze(ts, contents, functionName, options = {}) { - const filename = 'file.ts'; - const serviceHost = new SingleFileServiceHost(ts, Object.assign(clone(options), { noResolve: true }), filename, contents); - const service = ts.createLanguageService(serviceHost); - const sourceFile = ts.createSourceFile(filename, contents, ts.ScriptTarget.ES5, true); - // all imports - const imports = (0, lazy_js_1.default)(collect(ts, sourceFile, n => isImportNode(ts, n) ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse)); - // import nls = require('vs/nls'); - const importEqualsDeclarations = imports - .filter(n => n.kind === ts.SyntaxKind.ImportEqualsDeclaration) - .map(n => n) - .filter(d => d.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) - .filter(d => d.moduleReference.expression.getText().endsWith(`/nls.js'`)); - // import ... from 'vs/nls'; - const importDeclarations = imports - .filter(n => n.kind === ts.SyntaxKind.ImportDeclaration) - .map(n => n) - .filter(d => d.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral) - .filter(d => d.moduleSpecifier.getText().endsWith(`/nls.js'`)) - .filter(d => !!d.importClause && !!d.importClause.namedBindings); - // `nls.localize(...)` calls - const nlsLocalizeCallExpressions = importDeclarations - .filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport)) - .map(d => d.importClause.namedBindings.name) - .concat(importEqualsDeclarations.map(d => d.name)) - // find read-only references to `nls` - .map(n => service.getReferencesAtPosition(filename, n.pos + 1)) - .flatten() - .filter(r => !r.isWriteAccess) - // find the deepest call expressions AST nodes that contain those references - .map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n))) - .map(a => (0, lazy_js_1.default)(a).last()) - .filter(n => !!n) - .map(n => n) - // only `localize` calls - .filter(n => n.expression.kind === ts.SyntaxKind.PropertyAccessExpression && n.expression.name.getText() === functionName); - // `localize` named imports - const allLocalizeImportDeclarations = importDeclarations - .filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamedImports)) - .map(d => [].concat(d.importClause.namedBindings.elements)) - .flatten(); - // `localize` read-only references - const localizeReferences = allLocalizeImportDeclarations - .filter(d => d.name.getText() === functionName) - .map(n => service.getReferencesAtPosition(filename, n.pos + 1)) - .flatten() - .filter(r => !r.isWriteAccess); - // custom named `localize` read-only references - const namedLocalizeReferences = allLocalizeImportDeclarations - .filter(d => d.propertyName && d.propertyName.getText() === functionName) - .map(n => service.getReferencesAtPosition(filename, n.name.pos + 1)) - .flatten() - .filter(r => !r.isWriteAccess); - // find the deepest call expressions AST nodes that contain those references - const localizeCallExpressions = localizeReferences - .concat(namedLocalizeReferences) - .map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n))) - .map(a => (0, lazy_js_1.default)(a).last()) - .filter(n => !!n) - .map(n => n); - // collect everything - const localizeCalls = nlsLocalizeCallExpressions - .concat(localizeCallExpressions) - .map(e => e.arguments) - .filter(a => a.length > 1) - .sort((a, b) => a[0].getStart() - b[0].getStart()) - .map(a => ({ - keySpan: { start: ts.getLineAndCharacterOfPosition(sourceFile, a[0].getStart()), end: ts.getLineAndCharacterOfPosition(sourceFile, a[0].getEnd()) }, - key: a[0].getText(), - valueSpan: { start: ts.getLineAndCharacterOfPosition(sourceFile, a[1].getStart()), end: ts.getLineAndCharacterOfPosition(sourceFile, a[1].getEnd()) }, - value: a[1].getText() - })); - return { - localizeCalls: localizeCalls.toArray() - }; - } - class TextModel { - lines; - lineEndings; - constructor(contents) { - const regex = /\r\n|\r|\n/g; - let index = 0; - let match; - this.lines = []; - this.lineEndings = []; - while (match = regex.exec(contents)) { - this.lines.push(contents.substring(index, match.index)); - this.lineEndings.push(match[0]); - index = regex.lastIndex; - } - if (contents.length > 0) { - this.lines.push(contents.substring(index, contents.length)); - this.lineEndings.push(''); - } - } - get(index) { - return this.lines[index]; - } - set(index, line) { - this.lines[index] = line; - } - get lineCount() { - return this.lines.length; - } - /** - * Applies patch(es) to the model. - * Multiple patches must be ordered. - * Does not support patches spanning multiple lines. - */ - apply(patch) { - const startLineNumber = patch.span.start.line; - const endLineNumber = patch.span.end.line; - const startLine = this.lines[startLineNumber] || ''; - const endLine = this.lines[endLineNumber] || ''; - this.lines[startLineNumber] = [ - startLine.substring(0, patch.span.start.character), - patch.content, - endLine.substring(patch.span.end.character) - ].join(''); - for (let i = startLineNumber + 1; i <= endLineNumber; i++) { - this.lines[i] = ''; - } - } - toString() { - return (0, lazy_js_1.default)(this.lines).zip(this.lineEndings) - .flatten().toArray().join(''); - } - } - function patchJavascript(patches, contents) { - const model = new TextModel(contents); - // patch the localize calls - (0, lazy_js_1.default)(patches).reverse().each(p => model.apply(p)); - return model.toString(); - } - function patchSourcemap(patches, rsm, smc) { - const smg = new source_map_1.default.SourceMapGenerator({ - file: rsm.file, - sourceRoot: rsm.sourceRoot - }); - patches = patches.reverse(); - let currentLine = -1; - let currentLineDiff = 0; - let source = null; - smc.eachMapping(m => { - const patch = patches[patches.length - 1]; - const original = { line: m.originalLine, column: m.originalColumn }; - const generated = { line: m.generatedLine, column: m.generatedColumn }; - if (currentLine !== generated.line) { - currentLineDiff = 0; - } - currentLine = generated.line; - generated.column += currentLineDiff; - if (patch && m.generatedLine - 1 === patch.span.end.line && m.generatedColumn === patch.span.end.character) { - const originalLength = patch.span.end.character - patch.span.start.character; - const modifiedLength = patch.content.length; - const lengthDiff = modifiedLength - originalLength; - currentLineDiff += lengthDiff; - generated.column += lengthDiff; - patches.pop(); - } - source = rsm.sourceRoot ? path_1.default.relative(rsm.sourceRoot, m.source) : m.source; - source = source.replace(/\\/g, '/'); - smg.addMapping({ source, name: m.name, original, generated }); - }, null, source_map_1.default.SourceMapConsumer.GENERATED_ORDER); - if (source) { - smg.setSourceContent(source, smc.sourceContentFor(source)); - } - return JSON.parse(smg.toString()); - } - function parseLocalizeKeyOrValue(sourceExpression) { - // sourceValue can be "foo", 'foo', `foo` or { .... } - // in its evalulated form - // we want to return either the string or the object - // eslint-disable-next-line no-eval - return eval(`(${sourceExpression})`); - } - function patch(ts, typescript, javascript, sourcemap, options) { - const { localizeCalls } = analyze(ts, typescript, 'localize'); - const { localizeCalls: localize2Calls } = analyze(ts, typescript, 'localize2'); - if (localizeCalls.length === 0 && localize2Calls.length === 0) { - return { javascript, sourcemap }; - } - const nlsKeys = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.key)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.key))); - const nlsMessages = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.value)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.value))); - const smc = new source_map_1.default.SourceMapConsumer(sourcemap); - const positionFrom = mappedPositionFrom.bind(null, sourcemap.sources[0]); - // build patches - const toPatch = (c) => { - const start = lcFrom(smc.generatedPositionFor(positionFrom(c.range.start))); - const end = lcFrom(smc.generatedPositionFor(positionFrom(c.range.end))); - return { span: { start, end }, content: c.content }; - }; - const localizePatches = (0, lazy_js_1.default)(localizeCalls) - .map(lc => (options.preserveEnglish ? [ - { range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize('key', "message") => localize(, "message") - ] : [ - { range: lc.keySpan, content: `${allNLSMessagesIndex++}` }, // localize('key', "message") => localize(, null) - { range: lc.valueSpan, content: 'null' } - ])) - .flatten() - .map(toPatch); - const localize2Patches = (0, lazy_js_1.default)(localize2Calls) - .map(lc => ({ range: lc.keySpan, content: `${allNLSMessagesIndex++}` } // localize2('key', "message") => localize(, "message") - )) - .map(toPatch); - // Sort patches by their start position - const patches = localizePatches.concat(localize2Patches).toArray().sort((a, b) => { - if (a.span.start.line < b.span.start.line) { - return -1; - } - else if (a.span.start.line > b.span.start.line) { - return 1; - } - else if (a.span.start.character < b.span.start.character) { - return -1; - } - else if (a.span.start.character > b.span.start.character) { - return 1; - } - else { - return 0; - } - }); - javascript = patchJavascript(patches, javascript); - sourcemap = patchSourcemap(patches, sourcemap, smc); - return { javascript, sourcemap, nlsKeys, nlsMessages }; - } - function patchFile(javascriptFile, typescript, options) { - const ts = require('typescript'); - // hack? - const moduleId = javascriptFile.relative - .replace(/\.js$/, '') - .replace(/\\/g, '/'); - const { javascript, sourcemap, nlsKeys, nlsMessages } = patch(ts, typescript, javascriptFile.contents.toString(), javascriptFile.sourceMap, options); - const result = fileFrom(javascriptFile, javascript); - result.sourceMap = sourcemap; - if (nlsKeys) { - _nls.moduleToNLSKeys[moduleId] = nlsKeys; - _nls.allNLSModulesAndKeys.push([moduleId, nlsKeys.map(nlsKey => typeof nlsKey === 'string' ? nlsKey : nlsKey.key)]); - } - if (nlsMessages) { - _nls.moduleToNLSMessages[moduleId] = nlsMessages; - _nls.allNLSMessages.push(...nlsMessages); - } - return result; - } - _nls.patchFile = patchFile; -})(_nls || (_nls = {})); -//# sourceMappingURL=nls.js.map \ No newline at end of file diff --git a/build/lib/nls.ts b/build/lib/nls.ts index ef2afc5d7c8af..39cc07d9d01ee 100644 --- a/build/lib/nls.ts +++ b/build/lib/nls.ts @@ -3,24 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type * as ts from 'typescript'; +import * as ts from 'typescript'; import lazy from 'lazy.js'; -import { duplex, through } from 'event-stream'; +import eventStream from 'event-stream'; import File from 'vinyl'; import sm from 'source-map'; import path from 'path'; import sort from 'gulp-sort'; -declare class FileSourceMap extends File { - public sourceMap: sm.RawSourceMap; -} +type FileWithSourcemap = File & { sourceMap: sm.RawSourceMap }; -enum CollectStepResult { - Yes, - YesAndRecurse, - No, - NoAndRecurse -} +const CollectStepResult = Object.freeze({ + Yes: 'Yes', + YesAndRecurse: 'YesAndRecurse', + No: 'No', + NoAndRecurse: 'NoAndRecurse' +}); + +type CollectStepResult = typeof CollectStepResult[keyof typeof CollectStepResult]; function collect(ts: typeof import('typescript'), node: ts.Node, fn: (node: ts.Node) => CollectStepResult): ts.Node[] { const result: ts.Node[] = []; @@ -42,11 +42,11 @@ function collect(ts: typeof import('typescript'), node: ts.Node, fn: (node: ts.N } function clone(object: T): T { - const result = {} as any as T; + const result: Record = {}; for (const id in object) { result[id] = object[id]; } - return result; + return result as T; } /** @@ -54,10 +54,10 @@ function clone(object: T): T { */ export function nls(options: { preserveEnglish: boolean }): NodeJS.ReadWriteStream { let base: string; - const input = through(); + const input = eventStream.through(); const output = input .pipe(sort()) // IMPORTANT: to ensure stable NLS metadata generation, we must sort the files because NLS messages are globally extracted and indexed across all files - .pipe(through(function (f: FileSourceMap) { + .pipe(eventStream.through(function (f: FileWithSourcemap) { if (!f.sourceMap) { return this.emit('error', new Error(`File ${f.relative} does not have sourcemaps.`)); } @@ -114,19 +114,19 @@ globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(_nls.allNLSMessages)};`), this.emit('end'); })); - return duplex(input, output); + return eventStream.duplex(input, output); } function isImportNode(ts: typeof import('typescript'), node: ts.Node): boolean { return node.kind === ts.SyntaxKind.ImportDeclaration || node.kind === ts.SyntaxKind.ImportEqualsDeclaration; } -module _nls { +const _nls = (() => { - export const moduleToNLSKeys: { [name: string /* module ID */]: ILocalizeKey[] /* keys */ } = {}; - export const moduleToNLSMessages: { [name: string /* module ID */]: string[] /* messages */ } = {}; - export const allNLSMessages: string[] = []; - export const allNLSModulesAndKeys: Array<[string /* module ID */, string[] /* keys */]> = []; + const moduleToNLSKeys: { [name: string /* module ID */]: ILocalizeKey[] /* keys */ } = {}; + const moduleToNLSMessages: { [name: string /* module ID */]: string[] /* messages */ } = {}; + const allNLSMessages: string[] = []; + const allNLSModulesAndKeys: Array<[string /* module ID */, string[] /* keys */]> = []; let allNLSMessagesIndex = 0; type ILocalizeKey = string | { key: string }; // key might contain metadata for translators and then is not just a string @@ -180,8 +180,12 @@ module _nls { private file: ts.IScriptSnapshot; private lib: ts.IScriptSnapshot; + private options: ts.CompilerOptions; + private filename: string; - constructor(ts: typeof import('typescript'), private options: ts.CompilerOptions, private filename: string, contents: string) { + constructor(ts: typeof import('typescript'), options: ts.CompilerOptions, filename: string, contents: string) { + this.options = options; + this.filename = filename; this.file = ts.ScriptSnapshot.fromString(contents); this.lib = ts.ScriptSnapshot.fromString(''); } @@ -229,14 +233,14 @@ module _nls { // import nls = require('vs/nls'); const importEqualsDeclarations = imports .filter(n => n.kind === ts.SyntaxKind.ImportEqualsDeclaration) - .map(n => n) + .map(n => n as ts.ImportEqualsDeclaration) .filter(d => d.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) - .filter(d => (d.moduleReference).expression.getText().endsWith(`/nls.js'`)); + .filter(d => (d.moduleReference as ts.ExternalModuleReference).expression.getText().endsWith(`/nls.js'`)); // import ... from 'vs/nls'; const importDeclarations = imports .filter(n => n.kind === ts.SyntaxKind.ImportDeclaration) - .map(n => n) + .map(n => n as ts.ImportDeclaration) .filter(d => d.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral) .filter(d => d.moduleSpecifier.getText().endsWith(`/nls.js'`)) .filter(d => !!d.importClause && !!d.importClause.namedBindings); @@ -244,11 +248,11 @@ module _nls { // `nls.localize(...)` calls const nlsLocalizeCallExpressions = importDeclarations .filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport)) - .map(d => (d.importClause!.namedBindings).name) + .map(d => (d.importClause!.namedBindings as ts.NamespaceImport).name) .concat(importEqualsDeclarations.map(d => d.name)) // find read-only references to `nls` - .map(n => service.getReferencesAtPosition(filename, n.pos + 1)) + .map(n => service.getReferencesAtPosition(filename, n.pos + 1) ?? []) .flatten() .filter(r => !r.isWriteAccess) @@ -256,28 +260,28 @@ module _nls { .map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n))) .map(a => lazy(a).last()) .filter(n => !!n) - .map(n => n) + .map(n => n as ts.CallExpression) // only `localize` calls - .filter(n => n.expression.kind === ts.SyntaxKind.PropertyAccessExpression && (n.expression).name.getText() === functionName); + .filter(n => n.expression.kind === ts.SyntaxKind.PropertyAccessExpression && (n.expression as ts.PropertyAccessExpression).name.getText() === functionName); // `localize` named imports const allLocalizeImportDeclarations = importDeclarations .filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamedImports)) - .map(d => ([] as any[]).concat((d.importClause!.namedBindings!).elements)) + .map(d => (d.importClause!.namedBindings! as ts.NamedImports).elements) .flatten(); // `localize` read-only references const localizeReferences = allLocalizeImportDeclarations .filter(d => d.name.getText() === functionName) - .map(n => service.getReferencesAtPosition(filename, n.pos + 1)) + .map(n => service.getReferencesAtPosition(filename, n.pos + 1) ?? []) .flatten() .filter(r => !r.isWriteAccess); // custom named `localize` read-only references const namedLocalizeReferences = allLocalizeImportDeclarations - .filter(d => d.propertyName && d.propertyName.getText() === functionName) - .map(n => service.getReferencesAtPosition(filename, n.name.pos + 1)) + .filter(d => !!d.propertyName && d.propertyName.getText() === functionName) + .map(n => service.getReferencesAtPosition(filename, n.name.pos + 1) ?? []) .flatten() .filter(r => !r.isWriteAccess); @@ -287,7 +291,7 @@ module _nls { .map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n))) .map(a => lazy(a).last()) .filter(n => !!n) - .map(n => n); + .map(n => n as ts.CallExpression); // collect everything const localizeCalls = nlsLocalizeCallExpressions @@ -494,8 +498,7 @@ module _nls { return { javascript, sourcemap, nlsKeys, nlsMessages }; } - export function patchFile(javascriptFile: File, typescript: string, options: { preserveEnglish: boolean }): File { - const ts = require('typescript') as typeof import('typescript'); + function patchFile(javascriptFile: File, typescript: string, options: { preserveEnglish: boolean }): File { // hack? const moduleId = javascriptFile.relative .replace(/\.js$/, '') @@ -504,13 +507,13 @@ module _nls { const { javascript, sourcemap, nlsKeys, nlsMessages } = patch( ts, typescript, - javascriptFile.contents.toString(), - (javascriptFile).sourceMap, + javascriptFile.contents!.toString(), + javascriptFile.sourceMap, options ); const result = fileFrom(javascriptFile, javascript); - (result).sourceMap = sourcemap; + result.sourceMap = sourcemap; if (nlsKeys) { moduleToNLSKeys[moduleId] = nlsKeys; @@ -524,4 +527,12 @@ module _nls { return result; } -} + + return { + moduleToNLSKeys, + moduleToNLSMessages, + allNLSMessages, + allNLSModulesAndKeys, + patchFile + }; +})(); diff --git a/build/lib/node.js b/build/lib/node.js deleted file mode 100644 index 01a381183ff54..0000000000000 --- a/build/lib/node.js +++ /dev/null @@ -1,21 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const path_1 = __importDefault(require("path")); -const fs_1 = __importDefault(require("fs")); -const root = path_1.default.dirname(path_1.default.dirname(__dirname)); -const npmrcPath = path_1.default.join(root, 'remote', '.npmrc'); -const npmrc = fs_1.default.readFileSync(npmrcPath, 'utf8'); -const version = /^target="(.*)"$/m.exec(npmrc)[1]; -const platform = process.platform; -const arch = process.arch; -const node = platform === 'win32' ? 'node.exe' : 'node'; -const nodePath = path_1.default.join(root, '.build', 'node', `v${version}`, `${platform}-${arch}`, node); -console.log(nodePath); -//# sourceMappingURL=node.js.map \ No newline at end of file diff --git a/build/lib/node.ts b/build/lib/node.ts index a2fdc361aa15b..1825546deb94b 100644 --- a/build/lib/node.ts +++ b/build/lib/node.ts @@ -6,10 +6,14 @@ import path from 'path'; import fs from 'fs'; -const root = path.dirname(path.dirname(__dirname)); +const root = path.dirname(path.dirname(import.meta.dirname)); const npmrcPath = path.join(root, 'remote', '.npmrc'); const npmrc = fs.readFileSync(npmrcPath, 'utf8'); -const version = /^target="(.*)"$/m.exec(npmrc)![1]; +const version = /^target="(.*)"$/m.exec(npmrc)?.[1]; + +if (!version) { + throw new Error('Failed to extract Node version from .npmrc'); +} const platform = process.platform; const arch = process.arch; diff --git a/build/lib/optimize.js b/build/lib/optimize.js deleted file mode 100644 index 2a87c239c94ce..0000000000000 --- a/build/lib/optimize.js +++ /dev/null @@ -1,224 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.bundleTask = bundleTask; -exports.minifyTask = minifyTask; -const event_stream_1 = __importDefault(require("event-stream")); -const gulp_1 = __importDefault(require("gulp")); -const gulp_filter_1 = __importDefault(require("gulp-filter")); -const path_1 = __importDefault(require("path")); -const fs_1 = __importDefault(require("fs")); -const pump_1 = __importDefault(require("pump")); -const vinyl_1 = __importDefault(require("vinyl")); -const bundle = __importStar(require("./bundle")); -const esbuild_1 = __importDefault(require("esbuild")); -const gulp_sourcemaps_1 = __importDefault(require("gulp-sourcemaps")); -const fancy_log_1 = __importDefault(require("fancy-log")); -const ansi_colors_1 = __importDefault(require("ansi-colors")); -const REPO_ROOT_PATH = path_1.default.join(__dirname, '../..'); -const DEFAULT_FILE_HEADER = [ - '/*!--------------------------------------------------------', - ' * Copyright (C) Microsoft Corporation. All rights reserved.', - ' *--------------------------------------------------------*/' -].join('\n'); -function bundleESMTask(opts) { - const resourcesStream = event_stream_1.default.through(); // this stream will contain the resources - const bundlesStream = event_stream_1.default.through(); // this stream will contain the bundled files - const entryPoints = opts.entryPoints.map(entryPoint => { - if (typeof entryPoint === 'string') { - return { name: path_1.default.parse(entryPoint).name }; - } - return entryPoint; - }); - const bundleAsync = async () => { - const files = []; - const tasks = []; - for (const entryPoint of entryPoints) { - (0, fancy_log_1.default)(`Bundled entry point: ${ansi_colors_1.default.yellow(entryPoint.name)}...`); - // support for 'dest' via esbuild#in/out - const dest = entryPoint.dest?.replace(/\.[^/.]+$/, '') ?? entryPoint.name; - // banner contents - const banner = { - js: DEFAULT_FILE_HEADER, - css: DEFAULT_FILE_HEADER - }; - // TS Boilerplate - if (!opts.skipTSBoilerplateRemoval?.(entryPoint.name)) { - const tslibPath = path_1.default.join(require.resolve('tslib'), '../tslib.es6.js'); - banner.js += await fs_1.default.promises.readFile(tslibPath, 'utf-8'); - } - const contentsMapper = { - name: 'contents-mapper', - setup(build) { - build.onLoad({ filter: /\.js$/ }, async ({ path }) => { - const contents = await fs_1.default.promises.readFile(path, 'utf-8'); - // TS Boilerplate - let newContents; - if (!opts.skipTSBoilerplateRemoval?.(entryPoint.name)) { - newContents = bundle.removeAllTSBoilerplate(contents); - } - else { - newContents = contents; - } - // File Content Mapper - const mapper = opts.fileContentMapper?.(path.replace(/\\/g, '/')); - if (mapper) { - newContents = await mapper(newContents); - } - return { contents: newContents }; - }); - } - }; - const externalOverride = { - name: 'external-override', - setup(build) { - // We inline selected modules that are we depend on on startup without - // a conditional `await import(...)` by hooking into the resolution. - build.onResolve({ filter: /^minimist$/ }, () => { - return { path: path_1.default.join(REPO_ROOT_PATH, 'node_modules', 'minimist', 'index.js'), external: false }; - }); - }, - }; - const task = esbuild_1.default.build({ - bundle: true, - packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages - platform: 'neutral', // makes esm - format: 'esm', - sourcemap: 'external', - plugins: [contentsMapper, externalOverride], - target: ['es2022'], - loader: { - '.ttf': 'file', - '.svg': 'file', - '.png': 'file', - '.sh': 'file', - }, - assetNames: 'media/[name]', // moves media assets into a sub-folder "media" - banner: entryPoint.name === 'vs/workbench/workbench.web.main' ? undefined : banner, // TODO@esm remove line when we stop supporting web-amd-esm-bridge - entryPoints: [ - { - in: path_1.default.join(REPO_ROOT_PATH, opts.src, `${entryPoint.name}.js`), - out: dest, - } - ], - outdir: path_1.default.join(REPO_ROOT_PATH, opts.src), - write: false, // enables res.outputFiles - metafile: true, // enables res.metafile - // minify: NOT enabled because we have a separate minify task that takes care of the TSLib banner as well - }).then(res => { - for (const file of res.outputFiles) { - let sourceMapFile = undefined; - if (file.path.endsWith('.js')) { - sourceMapFile = res.outputFiles.find(f => f.path === `${file.path}.map`); - } - const fileProps = { - contents: Buffer.from(file.contents), - sourceMap: sourceMapFile ? JSON.parse(sourceMapFile.text) : undefined, // support gulp-sourcemaps - path: file.path, - base: path_1.default.join(REPO_ROOT_PATH, opts.src) - }; - files.push(new vinyl_1.default(fileProps)); - } - }); - tasks.push(task); - } - await Promise.all(tasks); - return { files }; - }; - bundleAsync().then((output) => { - // bundle output (JS, CSS, SVG...) - event_stream_1.default.readArray(output.files).pipe(bundlesStream); - // forward all resources - gulp_1.default.src(opts.resources ?? [], { base: `${opts.src}`, allowEmpty: true }).pipe(resourcesStream); - }); - const result = event_stream_1.default.merge(bundlesStream, resourcesStream); - return result - .pipe(gulp_sourcemaps_1.default.write('./', { - sourceRoot: undefined, - addComment: true, - includeContent: true - })); -} -function bundleTask(opts) { - return function () { - return bundleESMTask(opts.esm).pipe(gulp_1.default.dest(opts.out)); - }; -} -function minifyTask(src, sourceMapBaseUrl) { - const sourceMappingURL = sourceMapBaseUrl ? ((f) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined; - return cb => { - const svgmin = require('gulp-svgmin'); - const esbuildFilter = (0, gulp_filter_1.default)('**/*.{js,css}', { restore: true }); - const svgFilter = (0, gulp_filter_1.default)('**/*.svg', { restore: true }); - (0, pump_1.default)(gulp_1.default.src([src + '/**', '!' + src + '/**/*.map']), esbuildFilter, gulp_sourcemaps_1.default.init({ loadMaps: true }), event_stream_1.default.map((f, cb) => { - esbuild_1.default.build({ - entryPoints: [f.path], - minify: true, - sourcemap: 'external', - outdir: '.', - packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages - platform: 'neutral', // makes esm - target: ['es2022'], - write: false, - }).then(res => { - const jsOrCSSFile = res.outputFiles.find(f => /\.(js|css)$/.test(f.path)); - const sourceMapFile = res.outputFiles.find(f => /\.(js|css)\.map$/.test(f.path)); - const contents = Buffer.from(jsOrCSSFile.contents); - const unicodeMatch = contents.toString().match(/[^\x00-\xFF]+/g); - if (unicodeMatch) { - cb(new Error(`Found non-ascii character ${unicodeMatch[0]} in the minified output of ${f.path}. Non-ASCII characters in the output can cause performance problems when loading. Please review if you have introduced a regular expression that esbuild is not automatically converting and convert it to using unicode escape sequences.`)); - } - else { - f.contents = contents; - f.sourceMap = JSON.parse(sourceMapFile.text); - cb(undefined, f); - } - }, cb); - }), esbuildFilter.restore, svgFilter, svgmin(), svgFilter.restore, gulp_sourcemaps_1.default.write('./', { - sourceMappingURL, - sourceRoot: undefined, - includeContent: true, - addComment: true - }), gulp_1.default.dest(src + '-min'), (err) => cb(err)); - }; -} -//# sourceMappingURL=optimize.js.map \ No newline at end of file diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index d89d0d627f900..f5e812e289079 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -10,13 +10,29 @@ import path from 'path'; import fs from 'fs'; import pump from 'pump'; import VinylFile from 'vinyl'; -import * as bundle from './bundle'; +import * as bundle from './bundle.ts'; import esbuild from 'esbuild'; import sourcemaps from 'gulp-sourcemaps'; import fancyLog from 'fancy-log'; import ansiColors from 'ansi-colors'; +import { getTargetStringFromTsConfig } from './tsconfigUtils.ts'; +import svgmin from 'gulp-svgmin'; +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); + +declare module 'gulp-sourcemaps' { + interface WriteOptions { + addComment?: boolean; + includeContent?: boolean; + sourceRoot?: string | WriteMapper; + sourceMappingURL?: ((f: any) => string); + sourceMappingURLPrefix?: string | WriteMapper; + clone?: boolean | CloneOptions; + } +} -const REPO_ROOT_PATH = path.join(__dirname, '../..'); +const REPO_ROOT_PATH = path.join(import.meta.dirname, '../..'); export interface IBundleESMTaskOpts { /** @@ -53,6 +69,8 @@ function bundleESMTask(opts: IBundleESMTaskOpts): NodeJS.ReadWriteStream { const resourcesStream = es.through(); // this stream will contain the resources const bundlesStream = es.through(); // this stream will contain the bundled files + const target = getBuildTarget(); + const entryPoints = opts.entryPoints.map(entryPoint => { if (typeof entryPoint === 'string') { return { name: path.parse(entryPoint).name }; @@ -126,7 +144,7 @@ function bundleESMTask(opts: IBundleESMTaskOpts): NodeJS.ReadWriteStream { format: 'esm', sourcemap: 'external', plugins: [contentsMapper, externalOverride], - target: ['es2022'], + target: [target], loader: { '.ttf': 'file', '.svg': 'file', @@ -134,7 +152,7 @@ function bundleESMTask(opts: IBundleESMTaskOpts): NodeJS.ReadWriteStream { '.sh': 'file', }, assetNames: 'media/[name]', // moves media assets into a sub-folder "media" - banner: entryPoint.name === 'vs/workbench/workbench.web.main' ? undefined : banner, // TODO@esm remove line when we stop supporting web-amd-esm-bridge + banner, entryPoints: [ { in: path.join(REPO_ROOT_PATH, opts.src, `${entryPoint.name}.js`), @@ -191,7 +209,7 @@ function bundleESMTask(opts: IBundleESMTaskOpts): NodeJS.ReadWriteStream { })); } -export interface IBundleESMTaskOpts { +export interface IBundleTaskOpts { /** * Destination folder for the bundled files. */ @@ -202,7 +220,7 @@ export interface IBundleESMTaskOpts { esm: IBundleESMTaskOpts; } -export function bundleTask(opts: IBundleESMTaskOpts): () => NodeJS.ReadWriteStream { +export function bundleTask(opts: IBundleTaskOpts): () => NodeJS.ReadWriteStream { return function () { return bundleESMTask(opts.esm).pipe(gulp.dest(opts.out)); }; @@ -210,9 +228,9 @@ export function bundleTask(opts: IBundleESMTaskOpts): () => NodeJS.ReadWriteStre export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => void { const sourceMappingURL = sourceMapBaseUrl ? ((f: any) => `${sourceMapBaseUrl}/${f.relative}.map`) : undefined; + const target = getBuildTarget(); return cb => { - const svgmin = require('gulp-svgmin') as typeof import('gulp-svgmin'); const esbuildFilter = filter('**/*.{js,css}', { restore: true }); const svgFilter = filter('**/*.svg', { restore: true }); @@ -229,7 +247,7 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => outdir: '.', packages: 'external', // "external all the things", see https://esbuild.github.io/api/#packages platform: 'neutral', // makes esm - target: ['es2022'], + target: [target], write: false, }).then(res => { const jsOrCSSFile = res.outputFiles.find(f => /\.(js|css)$/.test(f.path))!; @@ -256,8 +274,14 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => sourceRoot: undefined, includeContent: true, addComment: true - } as any), + }), gulp.dest(src + '-min'), (err: any) => cb(err)); }; } + +function getBuildTarget() { + const tsconfigPath = path.join(REPO_ROOT_PATH, 'src', 'tsconfig.base.json'); + return getTargetStringFromTsConfig(tsconfigPath); +} + diff --git a/build/lib/policies.js b/build/lib/policies.js deleted file mode 100644 index b6b520098d110..0000000000000 --- a/build/lib/policies.js +++ /dev/null @@ -1,887 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const child_process_1 = require("child_process"); -const fs_1 = require("fs"); -const path_1 = __importDefault(require("path")); -const byline_1 = __importDefault(require("byline")); -const ripgrep_1 = require("@vscode/ripgrep"); -const tree_sitter_1 = __importDefault(require("tree-sitter")); -const { typescript } = require('tree-sitter-typescript'); -const product = require('../../product.json'); -const packageJson = require('../../package.json'); -function isNlsString(value) { - return value ? typeof value !== 'string' : false; -} -function isStringArray(value) { - return !value.some(s => isNlsString(s)); -} -function isNlsStringArray(value) { - return value.every(s => isNlsString(s)); -} -var PolicyType; -(function (PolicyType) { - PolicyType["Boolean"] = "boolean"; - PolicyType["Number"] = "number"; - PolicyType["Object"] = "object"; - PolicyType["String"] = "string"; - PolicyType["StringEnum"] = "stringEnum"; -})(PolicyType || (PolicyType = {})); -function renderADMLString(prefix, moduleName, nlsString, translations) { - let value; - if (translations) { - const moduleTranslations = translations[moduleName]; - if (moduleTranslations) { - value = moduleTranslations[nlsString.nlsKey]; - } - } - if (!value) { - value = nlsString.value; - } - return `${value}`; -} -function renderProfileString(_prefix, moduleName, nlsString, translations) { - let value; - if (translations) { - const moduleTranslations = translations[moduleName]; - if (moduleTranslations) { - value = moduleTranslations[nlsString.nlsKey]; - } - } - if (!value) { - value = nlsString.value; - } - return value; -} -class BasePolicy { - type; - name; - category; - minimumVersion; - description; - moduleName; - constructor(type, name, category, minimumVersion, description, moduleName) { - this.type = type; - this.name = name; - this.category = category; - this.minimumVersion = minimumVersion; - this.description = description; - this.moduleName = moduleName; - } - renderADMLString(nlsString, translations) { - return renderADMLString(this.name, this.moduleName, nlsString, translations); - } - renderADMX(regKey) { - return [ - ``, - ` `, - ` `, - ` `, - ...this.renderADMXElements(), - ` `, - `` - ]; - } - renderADMLStrings(translations) { - return [ - `${this.name}`, - this.renderADMLString(this.description, translations) - ]; - } - renderADMLPresentation() { - return `${this.renderADMLPresentationContents()}`; - } - renderProfile() { - return [`${this.name}`, this.renderProfileValue()]; - } - renderProfileManifest(translations) { - return ` -${this.renderProfileManifestValue(translations)} -`; - } -} -class BooleanPolicy extends BasePolicy { - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); - if (type !== 'boolean') { - return undefined; - } - return new BooleanPolicy(name, category, minimumVersion, description, moduleName); - } - constructor(name, category, minimumVersion, description, moduleName) { - super(PolicyType.Boolean, name, category, minimumVersion, description, moduleName); - } - renderADMXElements() { - return [ - ``, - ` `, - `` - ]; - } - renderADMLPresentationContents() { - return `${this.name}`; - } - renderProfileValue() { - return ``; - } - renderProfileManifestValue(translations) { - return `pfm_default - -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -boolean`; - } -} -class ParseError extends Error { - constructor(message, moduleName, node) { - super(`${message}. ${moduleName}.ts:${node.startPosition.row + 1}`); - } -} -class NumberPolicy extends BasePolicy { - defaultValue; - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); - if (type !== 'number') { - return undefined; - } - const defaultValue = getNumberProperty(moduleName, settingNode, 'default'); - if (typeof defaultValue === 'undefined') { - throw new ParseError(`Missing required 'default' property.`, moduleName, settingNode); - } - return new NumberPolicy(name, category, minimumVersion, description, moduleName, defaultValue); - } - constructor(name, category, minimumVersion, description, moduleName, defaultValue) { - super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); - this.defaultValue = defaultValue; - } - renderADMXElements() { - return [ - `` - // `` - ]; - } - renderADMLPresentationContents() { - return `${this.name}`; - } - renderProfileValue() { - return `${this.defaultValue}`; - } - renderProfileManifestValue(translations) { - return `pfm_default -${this.defaultValue} -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -integer`; - } -} -class StringPolicy extends BasePolicy { - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); - if (type !== 'string') { - return undefined; - } - return new StringPolicy(name, category, minimumVersion, description, moduleName); - } - constructor(name, category, minimumVersion, description, moduleName) { - super(PolicyType.String, name, category, minimumVersion, description, moduleName); - } - renderADMXElements() { - return [``]; - } - renderADMLPresentationContents() { - return ``; - } - renderProfileValue() { - return ``; - } - renderProfileManifestValue(translations) { - return `pfm_default - -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -string`; - } -} -class ObjectPolicy extends BasePolicy { - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); - if (type !== 'object' && type !== 'array') { - return undefined; - } - return new ObjectPolicy(name, category, minimumVersion, description, moduleName); - } - constructor(name, category, minimumVersion, description, moduleName) { - super(PolicyType.Object, name, category, minimumVersion, description, moduleName); - } - renderADMXElements() { - return [``]; - } - renderADMLPresentationContents() { - return ``; - } - renderProfileValue() { - return ``; - } - renderProfileManifestValue(translations) { - return `pfm_default - -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -string -`; - } -} -class StringEnumPolicy extends BasePolicy { - enum_; - enumDescriptions; - static from(name, category, minimumVersion, description, moduleName, settingNode) { - const type = getStringProperty(moduleName, settingNode, 'type'); - if (type !== 'string') { - return undefined; - } - const enum_ = getStringArrayProperty(moduleName, settingNode, 'enum'); - if (!enum_) { - return undefined; - } - if (!isStringArray(enum_)) { - throw new ParseError(`Property 'enum' should not be localized.`, moduleName, settingNode); - } - const enumDescriptions = getStringArrayProperty(moduleName, settingNode, 'enumDescriptions'); - if (!enumDescriptions) { - throw new ParseError(`Missing required 'enumDescriptions' property.`, moduleName, settingNode); - } - else if (!isNlsStringArray(enumDescriptions)) { - throw new ParseError(`Property 'enumDescriptions' should be localized.`, moduleName, settingNode); - } - return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); - } - constructor(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions) { - super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); - this.enum_ = enum_; - this.enumDescriptions = enumDescriptions; - } - renderADMXElements() { - return [ - ``, - ...this.enum_.map((value, index) => ` ${value}`), - `` - ]; - } - renderADMLStrings(translations) { - return [ - ...super.renderADMLStrings(translations), - ...this.enumDescriptions.map(e => this.renderADMLString(e, translations)) - ]; - } - renderADMLPresentationContents() { - return ``; - } - renderProfileValue() { - return `${this.enum_[0]}`; - } - renderProfileManifestValue(translations) { - return `pfm_default -${this.enum_[0]} -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -string -pfm_range_list - - ${this.enum_.map(e => `${e}`).join('\n ')} -`; - } -} -const NumberQ = { - Q: `(number) @value`, - value(matches) { - const match = matches[0]; - if (!match) { - return undefined; - } - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - return parseInt(value); - } -}; -const StringQ = { - Q: `[ - (string (string_fragment) @value) - (call_expression - function: [ - (identifier) @localizeFn (#eq? @localizeFn localize) - (member_expression - object: (identifier) @nlsObj (#eq? @nlsObj nls) - property: (property_identifier) @localizeFn (#eq? @localizeFn localize) - ) - ] - arguments: (arguments (string (string_fragment) @nlsKey) (string (string_fragment) @value)) - ) - ]`, - value(matches) { - const match = matches[0]; - if (!match) { - return undefined; - } - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; - if (nlsKey) { - return { value, nlsKey }; - } - else { - return value; - } - } -}; -const StringArrayQ = { - Q: `(array ${StringQ.Q})`, - value(matches) { - if (matches.length === 0) { - return undefined; - } - return matches.map(match => { - return StringQ.value([match]); - }); - } -}; -function getProperty(qtype, moduleName, node, key) { - const query = new tree_sitter_1.default.Query(typescript, `( - (pair - key: [(property_identifier)(string)] @key - value: ${qtype.Q} - ) - (#any-of? @key "${key}" "'${key}'") - )`); - try { - const matches = query.matches(node).filter(m => m.captures[0].node.parent?.parent === node); - return qtype.value(matches); - } - catch (e) { - throw new ParseError(e.message, moduleName, node); - } -} -function getNumberProperty(moduleName, node, key) { - return getProperty(NumberQ, moduleName, node, key); -} -function getStringProperty(moduleName, node, key) { - return getProperty(StringQ, moduleName, node, key); -} -function getStringArrayProperty(moduleName, node, key) { - return getProperty(StringArrayQ, moduleName, node, key); -} -// TODO: add more policy types -const PolicyTypes = [ - BooleanPolicy, - NumberPolicy, - StringEnumPolicy, - StringPolicy, - ObjectPolicy -]; -function getPolicy(moduleName, configurationNode, settingNode, policyNode, categories) { - const name = getStringProperty(moduleName, policyNode, 'name'); - if (!name) { - throw new ParseError(`Missing required 'name' property`, moduleName, policyNode); - } - else if (isNlsString(name)) { - throw new ParseError(`Property 'name' should be a literal string`, moduleName, policyNode); - } - const categoryName = getStringProperty(moduleName, configurationNode, 'title'); - if (!categoryName) { - throw new ParseError(`Missing required 'title' property`, moduleName, configurationNode); - } - else if (!isNlsString(categoryName)) { - throw new ParseError(`Property 'title' should be localized`, moduleName, configurationNode); - } - const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; - let category = categories.get(categoryKey); - if (!category) { - category = { moduleName, name: categoryName }; - categories.set(categoryKey, category); - } - const minimumVersion = getStringProperty(moduleName, policyNode, 'minimumVersion'); - if (!minimumVersion) { - throw new ParseError(`Missing required 'minimumVersion' property.`, moduleName, policyNode); - } - else if (isNlsString(minimumVersion)) { - throw new ParseError(`Property 'minimumVersion' should be a literal string.`, moduleName, policyNode); - } - const description = getStringProperty(moduleName, policyNode, 'description') ?? getStringProperty(moduleName, settingNode, 'description'); - if (!description) { - throw new ParseError(`Missing required 'description' property.`, moduleName, settingNode); - } - if (!isNlsString(description)) { - throw new ParseError(`Property 'description' should be localized.`, moduleName, settingNode); - } - let result; - for (const policyType of PolicyTypes) { - if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { - break; - } - } - if (!result) { - throw new ParseError(`Failed to parse policy '${name}'.`, moduleName, settingNode); - } - return result; -} -function getPolicies(moduleName, node) { - const query = new tree_sitter_1.default.Query(typescript, ` - ( - (call_expression - function: (member_expression property: (property_identifier) @registerConfigurationFn) (#eq? @registerConfigurationFn registerConfiguration) - arguments: (arguments (object (pair - key: [(property_identifier)(string)] @propertiesKey (#any-of? @propertiesKey "properties" "'properties'") - value: (object (pair - key: [(property_identifier)(string)(computed_property_name)] - value: (object (pair - key: [(property_identifier)(string)] @policyKey (#any-of? @policyKey "policy" "'policy'") - value: (object) @policy - )) @setting - )) - )) @configuration) - ) - ) - `); - const categories = new Map(); - return query.matches(node).map(m => { - const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; - const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; - const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; - return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); - }); -} -async function getFiles(root) { - return new Promise((c, e) => { - const result = []; - const rg = (0, child_process_1.spawn)(ripgrep_1.rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); - const stream = (0, byline_1.default)(rg.stdout.setEncoding('utf8')); - stream.on('data', path => result.push(path)); - stream.on('error', err => e(err)); - stream.on('end', () => c(result)); - }); -} -function renderADMX(regKey, versions, categories, policies) { - versions = versions.map(v => v.replace(/\./g, '_')); - return ` - - - - - - - - ${versions.map(v => ``).join(`\n `)} - - - - - ${categories.map(c => ``).join(`\n `)} - - - ${policies.map(p => p.renderADMX(regKey)).flat().join(`\n `)} - - -`; -} -function renderADML(appName, versions, categories, policies, translations) { - return ` - - - - - - ${appName} - ${versions.map(v => `${appName} >= ${v}`).join(`\n `)} - ${categories.map(c => renderADMLString('Category', c.moduleName, c.name, translations)).join(`\n `)} - ${policies.map(p => p.renderADMLStrings(translations)).flat().join(`\n `)} - - - ${policies.map(p => p.renderADMLPresentation()).join(`\n `)} - - - -`; -} -function renderProfileManifest(appName, bundleIdentifier, _versions, _categories, policies, translations) { - const requiredPayloadFields = ` - - pfm_default - Configure ${appName} - pfm_name - PayloadDescription - pfm_title - Payload Description - pfm_type - string - - - pfm_default - ${appName} - pfm_name - PayloadDisplayName - pfm_require - always - pfm_title - Payload Display Name - pfm_type - string - - - pfm_default - ${bundleIdentifier} - pfm_name - PayloadIdentifier - pfm_require - always - pfm_title - Payload Identifier - pfm_type - string - - - pfm_default - ${bundleIdentifier} - pfm_name - PayloadType - pfm_require - always - pfm_title - Payload Type - pfm_type - string - - - pfm_default - - pfm_name - PayloadUUID - pfm_require - always - pfm_title - Payload UUID - pfm_type - string - - - pfm_default - 1 - pfm_name - PayloadVersion - pfm_range_list - - 1 - - pfm_require - always - pfm_title - Payload Version - pfm_type - integer - - - pfm_default - Microsoft - pfm_name - PayloadOrganization - pfm_title - Payload Organization - pfm_type - string - `; - const profileManifestSubkeys = policies.map(policy => { - return policy.renderProfileManifest(translations); - }).join(''); - return ` - - - - pfm_app_url - https://code.visualstudio.com/ - pfm_description - ${appName} Managed Settings - pfm_documentation_url - https://code.visualstudio.com/docs/setup/enterprise - pfm_domain - ${bundleIdentifier} - pfm_format_version - 1 - pfm_interaction - combined - pfm_last_modified - ${new Date().toISOString().replace(/\.\d+Z$/, 'Z')} - pfm_platforms - - macOS - - pfm_subkeys - - ${requiredPayloadFields} - ${profileManifestSubkeys} - - pfm_title - ${appName} - pfm_unique - - pfm_version - 1 - -`; -} -function renderMacOSPolicy(policies, translations) { - const appName = product.nameLong; - const bundleIdentifier = product.darwinBundleIdentifier; - const payloadUUID = product.darwinProfilePayloadUUID; - const UUID = product.darwinProfileUUID; - const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); - const categories = [...new Set(policies.map(p => p.category))]; - const policyEntries = policies.map(policy => policy.renderProfile()) - .flat() - .map(entry => `\t\t\t\t${entry}`) - .join('\n'); - return { - profile: ` - - - - PayloadContent - - - PayloadDisplayName - ${appName} - PayloadIdentifier - ${bundleIdentifier}.${UUID} - PayloadType - ${bundleIdentifier} - PayloadUUID - ${UUID} - PayloadVersion - 1 -${policyEntries} - - - PayloadDescription - This profile manages ${appName}. For more information see https://code.visualstudio.com/docs/setup/enterprise - PayloadDisplayName - ${appName} - PayloadIdentifier - ${bundleIdentifier} - PayloadOrganization - Microsoft - PayloadType - Configuration - PayloadUUID - ${payloadUUID} - PayloadVersion - 1 - TargetDeviceType - 5 - -`, - manifests: [{ languageId: 'en-us', contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies) }, - ...translations.map(({ languageId, languageTranslations }) => ({ languageId, contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies, languageTranslations) })) - ] - }; -} -function renderGP(policies, translations) { - const appName = product.nameLong; - const regKey = product.win32RegValueName; - const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); - const categories = [...Object.values(policies.reduce((acc, p) => ({ ...acc, [p.category.name.nlsKey]: p.category }), {}))]; - return { - admx: renderADMX(regKey, versions, categories, policies), - adml: [ - { languageId: 'en-us', contents: renderADML(appName, versions, categories, policies) }, - ...translations.map(({ languageId, languageTranslations }) => ({ languageId, contents: renderADML(appName, versions, categories, policies, languageTranslations) })) - ] - }; -} -const Languages = { - 'fr': 'fr-fr', - 'it': 'it-it', - 'de': 'de-de', - 'es': 'es-es', - 'ru': 'ru-ru', - 'zh-hans': 'zh-cn', - 'zh-hant': 'zh-tw', - 'ja': 'ja-jp', - 'ko': 'ko-kr', - 'cs': 'cs-cz', - 'pt-br': 'pt-br', - 'tr': 'tr-tr', - 'pl': 'pl-pl', -}; -async function getSpecificNLS(resourceUrlTemplate, languageId, version) { - const resource = { - publisher: 'ms-ceintl', - name: `vscode-language-pack-${languageId}`, - version: `${version[0]}.${version[1]}.${version[2]}`, - path: 'extension/translations/main.i18n.json' - }; - const url = resourceUrlTemplate.replace(/\{([^}]+)\}/g, (_, key) => resource[key]); - const res = await fetch(url); - if (res.status !== 200) { - throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`); - } - const { contents: result } = await res.json(); - return result; -} -function parseVersion(version) { - const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version); - return [parseInt(major), parseInt(minor), parseInt(patch)]; -} -function compareVersions(a, b) { - if (a[0] !== b[0]) { - return a[0] - b[0]; - } - if (a[1] !== b[1]) { - return a[1] - b[1]; - } - return a[2] - b[2]; -} -async function queryVersions(serviceUrl, languageId) { - const res = await fetch(`${serviceUrl}/extensionquery`, { - method: 'POST', - headers: { - 'Accept': 'application/json;api-version=3.0-preview.1', - 'Content-Type': 'application/json', - 'User-Agent': 'VS Code Build', - }, - body: JSON.stringify({ - filters: [{ criteria: [{ filterType: 7, value: `ms-ceintl.vscode-language-pack-${languageId}` }] }], - flags: 0x1 - }) - }); - if (res.status !== 200) { - throw new Error(`[${res.status}] Error querying for extension: ${languageId}`); - } - const result = await res.json(); - return result.results[0].extensions[0].versions.map(v => parseVersion(v.version)).sort(compareVersions); -} -async function getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version) { - const versions = await queryVersions(extensionGalleryServiceUrl, languageId); - const nextMinor = [version[0], version[1] + 1, 0]; - const compatibleVersions = versions.filter(v => compareVersions(v, nextMinor) < 0); - const latestCompatibleVersion = compatibleVersions.at(-1); // order is newest to oldest - if (!latestCompatibleVersion) { - throw new Error(`No compatible language pack found for ${languageId} for version ${version}`); - } - return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); -} -async function parsePolicies() { - const parser = new tree_sitter_1.default(); - parser.setLanguage(typescript); - const files = await getFiles(process.cwd()); - const base = path_1.default.join(process.cwd(), 'src'); - const policies = []; - for (const file of files) { - const moduleName = path_1.default.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); - const contents = await fs_1.promises.readFile(file, { encoding: 'utf8' }); - const tree = parser.parse(contents); - policies.push(...getPolicies(moduleName, tree.rootNode)); - } - return policies; -} -async function getTranslations() { - const extensionGalleryServiceUrl = product.extensionsGallery?.serviceUrl; - if (!extensionGalleryServiceUrl) { - console.warn(`Skipping policy localization: No 'extensionGallery.serviceUrl' found in 'product.json'.`); - return []; - } - const resourceUrlTemplate = product.extensionsGallery?.resourceUrlTemplate; - if (!resourceUrlTemplate) { - console.warn(`Skipping policy localization: No 'resourceUrlTemplate' found in 'product.json'.`); - return []; - } - const version = parseVersion(packageJson.version); - const languageIds = Object.keys(Languages); - return await Promise.all(languageIds.map(languageId => getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version) - .then(languageTranslations => ({ languageId, languageTranslations })))); -} -async function windowsMain(policies, translations) { - const root = '.build/policies/win32'; - const { admx, adml } = await renderGP(policies, translations); - await fs_1.promises.rm(root, { recursive: true, force: true }); - await fs_1.promises.mkdir(root, { recursive: true }); - await fs_1.promises.writeFile(path_1.default.join(root, `${product.win32RegValueName}.admx`), admx.replace(/\r?\n/g, '\n')); - for (const { languageId, contents } of adml) { - const languagePath = path_1.default.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId]); - await fs_1.promises.mkdir(languagePath, { recursive: true }); - await fs_1.promises.writeFile(path_1.default.join(languagePath, `${product.win32RegValueName}.adml`), contents.replace(/\r?\n/g, '\n')); - } -} -async function darwinMain(policies, translations) { - const bundleIdentifier = product.darwinBundleIdentifier; - if (!bundleIdentifier || !product.darwinProfilePayloadUUID || !product.darwinProfileUUID) { - throw new Error(`Missing required product information.`); - } - const root = '.build/policies/darwin'; - const { profile, manifests } = await renderMacOSPolicy(policies, translations); - await fs_1.promises.rm(root, { recursive: true, force: true }); - await fs_1.promises.mkdir(root, { recursive: true }); - await fs_1.promises.writeFile(path_1.default.join(root, `${bundleIdentifier}.mobileconfig`), profile.replace(/\r?\n/g, '\n')); - for (const { languageId, contents } of manifests) { - const languagePath = path_1.default.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId]); - await fs_1.promises.mkdir(languagePath, { recursive: true }); - await fs_1.promises.writeFile(path_1.default.join(languagePath, `${bundleIdentifier}.plist`), contents.replace(/\r?\n/g, '\n')); - } -} -async function main() { - const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); - const platform = process.argv[2]; - if (platform === 'darwin') { - await darwinMain(policies, translations); - } - else if (platform === 'win32') { - await windowsMain(policies, translations); - } - else { - console.error(`Usage: node build/lib/policies `); - process.exit(1); - } -} -if (require.main === module) { - main().catch(err => { - if (err instanceof ParseError) { - console.error(`Parse Error:`, err.message); - } - else { - console.error(err); - } - process.exit(1); - }); -} -//# sourceMappingURL=policies.js.map \ No newline at end of file diff --git a/build/lib/policies.ts b/build/lib/policies.ts deleted file mode 100644 index 381d2f4c1ac97..0000000000000 --- a/build/lib/policies.ts +++ /dev/null @@ -1,1137 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { spawn } from 'child_process'; -import { promises as fs } from 'fs'; -import path from 'path'; -import byline from 'byline'; -import { rgPath } from '@vscode/ripgrep'; -import Parser from 'tree-sitter'; -const { typescript } = require('tree-sitter-typescript'); -const product = require('../../product.json'); -const packageJson = require('../../package.json'); - -type NlsString = { value: string; nlsKey: string }; - -function isNlsString(value: string | NlsString | undefined): value is NlsString { - return value ? typeof value !== 'string' : false; -} - -function isStringArray(value: (string | NlsString)[]): value is string[] { - return !value.some(s => isNlsString(s)); -} - -function isNlsStringArray(value: (string | NlsString)[]): value is NlsString[] { - return value.every(s => isNlsString(s)); -} - -interface Category { - readonly moduleName: string; - readonly name: NlsString; -} - -enum PolicyType { - Boolean = 'boolean', - Number = 'number', - Object = 'object', - String = 'string', - StringEnum = 'stringEnum', -} - -interface Policy { - readonly name: string; - readonly type: PolicyType; - readonly category: Category; - readonly minimumVersion: string; - renderADMX(regKey: string): string[]; - renderADMLStrings(translations?: LanguageTranslations): string[]; - renderADMLPresentation(): string; - renderProfile(): string[]; - // https://github.com/ProfileManifests/ProfileManifests/wiki/Manifest-Format - renderProfileManifest(translations?: LanguageTranslations): string; -} - -function renderADMLString(prefix: string, moduleName: string, nlsString: NlsString, translations?: LanguageTranslations): string { - let value: string | undefined; - - if (translations) { - const moduleTranslations = translations[moduleName]; - - if (moduleTranslations) { - value = moduleTranslations[nlsString.nlsKey]; - } - } - - if (!value) { - value = nlsString.value; - } - - return `${value}`; -} - -function renderProfileString(_prefix: string, moduleName: string, nlsString: NlsString, translations?: LanguageTranslations): string { - let value: string | undefined; - - if (translations) { - const moduleTranslations = translations[moduleName]; - - if (moduleTranslations) { - value = moduleTranslations[nlsString.nlsKey]; - } - } - - if (!value) { - value = nlsString.value; - } - - return value; -} - -abstract class BasePolicy implements Policy { - constructor( - readonly type: PolicyType, - readonly name: string, - readonly category: Category, - readonly minimumVersion: string, - protected description: NlsString, - protected moduleName: string, - ) { } - - protected renderADMLString(nlsString: NlsString, translations?: LanguageTranslations): string { - return renderADMLString(this.name, this.moduleName, nlsString, translations); - } - - renderADMX(regKey: string) { - return [ - ``, - ` `, - ` `, - ` `, - ...this.renderADMXElements(), - ` `, - `` - ]; - } - - protected abstract renderADMXElements(): string[]; - - renderADMLStrings(translations?: LanguageTranslations) { - return [ - `${this.name}`, - this.renderADMLString(this.description, translations) - ]; - } - - renderADMLPresentation(): string { - return `${this.renderADMLPresentationContents()}`; - } - - protected abstract renderADMLPresentationContents(): string; - - renderProfile() { - return [`${this.name}`, this.renderProfileValue()]; - } - - renderProfileManifest(translations?: LanguageTranslations): string { - return ` -${this.renderProfileManifestValue(translations)} -`; - } - - abstract renderProfileValue(): string; - abstract renderProfileManifestValue(translations?: LanguageTranslations): string; -} - -class BooleanPolicy extends BasePolicy { - - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): BooleanPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); - - if (type !== 'boolean') { - return undefined; - } - - return new BooleanPolicy(name, category, minimumVersion, description, moduleName); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - ) { - super(PolicyType.Boolean, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [ - ``, - ` `, - `` - ]; - } - - renderADMLPresentationContents() { - return `${this.name}`; - } - - renderProfileValue(): string { - return ``; - } - - renderProfileManifestValue(translations?: LanguageTranslations): string { - return `pfm_default - -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -boolean`; - } -} - -class ParseError extends Error { - constructor(message: string, moduleName: string, node: Parser.SyntaxNode) { - super(`${message}. ${moduleName}.ts:${node.startPosition.row + 1}`); - } -} - -class NumberPolicy extends BasePolicy { - - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): NumberPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); - - if (type !== 'number') { - return undefined; - } - - const defaultValue = getNumberProperty(moduleName, settingNode, 'default'); - - if (typeof defaultValue === 'undefined') { - throw new ParseError(`Missing required 'default' property.`, moduleName, settingNode); - } - - return new NumberPolicy(name, category, minimumVersion, description, moduleName, defaultValue); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - protected readonly defaultValue: number, - ) { - super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [ - `` - // `` - ]; - } - - renderADMLPresentationContents() { - return `${this.name}`; - } - - renderProfileValue() { - return `${this.defaultValue}`; - } - - renderProfileManifestValue(translations?: LanguageTranslations) { - return `pfm_default -${this.defaultValue} -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -integer`; - } -} - -class StringPolicy extends BasePolicy { - - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): StringPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); - - if (type !== 'string') { - return undefined; - } - - return new StringPolicy(name, category, minimumVersion, description, moduleName); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - ) { - super(PolicyType.String, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [``]; - } - - renderADMLPresentationContents() { - return ``; - } - - renderProfileValue(): string { - return ``; - } - - renderProfileManifestValue(translations?: LanguageTranslations): string { - return `pfm_default - -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -string`; - } -} - -class ObjectPolicy extends BasePolicy { - - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): ObjectPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); - - if (type !== 'object' && type !== 'array') { - return undefined; - } - - return new ObjectPolicy(name, category, minimumVersion, description, moduleName); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - ) { - super(PolicyType.Object, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [``]; - } - - renderADMLPresentationContents() { - return ``; - } - - renderProfileValue(): string { - return ``; - } - - renderProfileManifestValue(translations?: LanguageTranslations): string { - return `pfm_default - -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -string -`; - } -} - -class StringEnumPolicy extends BasePolicy { - - static from( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - settingNode: Parser.SyntaxNode - ): StringEnumPolicy | undefined { - const type = getStringProperty(moduleName, settingNode, 'type'); - - if (type !== 'string') { - return undefined; - } - - const enum_ = getStringArrayProperty(moduleName, settingNode, 'enum'); - - if (!enum_) { - return undefined; - } - - if (!isStringArray(enum_)) { - throw new ParseError(`Property 'enum' should not be localized.`, moduleName, settingNode); - } - - const enumDescriptions = getStringArrayProperty(moduleName, settingNode, 'enumDescriptions'); - - if (!enumDescriptions) { - throw new ParseError(`Missing required 'enumDescriptions' property.`, moduleName, settingNode); - } else if (!isNlsStringArray(enumDescriptions)) { - throw new ParseError(`Property 'enumDescriptions' should be localized.`, moduleName, settingNode); - } - - return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); - } - - private constructor( - name: string, - category: Category, - minimumVersion: string, - description: NlsString, - moduleName: string, - protected enum_: string[], - protected enumDescriptions: NlsString[], - ) { - super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); - } - - protected renderADMXElements(): string[] { - return [ - ``, - ...this.enum_.map((value, index) => ` ${value}`), - `` - ]; - } - - renderADMLStrings(translations?: LanguageTranslations) { - return [ - ...super.renderADMLStrings(translations), - ...this.enumDescriptions.map(e => this.renderADMLString(e, translations)) - ]; - } - - renderADMLPresentationContents() { - return ``; - } - - renderProfileValue() { - return `${this.enum_[0]}`; - } - - renderProfileManifestValue(translations?: LanguageTranslations): string { - return `pfm_default -${this.enum_[0]} -pfm_description -${renderProfileString(this.name, this.moduleName, this.description, translations)} -pfm_name -${this.name} -pfm_title -${this.name} -pfm_type -string -pfm_range_list - - ${this.enum_.map(e => `${e}`).join('\n ')} -`; - } -} - -interface QType { - Q: string; - value(matches: Parser.QueryMatch[]): T | undefined; -} - -const NumberQ: QType = { - Q: `(number) @value`, - - value(matches: Parser.QueryMatch[]): number | undefined { - const match = matches[0]; - - if (!match) { - return undefined; - } - - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - - return parseInt(value); - } -}; - -const StringQ: QType = { - Q: `[ - (string (string_fragment) @value) - (call_expression - function: [ - (identifier) @localizeFn (#eq? @localizeFn localize) - (member_expression - object: (identifier) @nlsObj (#eq? @nlsObj nls) - property: (property_identifier) @localizeFn (#eq? @localizeFn localize) - ) - ] - arguments: (arguments (string (string_fragment) @nlsKey) (string (string_fragment) @value)) - ) - ]`, - - value(matches: Parser.QueryMatch[]): string | NlsString | undefined { - const match = matches[0]; - - if (!match) { - return undefined; - } - - const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; - - if (!value) { - throw new Error(`Missing required 'value' property.`); - } - - const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; - - if (nlsKey) { - return { value, nlsKey }; - } else { - return value; - } - } -}; - -const StringArrayQ: QType<(string | NlsString)[]> = { - Q: `(array ${StringQ.Q})`, - - value(matches: Parser.QueryMatch[]): (string | NlsString)[] | undefined { - if (matches.length === 0) { - return undefined; - } - - return matches.map(match => { - return StringQ.value([match]) as string | NlsString; - }); - } -}; - -function getProperty(qtype: QType, moduleName: string, node: Parser.SyntaxNode, key: string): T | undefined { - const query = new Parser.Query( - typescript, - `( - (pair - key: [(property_identifier)(string)] @key - value: ${qtype.Q} - ) - (#any-of? @key "${key}" "'${key}'") - )` - ); - - try { - const matches = query.matches(node).filter(m => m.captures[0].node.parent?.parent === node); - return qtype.value(matches); - } catch (e) { - throw new ParseError(e.message, moduleName, node); - } -} - -function getNumberProperty(moduleName: string, node: Parser.SyntaxNode, key: string): number | undefined { - return getProperty(NumberQ, moduleName, node, key); -} - -function getStringProperty(moduleName: string, node: Parser.SyntaxNode, key: string): string | NlsString | undefined { - return getProperty(StringQ, moduleName, node, key); -} - -function getStringArrayProperty(moduleName: string, node: Parser.SyntaxNode, key: string): (string | NlsString)[] | undefined { - return getProperty(StringArrayQ, moduleName, node, key); -} - -// TODO: add more policy types -const PolicyTypes = [ - BooleanPolicy, - NumberPolicy, - StringEnumPolicy, - StringPolicy, - ObjectPolicy -]; - -function getPolicy( - moduleName: string, - configurationNode: Parser.SyntaxNode, - settingNode: Parser.SyntaxNode, - policyNode: Parser.SyntaxNode, - categories: Map -): Policy { - const name = getStringProperty(moduleName, policyNode, 'name'); - - if (!name) { - throw new ParseError(`Missing required 'name' property`, moduleName, policyNode); - } else if (isNlsString(name)) { - throw new ParseError(`Property 'name' should be a literal string`, moduleName, policyNode); - } - - const categoryName = getStringProperty(moduleName, configurationNode, 'title'); - - if (!categoryName) { - throw new ParseError(`Missing required 'title' property`, moduleName, configurationNode); - } else if (!isNlsString(categoryName)) { - throw new ParseError(`Property 'title' should be localized`, moduleName, configurationNode); - } - - const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; - let category = categories.get(categoryKey); - - if (!category) { - category = { moduleName, name: categoryName }; - categories.set(categoryKey, category); - } - - const minimumVersion = getStringProperty(moduleName, policyNode, 'minimumVersion'); - - if (!minimumVersion) { - throw new ParseError(`Missing required 'minimumVersion' property.`, moduleName, policyNode); - } else if (isNlsString(minimumVersion)) { - throw new ParseError(`Property 'minimumVersion' should be a literal string.`, moduleName, policyNode); - } - - const description = getStringProperty(moduleName, policyNode, 'description') ?? getStringProperty(moduleName, settingNode, 'description'); - - if (!description) { - throw new ParseError(`Missing required 'description' property.`, moduleName, settingNode); - } if (!isNlsString(description)) { - throw new ParseError(`Property 'description' should be localized.`, moduleName, settingNode); - } - - let result: Policy | undefined; - - for (const policyType of PolicyTypes) { - if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { - break; - } - } - - if (!result) { - throw new ParseError(`Failed to parse policy '${name}'.`, moduleName, settingNode); - } - - return result; -} - -function getPolicies(moduleName: string, node: Parser.SyntaxNode): Policy[] { - const query = new Parser.Query(typescript, ` - ( - (call_expression - function: (member_expression property: (property_identifier) @registerConfigurationFn) (#eq? @registerConfigurationFn registerConfiguration) - arguments: (arguments (object (pair - key: [(property_identifier)(string)] @propertiesKey (#any-of? @propertiesKey "properties" "'properties'") - value: (object (pair - key: [(property_identifier)(string)(computed_property_name)] - value: (object (pair - key: [(property_identifier)(string)] @policyKey (#any-of? @policyKey "policy" "'policy'") - value: (object) @policy - )) @setting - )) - )) @configuration) - ) - ) - `); - - const categories = new Map(); - - return query.matches(node).map(m => { - const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; - const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; - const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; - return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); - }); -} - -async function getFiles(root: string): Promise { - return new Promise((c, e) => { - const result: string[] = []; - const rg = spawn(rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); - const stream = byline(rg.stdout.setEncoding('utf8')); - stream.on('data', path => result.push(path)); - stream.on('error', err => e(err)); - stream.on('end', () => c(result)); - }); -} - -function renderADMX(regKey: string, versions: string[], categories: Category[], policies: Policy[]) { - versions = versions.map(v => v.replace(/\./g, '_')); - - return ` - - - - - - - - ${versions.map(v => ``).join(`\n `)} - - - - - ${categories.map(c => ``).join(`\n `)} - - - ${policies.map(p => p.renderADMX(regKey)).flat().join(`\n `)} - - -`; -} - -function renderADML(appName: string, versions: string[], categories: Category[], policies: Policy[], translations?: LanguageTranslations) { - return ` - - - - - - ${appName} - ${versions.map(v => `${appName} >= ${v}`).join(`\n `)} - ${categories.map(c => renderADMLString('Category', c.moduleName, c.name, translations)).join(`\n `)} - ${policies.map(p => p.renderADMLStrings(translations)).flat().join(`\n `)} - - - ${policies.map(p => p.renderADMLPresentation()).join(`\n `)} - - - -`; -} - -function renderProfileManifest(appName: string, bundleIdentifier: string, _versions: string[], _categories: Category[], policies: Policy[], translations?: LanguageTranslations) { - - const requiredPayloadFields = ` - - pfm_default - Configure ${appName} - pfm_name - PayloadDescription - pfm_title - Payload Description - pfm_type - string - - - pfm_default - ${appName} - pfm_name - PayloadDisplayName - pfm_require - always - pfm_title - Payload Display Name - pfm_type - string - - - pfm_default - ${bundleIdentifier} - pfm_name - PayloadIdentifier - pfm_require - always - pfm_title - Payload Identifier - pfm_type - string - - - pfm_default - ${bundleIdentifier} - pfm_name - PayloadType - pfm_require - always - pfm_title - Payload Type - pfm_type - string - - - pfm_default - - pfm_name - PayloadUUID - pfm_require - always - pfm_title - Payload UUID - pfm_type - string - - - pfm_default - 1 - pfm_name - PayloadVersion - pfm_range_list - - 1 - - pfm_require - always - pfm_title - Payload Version - pfm_type - integer - - - pfm_default - Microsoft - pfm_name - PayloadOrganization - pfm_title - Payload Organization - pfm_type - string - `; - - const profileManifestSubkeys = policies.map(policy => { - return policy.renderProfileManifest(translations); - }).join(''); - - return ` - - - - pfm_app_url - https://code.visualstudio.com/ - pfm_description - ${appName} Managed Settings - pfm_documentation_url - https://code.visualstudio.com/docs/setup/enterprise - pfm_domain - ${bundleIdentifier} - pfm_format_version - 1 - pfm_interaction - combined - pfm_last_modified - ${new Date().toISOString().replace(/\.\d+Z$/, 'Z')} - pfm_platforms - - macOS - - pfm_subkeys - - ${requiredPayloadFields} - ${profileManifestSubkeys} - - pfm_title - ${appName} - pfm_unique - - pfm_version - 1 - -`; -} - -function renderMacOSPolicy(policies: Policy[], translations: Translations) { - const appName = product.nameLong; - const bundleIdentifier = product.darwinBundleIdentifier; - const payloadUUID = product.darwinProfilePayloadUUID; - const UUID = product.darwinProfileUUID; - - const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); - const categories = [...new Set(policies.map(p => p.category))]; - - const policyEntries = - policies.map(policy => policy.renderProfile()) - .flat() - .map(entry => `\t\t\t\t${entry}`) - .join('\n'); - - - return { - profile: ` - - - - PayloadContent - - - PayloadDisplayName - ${appName} - PayloadIdentifier - ${bundleIdentifier}.${UUID} - PayloadType - ${bundleIdentifier} - PayloadUUID - ${UUID} - PayloadVersion - 1 -${policyEntries} - - - PayloadDescription - This profile manages ${appName}. For more information see https://code.visualstudio.com/docs/setup/enterprise - PayloadDisplayName - ${appName} - PayloadIdentifier - ${bundleIdentifier} - PayloadOrganization - Microsoft - PayloadType - Configuration - PayloadUUID - ${payloadUUID} - PayloadVersion - 1 - TargetDeviceType - 5 - -`, - manifests: [{ languageId: 'en-us', contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies) }, - ...translations.map(({ languageId, languageTranslations }) => - ({ languageId, contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies, languageTranslations) })) - ] - }; -} - -function renderGP(policies: Policy[], translations: Translations) { - const appName = product.nameLong; - const regKey = product.win32RegValueName; - - const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); - const categories = [...Object.values(policies.reduce((acc, p) => ({ ...acc, [p.category.name.nlsKey]: p.category }), {}))] as Category[]; - - return { - admx: renderADMX(regKey, versions, categories, policies), - adml: [ - { languageId: 'en-us', contents: renderADML(appName, versions, categories, policies) }, - ...translations.map(({ languageId, languageTranslations }) => - ({ languageId, contents: renderADML(appName, versions, categories, policies, languageTranslations) })) - ] - }; -} - -const Languages = { - 'fr': 'fr-fr', - 'it': 'it-it', - 'de': 'de-de', - 'es': 'es-es', - 'ru': 'ru-ru', - 'zh-hans': 'zh-cn', - 'zh-hant': 'zh-tw', - 'ja': 'ja-jp', - 'ko': 'ko-kr', - 'cs': 'cs-cz', - 'pt-br': 'pt-br', - 'tr': 'tr-tr', - 'pl': 'pl-pl', -}; - -type LanguageTranslations = { [moduleName: string]: { [nlsKey: string]: string } }; -type Translations = { languageId: string; languageTranslations: LanguageTranslations }[]; - -type Version = [number, number, number]; - -async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version) { - const resource = { - publisher: 'ms-ceintl', - name: `vscode-language-pack-${languageId}`, - version: `${version[0]}.${version[1]}.${version[2]}`, - path: 'extension/translations/main.i18n.json' - }; - - const url = resourceUrlTemplate.replace(/\{([^}]+)\}/g, (_, key) => resource[key as keyof typeof resource]); - const res = await fetch(url); - - if (res.status !== 200) { - throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`); - } - - const { contents: result } = await res.json() as { contents: LanguageTranslations }; - return result; -} - -function parseVersion(version: string): Version { - const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version)!; - return [parseInt(major), parseInt(minor), parseInt(patch)]; -} - -function compareVersions(a: Version, b: Version): number { - if (a[0] !== b[0]) { return a[0] - b[0]; } - if (a[1] !== b[1]) { return a[1] - b[1]; } - return a[2] - b[2]; -} - -async function queryVersions(serviceUrl: string, languageId: string): Promise { - const res = await fetch(`${serviceUrl}/extensionquery`, { - method: 'POST', - headers: { - 'Accept': 'application/json;api-version=3.0-preview.1', - 'Content-Type': 'application/json', - 'User-Agent': 'VS Code Build', - }, - body: JSON.stringify({ - filters: [{ criteria: [{ filterType: 7, value: `ms-ceintl.vscode-language-pack-${languageId}` }] }], - flags: 0x1 - }) - }); - - if (res.status !== 200) { - throw new Error(`[${res.status}] Error querying for extension: ${languageId}`); - } - - const result = await res.json() as { results: [{ extensions: { versions: { version: string }[] }[] }] }; - return result.results[0].extensions[0].versions.map(v => parseVersion(v.version)).sort(compareVersions); -} - -async function getNLS(extensionGalleryServiceUrl: string, resourceUrlTemplate: string, languageId: string, version: Version) { - const versions = await queryVersions(extensionGalleryServiceUrl, languageId); - const nextMinor: Version = [version[0], version[1] + 1, 0]; - const compatibleVersions = versions.filter(v => compareVersions(v, nextMinor) < 0); - const latestCompatibleVersion = compatibleVersions.at(-1)!; // order is newest to oldest - - if (!latestCompatibleVersion) { - throw new Error(`No compatible language pack found for ${languageId} for version ${version}`); - } - - return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); -} - -async function parsePolicies(): Promise { - const parser = new Parser(); - parser.setLanguage(typescript); - - const files = await getFiles(process.cwd()); - const base = path.join(process.cwd(), 'src'); - const policies = []; - - for (const file of files) { - const moduleName = path.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); - const contents = await fs.readFile(file, { encoding: 'utf8' }); - const tree = parser.parse(contents); - policies.push(...getPolicies(moduleName, tree.rootNode)); - } - - return policies; -} - -async function getTranslations(): Promise { - const extensionGalleryServiceUrl = product.extensionsGallery?.serviceUrl; - - if (!extensionGalleryServiceUrl) { - console.warn(`Skipping policy localization: No 'extensionGallery.serviceUrl' found in 'product.json'.`); - return []; - } - - const resourceUrlTemplate = product.extensionsGallery?.resourceUrlTemplate; - - if (!resourceUrlTemplate) { - console.warn(`Skipping policy localization: No 'resourceUrlTemplate' found in 'product.json'.`); - return []; - } - - const version = parseVersion(packageJson.version); - const languageIds = Object.keys(Languages); - - return await Promise.all(languageIds.map( - languageId => getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version) - .then(languageTranslations => ({ languageId, languageTranslations })) - )); -} - -async function windowsMain(policies: Policy[], translations: Translations) { - const root = '.build/policies/win32'; - const { admx, adml } = await renderGP(policies, translations); - - await fs.rm(root, { recursive: true, force: true }); - await fs.mkdir(root, { recursive: true }); - - await fs.writeFile(path.join(root, `${product.win32RegValueName}.admx`), admx.replace(/\r?\n/g, '\n')); - - for (const { languageId, contents } of adml) { - const languagePath = path.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId as keyof typeof Languages]); - await fs.mkdir(languagePath, { recursive: true }); - await fs.writeFile(path.join(languagePath, `${product.win32RegValueName}.adml`), contents.replace(/\r?\n/g, '\n')); - } -} - -async function darwinMain(policies: Policy[], translations: Translations) { - const bundleIdentifier = product.darwinBundleIdentifier; - if (!bundleIdentifier || !product.darwinProfilePayloadUUID || !product.darwinProfileUUID) { - throw new Error(`Missing required product information.`); - } - const root = '.build/policies/darwin'; - const { profile, manifests } = await renderMacOSPolicy(policies, translations); - - await fs.rm(root, { recursive: true, force: true }); - await fs.mkdir(root, { recursive: true }); - await fs.writeFile(path.join(root, `${bundleIdentifier}.mobileconfig`), profile.replace(/\r?\n/g, '\n')); - - for (const { languageId, contents } of manifests) { - const languagePath = path.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId as keyof typeof Languages]); - await fs.mkdir(languagePath, { recursive: true }); - await fs.writeFile(path.join(languagePath, `${bundleIdentifier}.plist`), contents.replace(/\r?\n/g, '\n')); - } -} - -async function main() { - const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); - const platform = process.argv[2]; - - if (platform === 'darwin') { - await darwinMain(policies, translations); - } else if (platform === 'win32') { - await windowsMain(policies, translations); - } else { - console.error(`Usage: node build/lib/policies `); - process.exit(1); - } -} - -if (require.main === module) { - main().catch(err => { - if (err instanceof ParseError) { - console.error(`Parse Error:`, err.message); - } else { - console.error(err); - } - process.exit(1); - }); -} diff --git a/build/lib/policies/basePolicy.ts b/build/lib/policies/basePolicy.ts new file mode 100644 index 0000000000000..7f650ba7b2edd --- /dev/null +++ b/build/lib/policies/basePolicy.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { renderADMLString } from './render.ts'; +import type { Category, LanguageTranslations, NlsString, Policy, PolicyType } from './types.ts'; + +export abstract class BasePolicy implements Policy { + readonly type: PolicyType; + readonly name: string; + readonly category: Category; + readonly minimumVersion: string; + protected description: NlsString; + protected moduleName: string; + + constructor( + type: PolicyType, + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + ) { + this.type = type; + this.name = name; + this.category = category; + this.minimumVersion = minimumVersion; + this.description = description; + this.moduleName = moduleName; + } + + protected renderADMLString(nlsString: NlsString, translations?: LanguageTranslations): string { + return renderADMLString(this.name, this.moduleName, nlsString, translations); + } + + renderADMX(regKey: string) { + return [ + ``, + ` `, + ` `, + ` `, + ...this.renderADMXElements(), + ` `, + `` + ]; + } + + protected abstract renderADMXElements(): string[]; + + renderADMLStrings(translations?: LanguageTranslations) { + return [ + `${this.name}`, + this.renderADMLString(this.description, translations) + ]; + } + + renderADMLPresentation(): string { + return `${this.renderADMLPresentationContents()}`; + } + + protected abstract renderADMLPresentationContents(): string; + + renderProfile() { + return [`${this.name}`, this.renderProfileValue()]; + } + + renderProfileManifest(translations?: LanguageTranslations): string { + return ` +${this.renderProfileManifestValue(translations)} +`; + } + + abstract renderJsonValue(): string | number | boolean | object | null; + abstract renderProfileValue(): string; + abstract renderProfileManifestValue(translations?: LanguageTranslations): string; +} diff --git a/build/lib/policies/booleanPolicy.ts b/build/lib/policies/booleanPolicy.ts new file mode 100644 index 0000000000000..59e2402eb3c69 --- /dev/null +++ b/build/lib/policies/booleanPolicy.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BasePolicy } from './basePolicy.ts'; +import type { CategoryDto, PolicyDto } from './policyDto.ts'; +import { renderProfileString } from './render.ts'; +import { type Category, type NlsString, PolicyType, type LanguageTranslations } from './types.ts'; + +export class BooleanPolicy extends BasePolicy { + + static from(category: CategoryDto, policy: PolicyDto): BooleanPolicy | undefined { + const { name, minimumVersion, localization, type } = policy; + + if (type !== 'boolean') { + return undefined; + } + + return new BooleanPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + } + + private constructor( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + ) { + super(PolicyType.Boolean, name, category, minimumVersion, description, moduleName); + } + + protected renderADMXElements(): string[] { + return [ + ``, + ` `, + `` + ]; + } + + renderADMLPresentationContents() { + return `${this.name}`; + } + + renderJsonValue() { + return false; + } + + renderProfileValue(): string { + return ``; + } + + renderProfileManifestValue(translations?: LanguageTranslations): string { + return `pfm_default + +pfm_description +${renderProfileString(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +boolean`; + } +} diff --git a/build/lib/policies/copyPolicyDto.ts b/build/lib/policies/copyPolicyDto.ts new file mode 100644 index 0000000000000..6bf8cd888023f --- /dev/null +++ b/build/lib/policies/copyPolicyDto.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as path from 'path'; + +const sourceFile = path.join(import.meta.dirname, '../../../src/vs/workbench/contrib/policyExport/common/policyDto.ts'); +const destFile = path.join(import.meta.dirname, 'policyDto.ts'); + +try { + // Check if source file exists + if (!fs.existsSync(sourceFile)) { + console.error(`Error: Source file not found: ${sourceFile}`); + console.error('Please ensure policyDto.ts exists in src/vs/workbench/contrib/policyExport/common/'); + process.exit(1); + } + + // Copy the file + fs.copyFileSync(sourceFile, destFile); +} catch (error) { + console.error(`Error copying policyDto.ts: ${(error as Error).message}`); + process.exit(1); +} diff --git a/build/lib/policies/numberPolicy.ts b/build/lib/policies/numberPolicy.ts new file mode 100644 index 0000000000000..3091e004677b1 --- /dev/null +++ b/build/lib/policies/numberPolicy.ts @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BasePolicy } from './basePolicy.ts'; +import type { CategoryDto, PolicyDto } from './policyDto.ts'; +import { renderProfileString } from './render.ts'; +import { type Category, type NlsString, PolicyType, type LanguageTranslations } from './types.ts'; + +export class NumberPolicy extends BasePolicy { + + static from(category: CategoryDto, policy: PolicyDto): NumberPolicy | undefined { + const { type, default: defaultValue, name, minimumVersion, localization } = policy; + + if (type !== 'number') { + return undefined; + } + + if (typeof defaultValue !== 'number') { + throw new Error(`Missing required 'default' property.`); + } + + return new NumberPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, '', defaultValue); + } + + protected readonly defaultValue: number; + + private constructor( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + defaultValue: number, + ) { + super(PolicyType.Number, name, category, minimumVersion, description, moduleName); + this.defaultValue = defaultValue; + } + + protected renderADMXElements(): string[] { + return [ + `` + // `` + ]; + } + + renderADMLPresentationContents() { + return `${this.name}`; + } + + renderJsonValue() { + return this.defaultValue; + } + + renderProfileValue() { + return `${this.defaultValue}`; + } + + renderProfileManifestValue(translations?: LanguageTranslations) { + return `pfm_default +${this.defaultValue} +pfm_description +${renderProfileString(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +integer`; + } +} diff --git a/build/lib/policies/objectPolicy.ts b/build/lib/policies/objectPolicy.ts new file mode 100644 index 0000000000000..b565b06e8bb3c --- /dev/null +++ b/build/lib/policies/objectPolicy.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BasePolicy } from './basePolicy.ts'; +import type { CategoryDto, PolicyDto } from './policyDto.ts'; +import { renderProfileString } from './render.ts'; +import { type Category, type NlsString, PolicyType, type LanguageTranslations } from './types.ts'; + +export class ObjectPolicy extends BasePolicy { + + static from(category: CategoryDto, policy: PolicyDto): ObjectPolicy | undefined { + const { type, name, minimumVersion, localization } = policy; + + if (type !== 'object' && type !== 'array') { + return undefined; + } + + return new ObjectPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + } + + private constructor( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + ) { + super(PolicyType.Object, name, category, minimumVersion, description, moduleName); + } + + protected renderADMXElements(): string[] { + return [``]; + } + + renderADMLPresentationContents() { + return ``; + } + + renderJsonValue() { + return ''; + } + + renderProfileValue(): string { + return ``; + } + + renderProfileManifestValue(translations?: LanguageTranslations): string { + return `pfm_default + +pfm_description +${renderProfileString(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +string +`; + } +} diff --git a/build/lib/policies/policyData.jsonc b/build/lib/policies/policyData.jsonc new file mode 100644 index 0000000000000..b8f4106fc97e5 --- /dev/null +++ b/build/lib/policies/policyData.jsonc @@ -0,0 +1,277 @@ +/** THIS FILE IS AUTOMATICALLY GENERATED USING `code --export-policy-data`. DO NOT MODIFY IT MANUALLY. **/ +{ + "categories": [ + { + "key": "Extensions", + "name": { + "key": "extensionsConfigurationTitle", + "value": "Extensions" + } + }, + { + "key": "IntegratedTerminal", + "name": { + "key": "terminalIntegratedConfigurationTitle", + "value": "Integrated Terminal" + } + }, + { + "key": "InteractiveSession", + "name": { + "key": "interactiveSessionConfigurationTitle", + "value": "Chat" + } + }, + { + "key": "Telemetry", + "name": { + "key": "telemetryConfigurationTitle", + "value": "Telemetry" + } + }, + { + "key": "Update", + "name": { + "key": "updateConfigurationTitle", + "value": "Update" + } + } + ], + "policies": [ + { + "key": "chat.mcp.gallery.serviceUrl", + "name": "McpGalleryServiceUrl", + "category": "InteractiveSession", + "minimumVersion": "1.101", + "localization": { + "description": { + "key": "mcp.gallery.serviceUrl", + "value": "Configure the MCP Gallery service URL to connect to" + } + }, + "type": "string", + "default": "" + }, + { + "key": "extensions.gallery.serviceUrl", + "name": "ExtensionGalleryServiceUrl", + "category": "Extensions", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "extensions.gallery.serviceUrl", + "value": "Configure the Marketplace service URL to connect to" + } + }, + "type": "string", + "default": "" + }, + { + "key": "extensions.allowed", + "name": "AllowedExtensions", + "category": "Extensions", + "minimumVersion": "1.96", + "localization": { + "description": { + "key": "extensions.allowed.policy", + "value": "Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions" + } + }, + "type": "object", + "default": "*" + }, + { + "key": "chat.tools.global.autoApprove", + "name": "ChatToolsAutoApprove", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "autoApprove2.description", + "value": "Global auto approve also known as \"YOLO mode\" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine." + } + }, + "type": "boolean", + "default": false + }, + { + "key": "chat.tools.eligibleForAutoApproval", + "name": "ChatToolsEligibleForAutoApproval", + "category": "InteractiveSession", + "minimumVersion": "1.107", + "localization": { + "description": { + "key": "chat.tools.eligibleForAutoApproval", + "value": "Controls which tools are eligible for automatic approval. Tools set to 'false' will always present a confirmation and will never offer the option to auto-approve. The default behavior (or setting a tool to 'true') may result in the tool offering auto-approval options." + } + }, + "type": "object", + "default": {} + }, + { + "key": "chat.mcp.access", + "name": "ChatMCP", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "chat.mcp.access", + "value": "Controls access to installed Model Context Protocol servers." + }, + "enumDescriptions": [ + { + "key": "chat.mcp.access.none", + "value": "No access to MCP servers." + }, + { + "key": "chat.mcp.access.registry", + "value": "Allows access to MCP servers installed from the registry that VS Code is connected to." + }, + { + "key": "chat.mcp.access.any", + "value": "Allow access to any installed MCP server." + } + ] + }, + "type": "string", + "default": "all", + "enum": [ + "none", + "registry", + "all" + ] + }, + { + "key": "chat.extensionTools.enabled", + "name": "ChatAgentExtensionTools", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "chat.extensionToolsEnabled", + "value": "Enable using tools contributed by third-party extensions." + } + }, + "type": "boolean", + "default": true + }, + { + "key": "chat.agent.enabled", + "name": "ChatAgentMode", + "category": "InteractiveSession", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "chat.agent.enabled.description", + "value": "When enabled, agent mode can be activated from chat and tools in agentic contexts with side effects can be used." + } + }, + "type": "boolean", + "default": true + }, + { + "key": "chat.tools.terminal.enableAutoApprove", + "name": "ChatToolsTerminalEnableAutoApprove", + "category": "IntegratedTerminal", + "minimumVersion": "1.104", + "localization": { + "description": { + "key": "autoApproveMode.description", + "value": "Controls whether to allow auto approval in the run in terminal tool." + } + }, + "type": "boolean", + "default": true + }, + { + "key": "update.mode", + "name": "UpdateMode", + "category": "Update", + "minimumVersion": "1.67", + "localization": { + "description": { + "key": "updateMode", + "value": "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service." + }, + "enumDescriptions": [ + { + "key": "none", + "value": "Disable updates." + }, + { + "key": "manual", + "value": "Disable automatic background update checks. Updates will be available if you manually check for updates." + }, + { + "key": "start", + "value": "Check for updates only on startup. Disable automatic background update checks." + }, + { + "key": "default", + "value": "Enable automatic update checks. Code will check for updates automatically and periodically." + } + ] + }, + "type": "string", + "default": "default", + "enum": [ + "none", + "manual", + "start", + "default" + ] + }, + { + "key": "telemetry.telemetryLevel", + "name": "TelemetryLevel", + "category": "Telemetry", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "telemetry.telemetryLevel.policyDescription", + "value": "Controls the level of telemetry." + }, + "enumDescriptions": [ + { + "key": "telemetry.telemetryLevel.default", + "value": "Sends usage data, errors, and crash reports." + }, + { + "key": "telemetry.telemetryLevel.error", + "value": "Sends general error telemetry and crash reports." + }, + { + "key": "telemetry.telemetryLevel.crash", + "value": "Sends OS level crash reports." + }, + { + "key": "telemetry.telemetryLevel.off", + "value": "Disables all product telemetry." + } + ] + }, + "type": "string", + "default": "all", + "enum": [ + "all", + "error", + "crash", + "off" + ] + }, + { + "key": "telemetry.feedback.enabled", + "name": "EnableFeedback", + "category": "Telemetry", + "minimumVersion": "1.99", + "localization": { + "description": { + "key": "telemetry.feedback.enabled", + "value": "Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options." + } + }, + "type": "boolean", + "default": true + } + ] +} diff --git a/build/lib/policies/policyGenerator.ts b/build/lib/policies/policyGenerator.ts new file mode 100644 index 0000000000000..e0de81f4d32b5 --- /dev/null +++ b/build/lib/policies/policyGenerator.ts @@ -0,0 +1,244 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import minimist from 'minimist'; +import * as fs from 'fs'; +import path from 'path'; +import { type CategoryDto, type ExportedPolicyDataDto } from './policyDto.ts'; +import * as JSONC from 'jsonc-parser'; +import { BooleanPolicy } from './booleanPolicy.ts'; +import { NumberPolicy } from './numberPolicy.ts'; +import { ObjectPolicy } from './objectPolicy.ts'; +import { StringEnumPolicy } from './stringEnumPolicy.ts'; +import { StringPolicy } from './stringPolicy.ts'; +import { type Version, type LanguageTranslations, type Policy, type Translations, Languages, type ProductJson } from './types.ts'; +import { renderGP, renderJsonPolicies, renderMacOSPolicy } from './render.ts'; + +const product: ProductJson = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, '../../../product.json'), 'utf8')); +const packageJson = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, '../../../package.json'), 'utf8')); + +async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version): Promise { + const resource = { + publisher: 'ms-ceintl', + name: `vscode-language-pack-${languageId}`, + version: `${version[0]}.${version[1]}.${version[2]}`, + path: 'extension/translations/main.i18n.json' + }; + + const url = resourceUrlTemplate.replace(/\{([^}]+)\}/g, (_, key) => resource[key as keyof typeof resource]); + const res = await fetch(url); + + if (res.status !== 200) { + throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`); + } + + const { contents: result } = await res.json() as { contents: LanguageTranslations }; + + // TODO: support module namespacing + // Flatten all moduleName keys to empty string + const flattened: LanguageTranslations = { '': {} }; + for (const moduleName in result) { + for (const nlsKey in result[moduleName]) { + flattened[''][nlsKey] = result[moduleName][nlsKey]; + } + } + + return flattened; +} + +function parseVersion(version: string): Version { + const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version)!; + return [parseInt(major), parseInt(minor), parseInt(patch)]; +} + +function compareVersions(a: Version, b: Version): number { + if (a[0] !== b[0]) { return a[0] - b[0]; } + if (a[1] !== b[1]) { return a[1] - b[1]; } + return a[2] - b[2]; +} + +async function queryVersions(serviceUrl: string, languageId: string): Promise { + const res = await fetch(`${serviceUrl}/extensionquery`, { + method: 'POST', + headers: { + 'Accept': 'application/json;api-version=3.0-preview.1', + 'Content-Type': 'application/json', + 'User-Agent': 'VS Code Build', + }, + body: JSON.stringify({ + filters: [{ criteria: [{ filterType: 7, value: `ms-ceintl.vscode-language-pack-${languageId}` }] }], + flags: 0x1 + }) + }); + + if (res.status !== 200) { + throw new Error(`[${res.status}] Error querying for extension: ${languageId}`); + } + + const result = await res.json() as { results: [{ extensions: { versions: { version: string }[] }[] }] }; + return result.results[0].extensions[0].versions.map(v => parseVersion(v.version)).sort(compareVersions); +} + +async function getNLS(extensionGalleryServiceUrl: string, resourceUrlTemplate: string, languageId: string, version: Version) { + const versions = await queryVersions(extensionGalleryServiceUrl, languageId); + const nextMinor: Version = [version[0], version[1] + 1, 0]; + const compatibleVersions = versions.filter(v => compareVersions(v, nextMinor) < 0); + const latestCompatibleVersion = compatibleVersions.at(-1)!; // order is newest to oldest + + if (!latestCompatibleVersion) { + throw new Error(`No compatible language pack found for ${languageId} for version ${version}`); + } + + return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); +} + +// TODO: add more policy types +const PolicyTypes = [ + BooleanPolicy, + NumberPolicy, + StringEnumPolicy, + StringPolicy, + ObjectPolicy +]; + +async function parsePolicies(policyDataFile: string): Promise { + const contents = JSONC.parse(await fs.promises.readFile(policyDataFile, { encoding: 'utf8' })) as ExportedPolicyDataDto; + const categories = new Map(); + for (const category of contents.categories) { + categories.set(category.key, category); + } + + const policies: Policy[] = []; + for (const policy of contents.policies) { + const category = categories.get(policy.category); + if (!category) { + throw new Error(`Unknown category: ${policy.category}`); + } + + let result: Policy | undefined; + for (const policyType of PolicyTypes) { + if (result = policyType.from(category, policy)) { + break; + } + } + + if (!result) { + throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`); + } + + policies.push(result); + } + + // Sort policies first by category name, then by policy name + policies.sort((a, b) => { + const categoryCompare = a.category.name.value.localeCompare(b.category.name.value); + if (categoryCompare !== 0) { + return categoryCompare; + } + return a.name.localeCompare(b.name); + }); + + return policies; +} + +async function getTranslations(): Promise { + const extensionGalleryServiceUrl = product.extensionsGallery?.serviceUrl; + + if (!extensionGalleryServiceUrl) { + console.warn(`Skipping policy localization: No 'extensionGallery.serviceUrl' found in 'product.json'.`); + return []; + } + + const resourceUrlTemplate = product.extensionsGallery?.resourceUrlTemplate; + + if (!resourceUrlTemplate) { + console.warn(`Skipping policy localization: No 'resourceUrlTemplate' found in 'product.json'.`); + return []; + } + + const version = parseVersion(packageJson.version); + const languageIds = Object.keys(Languages); + + return await Promise.all(languageIds.map( + languageId => getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version) + .then(languageTranslations => ({ languageId, languageTranslations })) + )); +} + +async function windowsMain(policies: Policy[], translations: Translations) { + const root = '.build/policies/win32'; + const { admx, adml } = renderGP(product, policies, translations); + + await fs.promises.rm(root, { recursive: true, force: true }); + await fs.promises.mkdir(root, { recursive: true }); + + await fs.promises.writeFile(path.join(root, `${product.win32RegValueName}.admx`), admx.replace(/\r?\n/g, '\n')); + + for (const { languageId, contents } of adml) { + const languagePath = path.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId as keyof typeof Languages]); + await fs.promises.mkdir(languagePath, { recursive: true }); + await fs.promises.writeFile(path.join(languagePath, `${product.win32RegValueName}.adml`), contents.replace(/\r?\n/g, '\n')); + } +} + +async function darwinMain(policies: Policy[], translations: Translations) { + const bundleIdentifier = product.darwinBundleIdentifier; + if (!bundleIdentifier || !product.darwinProfilePayloadUUID || !product.darwinProfileUUID) { + throw new Error(`Missing required product information.`); + } + const root = '.build/policies/darwin'; + const { profile, manifests } = renderMacOSPolicy(product, policies, translations); + + await fs.promises.rm(root, { recursive: true, force: true }); + await fs.promises.mkdir(root, { recursive: true }); + await fs.promises.writeFile(path.join(root, `${bundleIdentifier}.mobileconfig`), profile.replace(/\r?\n/g, '\n')); + + for (const { languageId, contents } of manifests) { + const languagePath = path.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId as keyof typeof Languages]); + await fs.promises.mkdir(languagePath, { recursive: true }); + await fs.promises.writeFile(path.join(languagePath, `${bundleIdentifier}.plist`), contents.replace(/\r?\n/g, '\n')); + } +} + +async function linuxMain(policies: Policy[]) { + const root = '.build/policies/linux'; + const policyFileContents = JSON.stringify(renderJsonPolicies(policies), undefined, 4); + + await fs.promises.rm(root, { recursive: true, force: true }); + await fs.promises.mkdir(root, { recursive: true }); + + const jsonPath = path.join(root, `policy.json`); + await fs.promises.writeFile(jsonPath, policyFileContents.replace(/\r?\n/g, '\n')); +} + +async function main() { + const args = minimist(process.argv.slice(2)); + if (args._.length !== 2) { + console.error(`Usage: node build/lib/policies `); + process.exit(1); + } + + const policyDataFile = args._[0]; + const platform = args._[1]; + const [policies, translations] = await Promise.all([parsePolicies(policyDataFile), getTranslations()]); + + if (platform === 'darwin') { + await darwinMain(policies, translations); + } else if (platform === 'win32') { + await windowsMain(policies, translations); + } else if (platform === 'linux') { + await linuxMain(policies); + } else { + console.error(`Usage: node build/lib/policies `); + process.exit(1); + } +} + +if (import.meta.main) { + main().catch(err => { + console.error(err); + process.exit(1); + }); +} diff --git a/build/lib/policies/render.ts b/build/lib/policies/render.ts new file mode 100644 index 0000000000000..47b485d1bf078 --- /dev/null +++ b/build/lib/policies/render.ts @@ -0,0 +1,303 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { NlsString, LanguageTranslations, Category, Policy, Translations, ProductJson } from './types.ts'; + +export function renderADMLString(prefix: string, moduleName: string, nlsString: NlsString, translations?: LanguageTranslations): string { + let value: string | undefined; + + if (translations) { + const moduleTranslations = translations[moduleName]; + + if (moduleTranslations) { + value = moduleTranslations[nlsString.nlsKey]; + } + } + + if (!value) { + value = nlsString.value; + } + + return `${value}`; +} + +export function renderProfileString(_prefix: string, moduleName: string, nlsString: NlsString, translations?: LanguageTranslations): string { + let value: string | undefined; + + if (translations) { + const moduleTranslations = translations[moduleName]; + + if (moduleTranslations) { + value = moduleTranslations[nlsString.nlsKey]; + } + } + + if (!value) { + value = nlsString.value; + } + + return value; +} + +export function renderADMX(regKey: string, versions: string[], categories: Category[], policies: Policy[]) { + versions = versions.map(v => v.replace(/\./g, '_')); + + return ` + + + + + + + + ${versions.map(v => ``).join(`\n `)} + + + + + ${categories.map(c => ``).join(`\n `)} + + + ${policies.map(p => p.renderADMX(regKey)).flat().join(`\n `)} + + +`; +} + +export function renderADML(appName: string, versions: string[], categories: Category[], policies: Policy[], translations?: LanguageTranslations) { + return ` + + + + + + ${appName} + ${versions.map(v => `${appName} >= ${v}`).join(`\n `)} + ${categories.map(c => renderADMLString('Category', c.moduleName, c.name, translations)).join(`\n `)} + ${policies.map(p => p.renderADMLStrings(translations)).flat().join(`\n `)} + + + ${policies.map(p => p.renderADMLPresentation()).join(`\n `)} + + + +`; +} + +export function renderProfileManifest(appName: string, bundleIdentifier: string, _versions: string[], _categories: Category[], policies: Policy[], translations?: LanguageTranslations) { + + const requiredPayloadFields = ` + + pfm_default + Configure ${appName} + pfm_name + PayloadDescription + pfm_title + Payload Description + pfm_type + string + + + pfm_default + ${appName} + pfm_name + PayloadDisplayName + pfm_require + always + pfm_title + Payload Display Name + pfm_type + string + + + pfm_default + ${bundleIdentifier} + pfm_name + PayloadIdentifier + pfm_require + always + pfm_title + Payload Identifier + pfm_type + string + + + pfm_default + ${bundleIdentifier} + pfm_name + PayloadType + pfm_require + always + pfm_title + Payload Type + pfm_type + string + + + pfm_default + + pfm_name + PayloadUUID + pfm_require + always + pfm_title + Payload UUID + pfm_type + string + + + pfm_default + 1 + pfm_name + PayloadVersion + pfm_range_list + + 1 + + pfm_require + always + pfm_title + Payload Version + pfm_type + integer + + + pfm_default + Microsoft + pfm_name + PayloadOrganization + pfm_title + Payload Organization + pfm_type + string + `; + + const profileManifestSubkeys = policies.map(policy => { + return policy.renderProfileManifest(translations); + }).join(''); + + return ` + + + + pfm_app_url + https://code.visualstudio.com/ + pfm_description + ${appName} Managed Settings + pfm_documentation_url + https://code.visualstudio.com/docs/setup/enterprise + pfm_domain + ${bundleIdentifier} + pfm_format_version + 1 + pfm_interaction + combined + pfm_last_modified + ${new Date().toISOString().replace(/\.\d+Z$/, 'Z')} + pfm_platforms + + macOS + + pfm_subkeys + + ${requiredPayloadFields} + ${profileManifestSubkeys} + + pfm_title + ${appName} + pfm_unique + + pfm_version + 1 + +`; +} + +export function renderMacOSPolicy(product: ProductJson, policies: Policy[], translations: Translations) { + const appName = product.nameLong; + const bundleIdentifier = product.darwinBundleIdentifier; + const payloadUUID = product.darwinProfilePayloadUUID; + const UUID = product.darwinProfileUUID; + + const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); + const categories = [...new Set(policies.map(p => p.category))]; + + const policyEntries = + policies.map(policy => policy.renderProfile()) + .flat() + .map(entry => `\t\t\t\t${entry}`) + .join('\n'); + + + return { + profile: ` + + + + PayloadContent + + + PayloadDisplayName + ${appName} + PayloadIdentifier + ${bundleIdentifier}.${UUID} + PayloadType + ${bundleIdentifier} + PayloadUUID + ${UUID} + PayloadVersion + 1 +${policyEntries} + + + PayloadDescription + This profile manages ${appName}. For more information see https://code.visualstudio.com/docs/setup/enterprise + PayloadDisplayName + ${appName} + PayloadIdentifier + ${bundleIdentifier} + PayloadOrganization + Microsoft + PayloadType + Configuration + PayloadUUID + ${payloadUUID} + PayloadVersion + 1 + TargetDeviceType + 5 + +`, + manifests: [{ languageId: 'en-us', contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies) }, + ...translations.map(({ languageId, languageTranslations }) => + ({ languageId, contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies, languageTranslations) })) + ] + }; +} + +export function renderGP(product: ProductJson, policies: Policy[], translations: Translations) { + const appName = product.nameLong; + const regKey = product.win32RegValueName; + + const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); + const categories = [...Object.values(policies.reduce((acc, p) => ({ ...acc, [p.category.name.nlsKey]: p.category }), {}))] as Category[]; + + return { + admx: renderADMX(regKey, versions, categories, policies), + adml: [ + { languageId: 'en-us', contents: renderADML(appName, versions, categories, policies) }, + ...translations.map(({ languageId, languageTranslations }) => + ({ languageId, contents: renderADML(appName, versions, categories, policies, languageTranslations) })) + ] + }; +} + +export function renderJsonPolicies(policies: Policy[]) { + const policyObject: { [key: string]: string | number | boolean | object | null } = {}; + for (const policy of policies) { + policyObject[policy.name] = policy.renderJsonValue(); + } + return policyObject; +} diff --git a/build/lib/policies/stringEnumPolicy.ts b/build/lib/policies/stringEnumPolicy.ts new file mode 100644 index 0000000000000..b590abcc87b84 --- /dev/null +++ b/build/lib/policies/stringEnumPolicy.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BasePolicy } from './basePolicy.ts'; +import type { CategoryDto, PolicyDto } from './policyDto.ts'; +import { renderProfileString } from './render.ts'; +import { type Category, type NlsString, PolicyType, type LanguageTranslations } from './types.ts'; + +export class StringEnumPolicy extends BasePolicy { + + static from(category: CategoryDto, policy: PolicyDto): StringEnumPolicy | undefined { + const { type, name, minimumVersion, enum: enumValue, localization } = policy; + + if (type !== 'string') { + return undefined; + } + + const enum_ = enumValue; + + if (!enum_) { + return undefined; + } + + if (!localization.enumDescriptions || !Array.isArray(localization.enumDescriptions) || localization.enumDescriptions.length !== enum_.length) { + throw new Error(`Invalid policy data: enumDescriptions must exist and have the same length as enum_ for policy "${name}".`); + } + const enumDescriptions = localization.enumDescriptions.map((e) => ({ nlsKey: e.key, value: e.value })); + return new StringEnumPolicy( + name, + { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, + minimumVersion, + { nlsKey: localization.description.key, value: localization.description.value }, + '', + enum_, + enumDescriptions + ); + } + + protected enum_: string[]; + protected enumDescriptions: NlsString[]; + + private constructor( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + enum_: string[], + enumDescriptions: NlsString[], + ) { + super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); + this.enum_ = enum_; + this.enumDescriptions = enumDescriptions; + } + + protected renderADMXElements(): string[] { + return [ + ``, + ...this.enum_.map((value, index) => ` ${value}`), + `` + ]; + } + + renderADMLStrings(translations?: LanguageTranslations) { + return [ + ...super.renderADMLStrings(translations), + ...this.enumDescriptions.map(e => this.renderADMLString(e, translations)) + ]; + } + + renderADMLPresentationContents() { + return ``; + } + + renderJsonValue() { + return this.enum_[0]; + } + + renderProfileValue() { + return `${this.enum_[0]}`; + } + + renderProfileManifestValue(translations?: LanguageTranslations): string { + return `pfm_default +${this.enum_[0]} +pfm_description +${renderProfileString(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +string +pfm_range_list + + ${this.enum_.map(e => `${e}`).join('\n ')} +`; + } +} diff --git a/build/lib/policies/stringPolicy.ts b/build/lib/policies/stringPolicy.ts new file mode 100644 index 0000000000000..e4e07e42c6963 --- /dev/null +++ b/build/lib/policies/stringPolicy.ts @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { BasePolicy } from './basePolicy.ts'; +import type { CategoryDto, PolicyDto } from './policyDto.ts'; +import { renderProfileString } from './render.ts'; +import { PolicyType, type Category, type LanguageTranslations, type NlsString } from './types.ts'; + +export class StringPolicy extends BasePolicy { + + static from(category: CategoryDto, policy: PolicyDto): StringPolicy | undefined { + const { type, name, minimumVersion, localization } = policy; + + if (type !== 'string') { + return undefined; + } + + return new StringPolicy(name, { moduleName: '', name: { nlsKey: category.name.key, value: category.name.value } }, minimumVersion, { nlsKey: localization.description.key, value: localization.description.value }, ''); + } + + private constructor( + name: string, + category: Category, + minimumVersion: string, + description: NlsString, + moduleName: string, + ) { + super(PolicyType.String, name, category, minimumVersion, description, moduleName); + } + + protected renderADMXElements(): string[] { + return [``]; + } + + renderJsonValue() { + return ''; + } + + renderADMLPresentationContents() { + return ``; + } + + renderProfileValue(): string { + return ``; + } + + renderProfileManifestValue(translations?: LanguageTranslations): string { + return `pfm_default + +pfm_description +${renderProfileString(this.name, this.moduleName, this.description, translations)} +pfm_name +${this.name} +pfm_title +${this.name} +pfm_type +string`; + } +} diff --git a/build/lib/policies/types.ts b/build/lib/policies/types.ts new file mode 100644 index 0000000000000..4fe801c23d69c --- /dev/null +++ b/build/lib/policies/types.ts @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface ProductJson { + readonly nameLong: string; + readonly darwinBundleIdentifier: string; + readonly darwinProfilePayloadUUID: string; + readonly darwinProfileUUID: string; + readonly win32RegValueName: string; + readonly extensionsGallery?: { + readonly serviceUrl: string; + readonly resourceUrlTemplate: string; + }; +} + +export interface Policy { + readonly name: string; + readonly type: PolicyType; + readonly category: Category; + readonly minimumVersion: string; + renderADMX(regKey: string): string[]; + renderADMLStrings(translations?: LanguageTranslations): string[]; + renderADMLPresentation(): string; + renderJsonValue(): string | number | boolean | object | null; + renderProfile(): string[]; + // https://github.com/ProfileManifests/ProfileManifests/wiki/Manifest-Format + renderProfileManifest(translations?: LanguageTranslations): string; +} + +export type NlsString = { value: string; nlsKey: string }; + +export interface Category { + readonly moduleName: string; + readonly name: NlsString; +} + +export const PolicyType = Object.freeze({ + Boolean: 'boolean', + Number: 'number', + Object: 'object', + String: 'string', + StringEnum: 'stringEnum', +}); +export type PolicyType = typeof PolicyType[keyof typeof PolicyType]; + +export const Languages = { + 'fr': 'fr-fr', + 'it': 'it-it', + 'de': 'de-de', + 'es': 'es-es', + 'ru': 'ru-ru', + 'zh-hans': 'zh-cn', + 'zh-hant': 'zh-tw', + 'ja': 'ja-jp', + 'ko': 'ko-kr', + 'cs': 'cs-cz', + 'pt-br': 'pt-br', + 'tr': 'tr-tr', + 'pl': 'pl-pl', +}; + +export type LanguageTranslations = { [moduleName: string]: { [nlsKey: string]: string } }; +export type Translations = { languageId: string; languageTranslations: LanguageTranslations }[]; + +export type Version = [number, number, number]; diff --git a/build/lib/preLaunch.js b/build/lib/preLaunch.js deleted file mode 100644 index 75207fe50c037..0000000000000 --- a/build/lib/preLaunch.js +++ /dev/null @@ -1,59 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -// @ts-check -const path_1 = __importDefault(require("path")); -const child_process_1 = require("child_process"); -const fs_1 = require("fs"); -const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; -const rootDir = path_1.default.resolve(__dirname, '..', '..'); -function runProcess(command, args = []) { - return new Promise((resolve, reject) => { - const child = (0, child_process_1.spawn)(command, args, { cwd: rootDir, stdio: 'inherit', env: process.env, shell: process.platform === 'win32' }); - child.on('exit', err => !err ? resolve() : process.exit(err ?? 1)); - child.on('error', reject); - }); -} -async function exists(subdir) { - try { - await fs_1.promises.stat(path_1.default.join(rootDir, subdir)); - return true; - } - catch { - return false; - } -} -async function ensureNodeModules() { - if (!(await exists('node_modules'))) { - await runProcess(npm, ['ci']); - } -} -async function getElectron() { - await runProcess(npm, ['run', 'electron']); -} -async function ensureCompiled() { - if (!(await exists('out'))) { - await runProcess(npm, ['run', 'compile']); - } -} -async function main() { - await ensureNodeModules(); - await getElectron(); - await ensureCompiled(); - // Can't require this until after dependencies are installed - const { getBuiltInExtensions } = require('./builtInExtensions'); - await getBuiltInExtensions(); -} -if (require.main === module) { - main().catch(err => { - console.error(err); - process.exit(1); - }); -} -//# sourceMappingURL=preLaunch.js.map \ No newline at end of file diff --git a/build/lib/preLaunch.ts b/build/lib/preLaunch.ts index 0c178afcb5985..5e175afde2884 100644 --- a/build/lib/preLaunch.ts +++ b/build/lib/preLaunch.ts @@ -2,15 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -// @ts-check - import path from 'path'; import { spawn } from 'child_process'; import { promises as fs } from 'fs'; const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; -const rootDir = path.resolve(__dirname, '..', '..'); +const rootDir = path.resolve(import.meta.dirname, '..', '..'); function runProcess(command: string, args: ReadonlyArray = []) { return new Promise((resolve, reject) => { @@ -51,11 +48,11 @@ async function main() { await ensureCompiled(); // Can't require this until after dependencies are installed - const { getBuiltInExtensions } = require('./builtInExtensions'); + const { getBuiltInExtensions } = await import('./builtInExtensions.ts'); await getBuiltInExtensions(); } -if (require.main === module) { +if (import.meta.main) { main().catch(err => { console.error(err); process.exit(1); diff --git a/build/lib/propertyInitOrderChecker.js b/build/lib/propertyInitOrderChecker.js deleted file mode 100644 index 67a17054cd66a..0000000000000 --- a/build/lib/propertyInitOrderChecker.js +++ /dev/null @@ -1,251 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -exports.EntryKind = void 0; -const ts = __importStar(require("typescript")); -const path = __importStar(require("path")); -const fs = __importStar(require("fs")); -const TS_CONFIG_PATH = path.join(__dirname, '../../', 'src', 'tsconfig.json'); -// -// ############################################################################################# -// -// A custom typescript checker that ensure constructor properties are NOT used to initialize -// defined properties. This is needed for the times when `useDefineForClassFields` is gone. -// -// see https://github.com/microsoft/vscode/issues/243049, https://github.com/microsoft/vscode/issues/186726, -// https://github.com/microsoft/vscode/pull/241544 -// -// ############################################################################################# -// -const cancellationToken = { - isCancellationRequested: () => false, - throwIfCancellationRequested: () => { }, -}; -const seenFiles = new Set(); -let errorCount = 0; -function createProgram(tsconfigPath) { - const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile); - const configHostParser = { fileExists: fs.existsSync, readDirectory: ts.sys.readDirectory, readFile: file => fs.readFileSync(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' }; - const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, path.resolve(path.dirname(tsconfigPath)), { noEmit: true }); - const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true); - return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost); -} -const program = createProgram(TS_CONFIG_PATH); -program.getTypeChecker(); -for (const file of program.getSourceFiles()) { - if (!file || file.isDeclarationFile) { - continue; - } - visit(file); -} -if (seenFiles.size) { - console.log(); - console.log(`Found ${errorCount} error${errorCount === 1 ? '' : 's'} in ${seenFiles.size} file${seenFiles.size === 1 ? '' : 's'}.`); - process.exit(errorCount); -} -function visit(node) { - if (ts.isParameter(node) && ts.isParameterPropertyDeclaration(node, node.parent)) { - checkParameterPropertyDeclaration(node); - } - ts.forEachChild(node, visit); -} -function checkParameterPropertyDeclaration(param) { - const uses = [...collectReferences(param.name, [])]; - if (!uses.length) { - return; - } - const sourceFile = param.getSourceFile(); - if (!seenFiles.has(sourceFile)) { - if (seenFiles.size) { - console.log(``); - } - console.log(`${formatFileName(param)}:`); - seenFiles.add(sourceFile); - } - else { - console.log(``); - } - console.log(` Parameter property '${param.name.getText()}' is used before its declaration.`); - for (const { stack, container } of uses) { - const use = stack[stack.length - 1]; - console.log(` at ${formatLocation(use)}: ${formatMember(container)} -> ${formatStack(stack)}`); - errorCount++; - } -} -function* collectReferences(node, stack, requiresInvocationDepth = 0, seen = new Set()) { - for (const use of findAllReferencesInClass(node)) { - const container = findContainer(use); - if (!container || seen.has(container) || ts.isConstructorDeclaration(container)) { - continue; - } - seen.add(container); - const nextStack = [...stack, use]; - let nextRequiresInvocationDepth = requiresInvocationDepth; - if (isInvocation(use) && nextRequiresInvocationDepth > 0) { - nextRequiresInvocationDepth--; - } - if (ts.isPropertyDeclaration(container) && nextRequiresInvocationDepth === 0) { - yield { stack: nextStack, container }; - } - else if (requiresInvocation(container)) { - nextRequiresInvocationDepth++; - } - yield* collectReferences(container.name ?? container, nextStack, nextRequiresInvocationDepth, seen); - } -} -function requiresInvocation(definition) { - return ts.isMethodDeclaration(definition) || ts.isFunctionDeclaration(definition) || ts.isFunctionExpression(definition) || ts.isArrowFunction(definition); -} -function isInvocation(use) { - let location = use; - if (ts.isPropertyAccessExpression(location.parent) && location.parent.name === location) { - location = location.parent; - } - else if (ts.isElementAccessExpression(location.parent) && location.parent.argumentExpression === location) { - location = location.parent; - } - return ts.isCallExpression(location.parent) && location.parent.expression === location - || ts.isTaggedTemplateExpression(location.parent) && location.parent.tag === location; -} -function formatFileName(node) { - const sourceFile = node.getSourceFile(); - return path.resolve(sourceFile.fileName); -} -function formatLocation(node) { - const sourceFile = node.getSourceFile(); - const { line, character } = ts.getLineAndCharacterOfPosition(sourceFile, node.pos); - return `${formatFileName(sourceFile)}(${line + 1},${character + 1})`; -} -function formatStack(stack) { - return stack.slice().reverse().map((use) => formatUse(use)).join(' -> '); -} -function formatMember(container) { - const name = container.name?.getText(); - if (name) { - const className = findClass(container)?.name?.getText(); - if (className) { - return `${className}.${name}`; - } - return name; - } - return ''; -} -function formatUse(use) { - let text = use.getText(); - if (use.parent && ts.isPropertyAccessExpression(use.parent) && use.parent.name === use) { - if (use.parent.expression.kind === ts.SyntaxKind.ThisKeyword) { - text = `this.${text}`; - } - use = use.parent; - } - else if (use.parent && ts.isElementAccessExpression(use.parent) && use.parent.argumentExpression === use) { - if (use.parent.expression.kind === ts.SyntaxKind.ThisKeyword) { - text = `this['${text}']`; - } - use = use.parent; - } - if (ts.isCallExpression(use.parent)) { - text = `${text}(...)`; - } - return text; -} -function findContainer(node) { - return ts.findAncestor(node, ancestor => { - switch (ancestor.kind) { - case ts.SyntaxKind.PropertyDeclaration: - case ts.SyntaxKind.MethodDeclaration: - case ts.SyntaxKind.GetAccessor: - case ts.SyntaxKind.SetAccessor: - case ts.SyntaxKind.Constructor: - case ts.SyntaxKind.ClassStaticBlockDeclaration: - case ts.SyntaxKind.ArrowFunction: - case ts.SyntaxKind.FunctionExpression: - case ts.SyntaxKind.FunctionDeclaration: - case ts.SyntaxKind.Parameter: - return true; - } - return false; - }); -} -function findClass(node) { - return ts.findAncestor(node, ts.isClassLike); -} -function* findAllReferencesInClass(node) { - const classDecl = findClass(node); - if (!classDecl) { - return []; - } - for (const ref of findAllReferences(node)) { - for (const entry of ref.references) { - if (entry.kind !== 1 /* EntryKind.Node */ || entry.node === node) { - continue; - } - if (findClass(entry.node) === classDecl) { - yield entry.node; - } - } - } -} -// NOTE: The following uses TypeScript internals and are subject to change from version to version. -function findAllReferences(node) { - const sourceFile = node.getSourceFile(); - const position = node.getStart(); - const name = ts.getTouchingPropertyName(sourceFile, position); - const options = { use: ts.FindAllReferences.FindReferencesUse.References }; - return ts.FindAllReferences.Core.getReferencedSymbolsForNode(position, name, program, [sourceFile], cancellationToken, options) ?? []; -} -var DefinitionKind; -(function (DefinitionKind) { - DefinitionKind[DefinitionKind["Symbol"] = 0] = "Symbol"; - DefinitionKind[DefinitionKind["Label"] = 1] = "Label"; - DefinitionKind[DefinitionKind["Keyword"] = 2] = "Keyword"; - DefinitionKind[DefinitionKind["This"] = 3] = "This"; - DefinitionKind[DefinitionKind["String"] = 4] = "String"; - DefinitionKind[DefinitionKind["TripleSlashReference"] = 5] = "TripleSlashReference"; -})(DefinitionKind || (DefinitionKind = {})); -/** @internal */ -var EntryKind; -(function (EntryKind) { - EntryKind[EntryKind["Span"] = 0] = "Span"; - EntryKind[EntryKind["Node"] = 1] = "Node"; - EntryKind[EntryKind["StringLiteral"] = 2] = "StringLiteral"; - EntryKind[EntryKind["SearchedLocalFoundProperty"] = 3] = "SearchedLocalFoundProperty"; - EntryKind[EntryKind["SearchedPropertyFoundLocal"] = 4] = "SearchedPropertyFoundLocal"; -})(EntryKind || (exports.EntryKind = EntryKind = {})); -//# sourceMappingURL=propertyInitOrderChecker.js.map \ No newline at end of file diff --git a/build/lib/propertyInitOrderChecker.ts b/build/lib/propertyInitOrderChecker.ts index 141a9c918e6d4..2c07f9c875754 100644 --- a/build/lib/propertyInitOrderChecker.ts +++ b/build/lib/propertyInitOrderChecker.ts @@ -8,7 +8,7 @@ import * as ts from 'typescript'; import * as path from 'path'; import * as fs from 'fs'; -const TS_CONFIG_PATH = path.join(__dirname, '../../', 'src', 'tsconfig.json'); +const TS_CONFIG_PATH = path.join(import.meta.dirname, '../../', 'src', 'tsconfig.json'); // // ############################################################################################# @@ -22,6 +22,15 @@ const TS_CONFIG_PATH = path.join(__dirname, '../../', 'src', 'tsconfig.json'); // ############################################################################################# // +const EntryKind = Object.freeze({ + Span: 'Span', + Node: 'Node', + StringLiteral: 'StringLiteral', + SearchedLocalFoundProperty: 'SearchedLocalFoundProperty', + SearchedPropertyFoundLocal: 'SearchedPropertyFoundLocal' +}); + +type EntryKind = typeof EntryKind[keyof typeof EntryKind]; const cancellationToken: ts.CancellationToken = { isCancellationRequested: () => false, @@ -241,12 +250,32 @@ function* findAllReferencesInClass(node: ts.Node): Generator { // NOTE: The following uses TypeScript internals and are subject to change from version to version. +interface TypeScriptInternals { + getTouchingPropertyName(sourceFile: ts.SourceFile, position: number): ts.Node; + FindAllReferences: { + FindReferencesUse: { + References: number; + }; + Core: { + getReferencedSymbolsForNode( + position: number, + node: ts.Node, + program: ts.Program, + sourceFiles: readonly ts.SourceFile[], + cancellationToken: ts.CancellationToken, + options: { use: number } + ): readonly SymbolAndEntries[] | undefined; + }; + }; +} + function findAllReferences(node: ts.Node): readonly SymbolAndEntries[] { const sourceFile = node.getSourceFile(); const position = node.getStart(); - const name: ts.Node = (ts as any).getTouchingPropertyName(sourceFile, position); - const options = { use: (ts as any).FindAllReferences.FindReferencesUse.References }; - return (ts as any).FindAllReferences.Core.getReferencedSymbolsForNode(position, name, program, [sourceFile], cancellationToken, options) ?? []; + const tsInternal = ts as unknown as TypeScriptInternals; + const name: ts.Node = tsInternal.getTouchingPropertyName(sourceFile, position); + const options = { use: tsInternal.FindAllReferences.FindReferencesUse.References }; + return tsInternal.FindAllReferences.Core.getReferencedSymbolsForNode(position, name, program, [sourceFile], cancellationToken, options) ?? []; } interface SymbolAndEntries { @@ -254,32 +283,25 @@ interface SymbolAndEntries { readonly references: readonly Entry[]; } -const enum DefinitionKind { - Symbol, - Label, - Keyword, - This, - String, - TripleSlashReference, -} +const DefinitionKind = Object.freeze({ + Symbol: 0, + Label: 1, + Keyword: 2, + This: 3, + String: 4, + TripleSlashReference: 5, +}); +type DefinitionKind = typeof DefinitionKind[keyof typeof DefinitionKind]; type Definition = - | { readonly type: DefinitionKind.Symbol; readonly symbol: ts.Symbol } - | { readonly type: DefinitionKind.Label; readonly node: ts.Identifier } - | { readonly type: DefinitionKind.Keyword; readonly node: ts.Node } - | { readonly type: DefinitionKind.This; readonly node: ts.Node } - | { readonly type: DefinitionKind.String; readonly node: ts.StringLiteralLike } - | { readonly type: DefinitionKind.TripleSlashReference; readonly reference: ts.FileReference; readonly file: ts.SourceFile }; - -/** @internal */ -export const enum EntryKind { - Span, - Node, - StringLiteral, - SearchedLocalFoundProperty, - SearchedPropertyFoundLocal, -} -type NodeEntryKind = EntryKind.Node | EntryKind.StringLiteral | EntryKind.SearchedLocalFoundProperty | EntryKind.SearchedPropertyFoundLocal; + | { readonly type: DefinitionKind; readonly symbol: ts.Symbol } + | { readonly type: DefinitionKind; readonly node: ts.Identifier } + | { readonly type: DefinitionKind; readonly node: ts.Node } + | { readonly type: DefinitionKind; readonly node: ts.Node } + | { readonly type: DefinitionKind; readonly node: ts.StringLiteralLike } + | { readonly type: DefinitionKind; readonly reference: ts.FileReference; readonly file: ts.SourceFile }; + +type NodeEntryKind = typeof EntryKind.Node | typeof EntryKind.StringLiteral | typeof EntryKind.SearchedLocalFoundProperty | typeof EntryKind.SearchedPropertyFoundLocal; type Entry = NodeEntry | SpanEntry; interface ContextWithStartAndEndNode { start: ts.Node; @@ -292,7 +314,7 @@ interface NodeEntry { readonly context?: ContextNode; } interface SpanEntry { - readonly kind: EntryKind.Span; + readonly kind: typeof EntryKind.Span; readonly fileName: string; readonly textSpan: ts.TextSpan; } diff --git a/build/lib/reporter.js b/build/lib/reporter.js deleted file mode 100644 index 16bb44ec539dc..0000000000000 --- a/build/lib/reporter.js +++ /dev/null @@ -1,105 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.createReporter = createReporter; -const event_stream_1 = __importDefault(require("event-stream")); -const fancy_log_1 = __importDefault(require("fancy-log")); -const ansi_colors_1 = __importDefault(require("ansi-colors")); -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -class ErrorLog { - id; - constructor(id) { - this.id = id; - } - allErrors = []; - startTime = null; - count = 0; - onStart() { - if (this.count++ > 0) { - return; - } - this.startTime = new Date().getTime(); - (0, fancy_log_1.default)(`Starting ${ansi_colors_1.default.green('compilation')}${this.id ? ansi_colors_1.default.blue(` ${this.id}`) : ''}...`); - } - onEnd() { - if (--this.count > 0) { - return; - } - this.log(); - } - log() { - const errors = this.allErrors.flat(); - const seen = new Set(); - errors.map(err => { - if (!seen.has(err)) { - seen.add(err); - (0, fancy_log_1.default)(`${ansi_colors_1.default.red('Error')}: ${err}`); - } - }); - (0, fancy_log_1.default)(`Finished ${ansi_colors_1.default.green('compilation')}${this.id ? ansi_colors_1.default.blue(` ${this.id}`) : ''} with ${errors.length} errors after ${ansi_colors_1.default.magenta((new Date().getTime() - this.startTime) + ' ms')}`); - const regex = /^([^(]+)\((\d+),(\d+)\): (.*)$/s; - const messages = errors - .map(err => regex.exec(err)) - .filter(match => !!match) - .map(x => x) - .map(([, path, line, column, message]) => ({ path, line: parseInt(line), column: parseInt(column), message })); - try { - const logFileName = 'log' + (this.id ? `_${this.id}` : ''); - fs_1.default.writeFileSync(path_1.default.join(buildLogFolder, logFileName), JSON.stringify(messages)); - } - catch (err) { - //noop - } - } -} -const errorLogsById = new Map(); -function getErrorLog(id = '') { - let errorLog = errorLogsById.get(id); - if (!errorLog) { - errorLog = new ErrorLog(id); - errorLogsById.set(id, errorLog); - } - return errorLog; -} -const buildLogFolder = path_1.default.join(path_1.default.dirname(path_1.default.dirname(__dirname)), '.build'); -try { - fs_1.default.mkdirSync(buildLogFolder); -} -catch (err) { - // ignore -} -function createReporter(id) { - const errorLog = getErrorLog(id); - const errors = []; - errorLog.allErrors.push(errors); - const result = (err) => errors.push(err); - result.hasErrors = () => errors.length > 0; - result.end = (emitError) => { - errors.length = 0; - errorLog.onStart(); - return event_stream_1.default.through(undefined, function () { - errorLog.onEnd(); - if (emitError && errors.length > 0) { - if (!errors.__logged__) { - errorLog.log(); - } - errors.__logged__ = true; - const err = new Error(`Found ${errors.length} errors`); - err.__reporter__ = true; - this.emit('error', err); - } - else { - this.emit('end'); - } - }); - }; - return result; -} -//# sourceMappingURL=reporter.js.map \ No newline at end of file diff --git a/build/lib/reporter.ts b/build/lib/reporter.ts index c21fd841c0d19..31a0cb3945d1c 100644 --- a/build/lib/reporter.ts +++ b/build/lib/reporter.ts @@ -10,7 +10,10 @@ import fs from 'fs'; import path from 'path'; class ErrorLog { - constructor(public id: string) { + public id: string; + + constructor(id: string) { + this.id = id; } allErrors: string[][] = []; startTime: number | null = null; @@ -73,7 +76,7 @@ function getErrorLog(id: string = '') { return errorLog; } -const buildLogFolder = path.join(path.dirname(path.dirname(__dirname)), '.build'); +const buildLogFolder = path.join(path.dirname(path.dirname(import.meta.dirname)), '.build'); try { fs.mkdirSync(buildLogFolder); @@ -87,10 +90,18 @@ export interface IReporter { end(emitError: boolean): NodeJS.ReadWriteStream; } +class ReporterError extends Error { + __reporter__ = true; +} + +interface Errors extends Array { + __logged__?: boolean; +} + export function createReporter(id?: string): IReporter { const errorLog = getErrorLog(id); - const errors: string[] = []; + const errors: Errors = []; errorLog.allErrors.push(errors); const result = (err: string) => errors.push(err); @@ -105,14 +116,13 @@ export function createReporter(id?: string): IReporter { errorLog.onEnd(); if (emitError && errors.length > 0) { - if (!(errors as any).__logged__) { + if (!errors.__logged__) { errorLog.log(); } - (errors as any).__logged__ = true; + errors.__logged__ = true; - const err = new Error(`Found ${errors.length} errors`); - (err as any).__reporter__ = true; + const err = new ReporterError(`Found ${errors.length} errors`); this.emit('error', err); } else { this.emit('end'); diff --git a/build/lib/snapshotLoader.js b/build/lib/snapshotLoader.js deleted file mode 100644 index 7d9b3f154f17d..0000000000000 --- a/build/lib/snapshotLoader.js +++ /dev/null @@ -1,58 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.snaps = void 0; -var snaps; -(function (snaps) { - const fs = require('fs'); - const path = require('path'); - const os = require('os'); - const cp = require('child_process'); - const mksnapshot = path.join(__dirname, `../../node_modules/.bin/${process.platform === 'win32' ? 'mksnapshot.cmd' : 'mksnapshot'}`); - const product = require('../../product.json'); - const arch = (process.argv.join('').match(/--arch=(.*)/) || [])[1]; - // - let loaderFilepath; - let startupBlobFilepath; - switch (process.platform) { - case 'darwin': - loaderFilepath = `VSCode-darwin/${product.nameLong}.app/Contents/Resources/app/out/vs/loader.js`; - startupBlobFilepath = `VSCode-darwin/${product.nameLong}.app/Contents/Frameworks/Electron Framework.framework/Resources/snapshot_blob.bin`; - break; - case 'win32': - case 'linux': - loaderFilepath = `VSCode-${process.platform}-${arch}/resources/app/out/vs/loader.js`; - startupBlobFilepath = `VSCode-${process.platform}-${arch}/snapshot_blob.bin`; - break; - default: - throw new Error('Unknown platform'); - } - loaderFilepath = path.join(__dirname, '../../../', loaderFilepath); - startupBlobFilepath = path.join(__dirname, '../../../', startupBlobFilepath); - snapshotLoader(loaderFilepath, startupBlobFilepath); - function snapshotLoader(loaderFilepath, startupBlobFilepath) { - const inputFile = fs.readFileSync(loaderFilepath); - const wrappedInputFile = ` - var Monaco_Loader_Init; - (function() { - var doNotInitLoader = true; - ${inputFile.toString()}; - Monaco_Loader_Init = function() { - AMDLoader.init(); - CSSLoaderPlugin.init(); - NLSLoaderPlugin.init(); - - return { define, require }; - } - })(); - `; - const wrappedInputFilepath = path.join(os.tmpdir(), 'wrapped-loader.js'); - console.log(wrappedInputFilepath); - fs.writeFileSync(wrappedInputFilepath, wrappedInputFile); - cp.execFileSync(mksnapshot, [wrappedInputFilepath, `--startup_blob`, startupBlobFilepath]); - } -})(snaps || (exports.snaps = snaps = {})); -//# sourceMappingURL=snapshotLoader.js.map \ No newline at end of file diff --git a/build/lib/snapshotLoader.ts b/build/lib/snapshotLoader.ts index 3cb2191144d30..3df83f734476e 100644 --- a/build/lib/snapshotLoader.ts +++ b/build/lib/snapshotLoader.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export namespace snaps { +export const snaps = (() => { const fs = require('fs'); const path = require('path'); const os = require('os'); const cp = require('child_process'); - const mksnapshot = path.join(__dirname, `../../node_modules/.bin/${process.platform === 'win32' ? 'mksnapshot.cmd' : 'mksnapshot'}`); + const mksnapshot = path.join(import.meta.dirname, `../../node_modules/.bin/${process.platform === 'win32' ? 'mksnapshot.cmd' : 'mksnapshot'}`); const product = require('../../product.json'); const arch = (process.argv.join('').match(/--arch=(.*)/) || [])[1]; @@ -34,8 +34,8 @@ export namespace snaps { throw new Error('Unknown platform'); } - loaderFilepath = path.join(__dirname, '../../../', loaderFilepath); - startupBlobFilepath = path.join(__dirname, '../../../', startupBlobFilepath); + loaderFilepath = path.join(import.meta.dirname, '../../../', loaderFilepath); + startupBlobFilepath = path.join(import.meta.dirname, '../../../', startupBlobFilepath); snapshotLoader(loaderFilepath, startupBlobFilepath); @@ -62,4 +62,6 @@ export namespace snaps { cp.execFileSync(mksnapshot, [wrappedInputFilepath, `--startup_blob`, startupBlobFilepath]); } -} + + return {}; +})(); diff --git a/build/lib/standalone.js b/build/lib/standalone.js deleted file mode 100644 index 732a34228b92e..0000000000000 --- a/build/lib/standalone.js +++ /dev/null @@ -1,214 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.extractEditor = extractEditor; -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const tss = __importStar(require("./treeshaking")); -const REPO_ROOT = path_1.default.join(__dirname, '../../'); -const SRC_DIR = path_1.default.join(REPO_ROOT, 'src'); -const dirCache = {}; -function writeFile(filePath, contents) { - function ensureDirs(dirPath) { - if (dirCache[dirPath]) { - return; - } - dirCache[dirPath] = true; - ensureDirs(path_1.default.dirname(dirPath)); - if (fs_1.default.existsSync(dirPath)) { - return; - } - fs_1.default.mkdirSync(dirPath); - } - ensureDirs(path_1.default.dirname(filePath)); - fs_1.default.writeFileSync(filePath, contents); -} -function extractEditor(options) { - const ts = require('typescript'); - const tsConfig = JSON.parse(fs_1.default.readFileSync(path_1.default.join(options.sourcesRoot, 'tsconfig.monaco.json')).toString()); - let compilerOptions; - if (tsConfig.extends) { - compilerOptions = Object.assign({}, require(path_1.default.join(options.sourcesRoot, tsConfig.extends)).compilerOptions, tsConfig.compilerOptions); - delete tsConfig.extends; - } - else { - compilerOptions = tsConfig.compilerOptions; - } - tsConfig.compilerOptions = compilerOptions; - tsConfig.compilerOptions.sourceMap = true; - tsConfig.compilerOptions.module = 'es2022'; - tsConfig.compilerOptions.outDir = options.tsOutDir; - compilerOptions.noEmit = false; - compilerOptions.noUnusedLocals = false; - compilerOptions.preserveConstEnums = false; - compilerOptions.declaration = false; - compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; - options.compilerOptions = compilerOptions; - console.log(`Running tree shaker with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); - // Take the extra included .d.ts files from `tsconfig.monaco.json` - options.typings = tsConfig.include.filter(includedFile => /\.d\.ts$/.test(includedFile)); - // Add extra .d.ts files from `node_modules/@types/` - if (Array.isArray(options.compilerOptions?.types)) { - options.compilerOptions.types.forEach((type) => { - if (type === '@webgpu/types') { - options.typings.push(`../node_modules/${type}/dist/index.d.ts`); - } - else { - options.typings.push(`../node_modules/@types/${type}/index.d.ts`); - } - }); - } - const result = tss.shake(options); - for (const fileName in result) { - if (result.hasOwnProperty(fileName)) { - writeFile(path_1.default.join(options.destRoot, fileName), result[fileName]); - } - } - const copied = {}; - const copyFile = (fileName) => { - if (copied[fileName]) { - return; - } - copied[fileName] = true; - const srcPath = path_1.default.join(options.sourcesRoot, fileName); - const dstPath = path_1.default.join(options.destRoot, fileName); - writeFile(dstPath, fs_1.default.readFileSync(srcPath)); - }; - const writeOutputFile = (fileName, contents) => { - writeFile(path_1.default.join(options.destRoot, fileName), contents); - }; - for (const fileName in result) { - if (result.hasOwnProperty(fileName)) { - const fileContents = result[fileName]; - const info = ts.preProcessFile(fileContents); - for (let i = info.importedFiles.length - 1; i >= 0; i--) { - const importedFileName = info.importedFiles[i].fileName; - let importedFilePath = importedFileName; - if (/(^\.\/)|(^\.\.\/)/.test(importedFilePath)) { - importedFilePath = path_1.default.join(path_1.default.dirname(fileName), importedFilePath); - } - if (/\.css$/.test(importedFilePath)) { - transportCSS(importedFilePath, copyFile, writeOutputFile); - } - else { - const pathToCopy = path_1.default.join(options.sourcesRoot, importedFilePath); - if (fs_1.default.existsSync(pathToCopy) && !fs_1.default.statSync(pathToCopy).isDirectory()) { - copyFile(importedFilePath); - } - } - } - } - } - delete tsConfig.compilerOptions.moduleResolution; - writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); - [ - 'vs/loader.js', - 'typings/css.d.ts' - ].forEach(copyFile); -} -function transportCSS(module, enqueue, write) { - if (!/\.css/.test(module)) { - return false; - } - const filename = path_1.default.join(SRC_DIR, module); - const fileContents = fs_1.default.readFileSync(filename).toString(); - const inlineResources = 'base64'; // see https://github.com/microsoft/monaco-editor/issues/148 - const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64'); - write(module, newContents); - return true; - function _rewriteOrInlineUrls(contents, forceBase64) { - return _replaceURL(contents, (url) => { - const fontMatch = url.match(/^(.*).ttf\?(.*)$/); - if (fontMatch) { - const relativeFontPath = `${fontMatch[1]}.ttf`; // trim the query parameter - const fontPath = path_1.default.join(path_1.default.dirname(module), relativeFontPath); - enqueue(fontPath); - return relativeFontPath; - } - const imagePath = path_1.default.join(path_1.default.dirname(module), url); - const fileContents = fs_1.default.readFileSync(path_1.default.join(SRC_DIR, imagePath)); - const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; - let DATA = ';base64,' + fileContents.toString('base64'); - if (!forceBase64 && /\.svg$/.test(url)) { - // .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris - const newText = fileContents.toString() - .replace(/"/g, '\'') - .replace(//g, '%3E') - .replace(/&/g, '%26') - .replace(/#/g, '%23') - .replace(/\s+/g, ' '); - const encodedData = ',' + newText; - if (encodedData.length < DATA.length) { - DATA = encodedData; - } - } - return '"data:' + MIME + DATA + '"'; - }); - } - function _replaceURL(contents, replacer) { - // Use ")" as the terminator as quotes are oftentimes not used at all - return contents.replace(/url\(\s*([^\)]+)\s*\)?/g, (_, ...matches) => { - let url = matches[0]; - // Eliminate starting quotes (the initial whitespace is not captured) - if (url.charAt(0) === '"' || url.charAt(0) === '\'') { - url = url.substring(1); - } - // The ending whitespace is captured - while (url.length > 0 && (url.charAt(url.length - 1) === ' ' || url.charAt(url.length - 1) === '\t')) { - url = url.substring(0, url.length - 1); - } - // Eliminate ending quotes - if (url.charAt(url.length - 1) === '"' || url.charAt(url.length - 1) === '\'') { - url = url.substring(0, url.length - 1); - } - if (!_startsWith(url, 'data:') && !_startsWith(url, 'http://') && !_startsWith(url, 'https://')) { - url = replacer(url); - } - return 'url(' + url + ')'; - }); - } - function _startsWith(haystack, needle) { - return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle; - } -} -//# sourceMappingURL=standalone.js.map \ No newline at end of file diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index b18908dcb038b..3e1006fce12d5 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -5,10 +5,8 @@ import fs from 'fs'; import path from 'path'; -import * as tss from './treeshaking'; - -const REPO_ROOT = path.join(__dirname, '../../'); -const SRC_DIR = path.join(REPO_ROOT, 'src'); +import * as tss from './treeshaking.ts'; +import ts from 'typescript'; const dirCache: { [dir: string]: boolean } = {}; @@ -29,27 +27,24 @@ function writeFile(filePath: string, contents: Buffer | string): void { fs.writeFileSync(filePath, contents); } -export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: string; tsOutDir: string }): void { - const ts = require('typescript') as typeof import('typescript'); - +export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: string; tsOutDir: string; additionalFilesToCopyOut?: string[] }): void { const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.monaco.json')).toString()); let compilerOptions: { [key: string]: any }; if (tsConfig.extends) { - compilerOptions = Object.assign({}, require(path.join(options.sourcesRoot, tsConfig.extends)).compilerOptions, tsConfig.compilerOptions); + const extendedConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, tsConfig.extends)).toString()); + compilerOptions = Object.assign({}, extendedConfig.compilerOptions, tsConfig.compilerOptions); delete tsConfig.extends; } else { compilerOptions = tsConfig.compilerOptions; } tsConfig.compilerOptions = compilerOptions; tsConfig.compilerOptions.sourceMap = true; - tsConfig.compilerOptions.module = 'es2022'; tsConfig.compilerOptions.outDir = options.tsOutDir; compilerOptions.noEmit = false; compilerOptions.noUnusedLocals = false; compilerOptions.preserveConstEnums = false; compilerOptions.declaration = false; - compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; options.compilerOptions = compilerOptions; @@ -57,37 +52,41 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str console.log(`Running tree shaker with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`); // Take the extra included .d.ts files from `tsconfig.monaco.json` - options.typings = (tsConfig.include).filter(includedFile => /\.d\.ts$/.test(includedFile)); - - // Add extra .d.ts files from `node_modules/@types/` - if (Array.isArray(options.compilerOptions?.types)) { - options.compilerOptions.types.forEach((type: string) => { - if (type === '@webgpu/types') { - options.typings.push(`../node_modules/${type}/dist/index.d.ts`); - } else { - options.typings.push(`../node_modules/@types/${type}/index.d.ts`); - } - }); - } + options.typings = (tsConfig.include as string[]).filter(includedFile => /\.d\.ts$/.test(includedFile)); const result = tss.shake(options); for (const fileName in result) { if (result.hasOwnProperty(fileName)) { - writeFile(path.join(options.destRoot, fileName), result[fileName]); + let fileContents = result[fileName]; + // Replace .ts? with .js? in new URL() patterns + fileContents = fileContents.replace( + /(new\s+URL\s*\(\s*['"`][^'"`]*?)\.ts(\?[^'"`]*['"`])/g, + '$1.js$2' + ); + const relativePath = path.relative(options.sourcesRoot, fileName); + writeFile(path.join(options.destRoot, relativePath), fileContents); } } const copied: { [fileName: string]: boolean } = {}; - const copyFile = (fileName: string) => { + const copyFile = (fileName: string, toFileName?: string) => { if (copied[fileName]) { return; } copied[fileName] = true; - const srcPath = path.join(options.sourcesRoot, fileName); - const dstPath = path.join(options.destRoot, fileName); - writeFile(dstPath, fs.readFileSync(srcPath)); + + if (path.isAbsolute(fileName)) { + const relativePath = path.relative(options.sourcesRoot, fileName); + const dstPath = path.join(options.destRoot, toFileName ?? relativePath); + writeFile(dstPath, fs.readFileSync(fileName)); + } else { + const srcPath = path.join(options.sourcesRoot, fileName); + const dstPath = path.join(options.destRoot, toFileName ?? fileName); + writeFile(dstPath, fs.readFileSync(srcPath)); + } }; const writeOutputFile = (fileName: string, contents: string | Buffer) => { - writeFile(path.join(options.destRoot, fileName), contents); + const relativePath = path.isAbsolute(fileName) ? path.relative(options.sourcesRoot, fileName) : fileName; + writeFile(path.join(options.destRoot, relativePath), contents); }; for (const fileName in result) { if (result.hasOwnProperty(fileName)) { @@ -117,10 +116,13 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str delete tsConfig.compilerOptions.moduleResolution; writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); - [ - 'vs/loader.js', - 'typings/css.d.ts' - ].forEach(copyFile); + options.additionalFilesToCopyOut?.forEach((file) => { + copyFile(file); + }); + + copyFile('vs/loader.js'); + copyFile('typings/css.d.ts'); + copyFile('../node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter.d.ts', '@vscode/tree-sitter-wasm.d.ts'); } function transportCSS(module: string, enqueue: (module: string) => void, write: (path: string, contents: string | Buffer) => void): boolean { @@ -129,8 +131,7 @@ function transportCSS(module: string, enqueue: (module: string) => void, write: return false; } - const filename = path.join(SRC_DIR, module); - const fileContents = fs.readFileSync(filename).toString(); + const fileContents = fs.readFileSync(module).toString(); const inlineResources = 'base64'; // see https://github.com/microsoft/monaco-editor/issues/148 const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64'); @@ -148,7 +149,7 @@ function transportCSS(module: string, enqueue: (module: string) => void, write: } const imagePath = path.join(path.dirname(module), url); - const fileContents = fs.readFileSync(path.join(SRC_DIR, imagePath)); + const fileContents = fs.readFileSync(imagePath); const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; let DATA = ';base64,' + fileContents.toString('base64'); diff --git a/build/lib/stats.js b/build/lib/stats.js deleted file mode 100644 index 3f6d953ae4073..0000000000000 --- a/build/lib/stats.js +++ /dev/null @@ -1,79 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.createStatsStream = createStatsStream; -const event_stream_1 = __importDefault(require("event-stream")); -const fancy_log_1 = __importDefault(require("fancy-log")); -const ansi_colors_1 = __importDefault(require("ansi-colors")); -class Entry { - name; - totalCount; - totalSize; - constructor(name, totalCount, totalSize) { - this.name = name; - this.totalCount = totalCount; - this.totalSize = totalSize; - } - toString(pretty) { - if (!pretty) { - if (this.totalCount === 1) { - return `${this.name}: ${this.totalSize} bytes`; - } - else { - return `${this.name}: ${this.totalCount} files with ${this.totalSize} bytes`; - } - } - else { - if (this.totalCount === 1) { - return `Stats for '${ansi_colors_1.default.grey(this.name)}': ${Math.round(this.totalSize / 1204)}KB`; - } - else { - const count = this.totalCount < 100 - ? ansi_colors_1.default.green(this.totalCount.toString()) - : ansi_colors_1.default.red(this.totalCount.toString()); - return `Stats for '${ansi_colors_1.default.grey(this.name)}': ${count} files, ${Math.round(this.totalSize / 1204)}KB`; - } - } - } -} -const _entries = new Map(); -function createStatsStream(group, log) { - const entry = new Entry(group, 0, 0); - _entries.set(entry.name, entry); - return event_stream_1.default.through(function (data) { - const file = data; - if (typeof file.path === 'string') { - entry.totalCount += 1; - if (Buffer.isBuffer(file.contents)) { - entry.totalSize += file.contents.length; - } - else if (file.stat && typeof file.stat.size === 'number') { - entry.totalSize += file.stat.size; - } - else { - // funky file... - } - } - this.emit('data', data); - }, function () { - if (log) { - if (entry.totalCount === 1) { - (0, fancy_log_1.default)(`Stats for '${ansi_colors_1.default.grey(entry.name)}': ${Math.round(entry.totalSize / 1204)}KB`); - } - else { - const count = entry.totalCount < 100 - ? ansi_colors_1.default.green(entry.totalCount.toString()) - : ansi_colors_1.default.red(entry.totalCount.toString()); - (0, fancy_log_1.default)(`Stats for '${ansi_colors_1.default.grey(entry.name)}': ${count} files, ${Math.round(entry.totalSize / 1204)}KB`); - } - } - this.emit('end'); - }); -} -//# sourceMappingURL=stats.js.map \ No newline at end of file diff --git a/build/lib/stats.ts b/build/lib/stats.ts index 8db55d3e77749..83bf0a4a7ae6a 100644 --- a/build/lib/stats.ts +++ b/build/lib/stats.ts @@ -9,7 +9,15 @@ import ansiColors from 'ansi-colors'; import File from 'vinyl'; class Entry { - constructor(readonly name: string, public totalCount: number, public totalSize: number) { } + readonly name: string; + public totalCount: number; + public totalSize: number; + + constructor(name: string, totalCount: number, totalSize: number) { + this.name = name; + this.totalCount = totalCount; + this.totalSize = totalSize; + } toString(pretty?: boolean): string { if (!pretty) { diff --git a/build/lib/stylelint/validateVariableNames.js b/build/lib/stylelint/validateVariableNames.js deleted file mode 100644 index b0e064e7b561e..0000000000000 --- a/build/lib/stylelint/validateVariableNames.js +++ /dev/null @@ -1,37 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getVariableNameValidator = getVariableNameValidator; -const fs_1 = require("fs"); -const path_1 = __importDefault(require("path")); -const RE_VAR_PROP = /var\(\s*(--([\w\-\.]+))/g; -let knownVariables; -function getKnownVariableNames() { - if (!knownVariables) { - const knownVariablesFileContent = (0, fs_1.readFileSync)(path_1.default.join(__dirname, './vscode-known-variables.json'), 'utf8').toString(); - const knownVariablesInfo = JSON.parse(knownVariablesFileContent); - knownVariables = new Set([...knownVariablesInfo.colors, ...knownVariablesInfo.others]); - } - return knownVariables; -} -const iconVariable = /^--vscode-icon-.+-(content|font-family)$/; -function getVariableNameValidator() { - const allVariables = getKnownVariableNames(); - return (value, report) => { - RE_VAR_PROP.lastIndex = 0; // reset lastIndex just to be sure - let match; - while (match = RE_VAR_PROP.exec(value)) { - const variableName = match[1]; - if (variableName && !allVariables.has(variableName) && !iconVariable.test(variableName)) { - report(variableName); - } - } - }; -} -//# sourceMappingURL=validateVariableNames.js.map \ No newline at end of file diff --git a/build/lib/stylelint/validateVariableNames.ts b/build/lib/stylelint/validateVariableNames.ts index b28aed13f4b94..3cab12ac98d81 100644 --- a/build/lib/stylelint/validateVariableNames.ts +++ b/build/lib/stylelint/validateVariableNames.ts @@ -11,9 +11,9 @@ const RE_VAR_PROP = /var\(\s*(--([\w\-\.]+))/g; let knownVariables: Set | undefined; function getKnownVariableNames() { if (!knownVariables) { - const knownVariablesFileContent = readFileSync(path.join(__dirname, './vscode-known-variables.json'), 'utf8').toString(); + const knownVariablesFileContent = readFileSync(path.join(import.meta.dirname, './vscode-known-variables.json'), 'utf8').toString(); const knownVariablesInfo = JSON.parse(knownVariablesFileContent); - knownVariables = new Set([...knownVariablesInfo.colors, ...knownVariablesInfo.others] as string[]); + knownVariables = new Set([...knownVariablesInfo.colors, ...knownVariablesInfo.others, ...(knownVariablesInfo.sizes || [])] as string[]); } return knownVariables; } diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index ecc408c0447d8..8f6ce9b030d5a 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -21,6 +21,9 @@ "--vscode-activityErrorBadge-foreground", "--vscode-activityWarningBadge-background", "--vscode-activityWarningBadge-foreground", + "--vscode-agentSessionReadIndicator-foreground", + "--vscode-agentSessionSelectedBadge-border", + "--vscode-agentSessionSelectedUnfocusedBadge-border", "--vscode-badge-background", "--vscode-badge-foreground", "--vscode-banner-background", @@ -63,6 +66,7 @@ "--vscode-chat-requestCodeBorder", "--vscode-chat-slashCommandBackground", "--vscode-chat-slashCommandForeground", + "--vscode-chatManagement-sashBorder", "--vscode-checkbox-background", "--vscode-checkbox-border", "--vscode-checkbox-disabled-background", @@ -158,6 +162,7 @@ "--vscode-editor-foldPlaceholderForeground", "--vscode-editor-foreground", "--vscode-editor-hoverHighlightBackground", + "--vscode-editor-inactiveLineHighlightBackground", "--vscode-editor-inactiveSelectionBackground", "--vscode-editor-inlineValuesBackground", "--vscode-editor-inlineValuesForeground", @@ -238,6 +243,7 @@ "--vscode-editorGutter-addedBackground", "--vscode-editorGutter-addedSecondaryBackground", "--vscode-editorGutter-background", + "--vscode-editorGutter-commentDraftGlyphForeground", "--vscode-editorGutter-commentGlyphForeground", "--vscode-editorGutter-commentRangeForeground", "--vscode-editorGutter-commentUnresolvedGlyphForeground", @@ -301,6 +307,7 @@ "--vscode-editorOverviewRuler-background", "--vscode-editorOverviewRuler-border", "--vscode-editorOverviewRuler-bracketMatchForeground", + "--vscode-editorOverviewRuler-commentDraftForeground", "--vscode-editorOverviewRuler-commentForeground", "--vscode-editorOverviewRuler-commentUnresolvedForeground", "--vscode-editorOverviewRuler-commonContentForeground", @@ -342,7 +349,6 @@ "--vscode-editorWarning-background", "--vscode-editorWarning-border", "--vscode-editorWarning-foreground", - "--vscode-editorWatermark-foreground", "--vscode-editorWhitespace-foreground", "--vscode-editorWidget-background", "--vscode-editorWidget-border", @@ -376,6 +382,7 @@ "--vscode-inlineChat-background", "--vscode-inlineChat-border", "--vscode-inlineChat-foreground", + "--vscode-inlineChat-regionHighlight", "--vscode-inlineChat-shadow", "--vscode-inlineChatDiff-inserted", "--vscode-inlineChatDiff-removed", @@ -456,6 +463,12 @@ "--vscode-listFilterWidget-noMatchesOutline", "--vscode-listFilterWidget-outline", "--vscode-listFilterWidget-shadow", + "--vscode-markdownAlert-caution-foreground", + "--vscode-markdownAlert-important-foreground", + "--vscode-markdownAlert-note-foreground", + "--vscode-markdownAlert-tip-foreground", + "--vscode-markdownAlert-warning-foreground", + "--vscode-mcpIcon-starForeground", "--vscode-menu-background", "--vscode-menu-border", "--vscode-menu-foreground", @@ -613,6 +626,7 @@ "--vscode-scmGraph-historyItemHoverLabelForeground", "--vscode-scmGraph-historyItemRefColor", "--vscode-scmGraph-historyItemRemoteRefColor", + "--vscode-scrollbar-background", "--vscode-scrollbar-shadow", "--vscode-scrollbarSlider-activeBackground", "--vscode-scrollbarSlider-background", @@ -800,6 +814,8 @@ "--vscode-terminalStickyScrollHover-background", "--vscode-terminalSymbolIcon-aliasForeground", "--vscode-terminalSymbolIcon-argumentForeground", + "--vscode-terminalSymbolIcon-branchForeground", + "--vscode-terminalSymbolIcon-commitForeground", "--vscode-terminalSymbolIcon-fileForeground", "--vscode-terminalSymbolIcon-flagForeground", "--vscode-terminalSymbolIcon-folderForeground", @@ -807,8 +823,14 @@ "--vscode-terminalSymbolIcon-methodForeground", "--vscode-terminalSymbolIcon-optionForeground", "--vscode-terminalSymbolIcon-optionValueForeground", + "--vscode-terminalSymbolIcon-pullRequestDoneForeground", + "--vscode-terminalSymbolIcon-pullRequestForeground", + "--vscode-terminalSymbolIcon-remoteForeground", + "--vscode-terminalSymbolIcon-stashForeground", + "--vscode-terminalSymbolIcon-symbolText", "--vscode-terminalSymbolIcon-symbolicLinkFileForeground", "--vscode-terminalSymbolIcon-symbolicLinkFolderForeground", + "--vscode-terminalSymbolIcon-tagForeground", "--vscode-testing-coverCountBadgeBackground", "--vscode-testing-coverCountBadgeForeground", "--vscode-testing-coveredBackground", @@ -847,6 +869,7 @@ "--vscode-textLink-activeForeground", "--vscode-textLink-foreground", "--vscode-textPreformat-background", + "--vscode-textPreformat-border", "--vscode-textPreformat-foreground", "--vscode-textSeparator-foreground", "--vscode-titleBar-activeBackground", @@ -879,10 +902,7 @@ "--background-light", "--chat-editing-last-edit-shift", "--chat-current-response-min-height", - "--dropdown-padding-bottom", - "--dropdown-padding-top", "--inline-chat-frame-progress", - "--inline-chat-hint-progress", "--insert-border-color", "--last-tab-margin-right", "--monaco-monospace-font", @@ -963,6 +983,31 @@ "--vscode-action-item-auto-timeout", "--monaco-editor-warning-decoration", "--animation-opacity", - "--chat-setup-dialog-glow-angle" + "--chat-setup-dialog-glow-angle", + "--vscode-chat-font-family", + "--vscode-chat-font-size-body-l", + "--vscode-chat-font-size-body-m", + "--vscode-chat-font-size-body-s", + "--vscode-chat-font-size-body-xl", + "--vscode-chat-font-size-body-xs", + "--vscode-chat-font-size-body-xxl", + "--comment-thread-editor-font-family", + "--comment-thread-editor-font-weight", + "--comment-thread-state-color", + "--comment-thread-state-background-color", + "--inline-edit-border-radius" + ], + "sizes": [ + "--vscode-bodyFontSize", + "--vscode-bodyFontSize-small", + "--vscode-bodyFontSize-xSmall", + "--vscode-codiconFontSize", + "--vscode-cornerRadius-circle", + "--vscode-cornerRadius-large", + "--vscode-cornerRadius-medium", + "--vscode-cornerRadius-small", + "--vscode-cornerRadius-xLarge", + "--vscode-cornerRadius-xSmall", + "--vscode-strokeThickness" ] -} \ No newline at end of file +} diff --git a/build/lib/task.js b/build/lib/task.js deleted file mode 100644 index 6887714681af7..0000000000000 --- a/build/lib/task.js +++ /dev/null @@ -1,100 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.series = series; -exports.parallel = parallel; -exports.define = define; -const fancy_log_1 = __importDefault(require("fancy-log")); -const ansi_colors_1 = __importDefault(require("ansi-colors")); -function _isPromise(p) { - if (typeof p.then === 'function') { - return true; - } - return false; -} -function _renderTime(time) { - return `${Math.round(time)} ms`; -} -async function _execute(task) { - const name = task.taskName || task.displayName || ``; - if (!task._tasks) { - (0, fancy_log_1.default)('Starting', ansi_colors_1.default.cyan(name), '...'); - } - const startTime = process.hrtime(); - await _doExecute(task); - const elapsedArr = process.hrtime(startTime); - const elapsedNanoseconds = (elapsedArr[0] * 1e9 + elapsedArr[1]); - if (!task._tasks) { - (0, fancy_log_1.default)(`Finished`, ansi_colors_1.default.cyan(name), 'after', ansi_colors_1.default.magenta(_renderTime(elapsedNanoseconds / 1e6))); - } -} -async function _doExecute(task) { - // Always invoke as if it were a callback task - return new Promise((resolve, reject) => { - if (task.length === 1) { - // this is a callback task - task((err) => { - if (err) { - return reject(err); - } - resolve(); - }); - return; - } - const taskResult = task(); - if (typeof taskResult === 'undefined') { - // this is a sync task - resolve(); - return; - } - if (_isPromise(taskResult)) { - // this is a promise returning task - taskResult.then(resolve, reject); - return; - } - // this is a stream returning task - taskResult.on('end', _ => resolve()); - taskResult.on('error', err => reject(err)); - }); -} -function series(...tasks) { - const result = async () => { - for (let i = 0; i < tasks.length; i++) { - await _execute(tasks[i]); - } - }; - result._tasks = tasks; - return result; -} -function parallel(...tasks) { - const result = async () => { - await Promise.all(tasks.map(t => _execute(t))); - }; - result._tasks = tasks; - return result; -} -function define(name, task) { - if (task._tasks) { - // This is a composite task - const lastTask = task._tasks[task._tasks.length - 1]; - if (lastTask._tasks || lastTask.taskName) { - // This is a composite task without a real task function - // => generate a fake task function - return define(name, series(task, () => Promise.resolve())); - } - lastTask.taskName = name; - task.displayName = name; - return task; - } - // This is a simple task - task.taskName = name; - task.displayName = name; - return task; -} -//# sourceMappingURL=task.js.map \ No newline at end of file diff --git a/build/lib/task.ts b/build/lib/task.ts index 6af2398317850..085843a93b73a 100644 --- a/build/lib/task.ts +++ b/build/lib/task.ts @@ -18,16 +18,13 @@ export interface StreamTask extends BaseTask { (): NodeJS.ReadWriteStream; } export interface CallbackTask extends BaseTask { - (cb?: (err?: any) => void): void; + (cb?: (err?: Error) => void): void; } export type Task = PromiseTask | StreamTask | CallbackTask; function _isPromise(p: Promise | NodeJS.ReadWriteStream): p is Promise { - if (typeof (p).then === 'function') { - return true; - } - return false; + return typeof (p as Promise).then === 'function'; } function _renderTime(time: number): string { diff --git a/build/lib/test/booleanPolicy.test.ts b/build/lib/test/booleanPolicy.test.ts new file mode 100644 index 0000000000000..d64f9fff64636 --- /dev/null +++ b/build/lib/test/booleanPolicy.test.ts @@ -0,0 +1,168 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { BooleanPolicy } from '../policies/booleanPolicy.ts'; +import { type LanguageTranslations, PolicyType } from '../policies/types.ts'; +import type { CategoryDto, PolicyDto } from '../policies/policyDto.ts'; + +suite('BooleanPolicy', () => { + const mockCategory: CategoryDto = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + + const mockPolicy: PolicyDto = { + key: 'test.boolean.policy', + name: 'TestBooleanPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'boolean', + localization: { + description: { key: 'test.policy.description', value: 'Test policy description' } + } + }; + + test('should create BooleanPolicy from factory method', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + assert.strictEqual(policy.name, 'TestBooleanPolicy'); + assert.strictEqual(policy.minimumVersion, '1.0'); + assert.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert.strictEqual(policy.category.name.value, mockCategory.name.value); + assert.strictEqual(policy.type, PolicyType.Boolean); + }); + + test('should render ADMX elements correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admx = policy.renderADMX('TestKey'); + + assert.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\t', + '', + '\t', + '' + ]); + }); + + test('should render ADML strings correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admlStrings = policy.renderADMLStrings(); + + assert.deepStrictEqual(admlStrings, [ + 'TestBooleanPolicy', + 'Test policy description' + ]); + }); + + test('should render ADML strings with translations', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated description' + } + }; + + const admlStrings = policy.renderADMLStrings(translations); + + assert.deepStrictEqual(admlStrings, [ + 'TestBooleanPolicy', + 'Translated description' + ]); + }); + + test('should render ADML presentation correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const presentation = policy.renderADMLPresentation(); + + assert.strictEqual(presentation, 'TestBooleanPolicy'); + }); + + test('should render JSON value correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const jsonValue = policy.renderJsonValue(); + + assert.strictEqual(jsonValue, false); + }); + + test('should render profile value correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profileValue = policy.renderProfileValue(); + + assert.strictEqual(profileValue, ''); + }); + + test('should render profile correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profile = policy.renderProfile(); + + assert.strictEqual(profile.length, 2); + assert.strictEqual(profile[0], 'TestBooleanPolicy'); + assert.strictEqual(profile[1], ''); + }); + + test('should render profile manifest value correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifestValue = policy.renderProfileManifestValue(); + + assert.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTest policy description\npfm_name\nTestBooleanPolicy\npfm_title\nTestBooleanPolicy\npfm_type\nboolean'); + }); + + test('should render profile manifest value with translations', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + + const manifestValue = policy.renderProfileManifestValue(translations); + + assert.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTranslated manifest description\npfm_name\nTestBooleanPolicy\npfm_title\nTestBooleanPolicy\npfm_type\nboolean'); + }); + + test('should render profile manifest correctly', () => { + const policy = BooleanPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifest = policy.renderProfileManifest(); + + assert.strictEqual(manifest, '\npfm_default\n\npfm_description\nTest policy description\npfm_name\nTestBooleanPolicy\npfm_title\nTestBooleanPolicy\npfm_type\nboolean\n'); + }); +}); diff --git a/build/lib/test/fixtures/policies/darwin/com.visualstudio.code.oss.mobileconfig b/build/lib/test/fixtures/policies/darwin/com.visualstudio.code.oss.mobileconfig new file mode 100644 index 0000000000000..0f0ae365d7c99 --- /dev/null +++ b/build/lib/test/fixtures/policies/darwin/com.visualstudio.code.oss.mobileconfig @@ -0,0 +1,61 @@ + + + + + PayloadContent + + + PayloadDisplayName + Code - OSS + PayloadIdentifier + com.visualstudio.code.oss.47827DD9-4734-49A0-AF80-7E19B11495CC + PayloadType + com.visualstudio.code.oss + PayloadUUID + 47827DD9-4734-49A0-AF80-7E19B11495CC + PayloadVersion + 1 + ChatAgentExtensionTools + + ChatAgentMode + + ChatMCP + none + ChatPromptFiles + + ChatToolsAutoApprove + + McpGalleryServiceUrl + + AllowedExtensions + + ExtensionGalleryServiceUrl + + ChatToolsTerminalEnableAutoApprove + + EnableFeedback + + TelemetryLevel + all + UpdateMode + none + + + PayloadDescription + This profile manages Code - OSS. For more information see https://code.visualstudio.com/docs/setup/enterprise + PayloadDisplayName + Code - OSS + PayloadIdentifier + com.visualstudio.code.oss + PayloadOrganization + Microsoft + PayloadType + Configuration + PayloadUUID + CF808BE7-53F3-46C6-A7E2-7EDB98A5E959 + PayloadVersion + 1 + TargetDeviceType + 5 + + \ No newline at end of file diff --git a/build/lib/test/fixtures/policies/darwin/en-us/com.visualstudio.code.oss.plist b/build/lib/test/fixtures/policies/darwin/en-us/com.visualstudio.code.oss.plist new file mode 100644 index 0000000000000..f90533cfeaaeb --- /dev/null +++ b/build/lib/test/fixtures/policies/darwin/en-us/com.visualstudio.code.oss.plist @@ -0,0 +1,274 @@ + + + + + pfm_app_url + https://code.visualstudio.com/ + pfm_description + Code - OSS Managed Settings + pfm_documentation_url + https://code.visualstudio.com/docs/setup/enterprise + pfm_domain + com.visualstudio.code.oss + pfm_format_version + 1 + pfm_interaction + combined + pfm_last_modified + 2025-10-21T23:04:34Z + pfm_platforms + + macOS + + pfm_subkeys + + + + pfm_default + Configure Code - OSS + pfm_name + PayloadDescription + pfm_title + Payload Description + pfm_type + string + + + pfm_default + Code - OSS + pfm_name + PayloadDisplayName + pfm_require + always + pfm_title + Payload Display Name + pfm_type + string + + + pfm_default + com.visualstudio.code.oss + pfm_name + PayloadIdentifier + pfm_require + always + pfm_title + Payload Identifier + pfm_type + string + + + pfm_default + com.visualstudio.code.oss + pfm_name + PayloadType + pfm_require + always + pfm_title + Payload Type + pfm_type + string + + + pfm_default + + pfm_name + PayloadUUID + pfm_require + always + pfm_title + Payload UUID + pfm_type + string + + + pfm_default + 1 + pfm_name + PayloadVersion + pfm_range_list + + 1 + + pfm_require + always + pfm_title + Payload Version + pfm_type + integer + + + pfm_default + Microsoft + pfm_name + PayloadOrganization + pfm_title + Payload Organization + pfm_type + string + + +pfm_default + +pfm_description +Enable using tools contributed by third-party extensions. +pfm_name +ChatAgentExtensionTools +pfm_title +ChatAgentExtensionTools +pfm_type +boolean + +pfm_default + +pfm_description +Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view. +pfm_name +ChatAgentMode +pfm_title +ChatAgentMode +pfm_type +boolean + +pfm_default +none +pfm_description +Controls access to installed Model Context Protocol servers. +pfm_name +ChatMCP +pfm_title +ChatMCP +pfm_type +string +pfm_range_list + + none + registry + all + + +pfm_default + +pfm_description +Enables reusable prompt and instruction files in Chat sessions. +pfm_name +ChatPromptFiles +pfm_title +ChatPromptFiles +pfm_type +boolean + +pfm_default + +pfm_description +Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised. + +This feature disables critical security protections and makes it much easier for an attacker to compromise the machine. +pfm_name +ChatToolsAutoApprove +pfm_title +ChatToolsAutoApprove +pfm_type +boolean + +pfm_default + +pfm_description +Configure the MCP Gallery service URL to connect to +pfm_name +McpGalleryServiceUrl +pfm_title +McpGalleryServiceUrl +pfm_type +string + +pfm_default + +pfm_description +Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions +pfm_name +AllowedExtensions +pfm_title +AllowedExtensions +pfm_type +string + + +pfm_default + +pfm_description +Configure the Marketplace service URL to connect to +pfm_name +ExtensionGalleryServiceUrl +pfm_title +ExtensionGalleryServiceUrl +pfm_type +string + +pfm_default + +pfm_description +Controls whether to allow auto approval in the run in terminal tool. +pfm_name +ChatToolsTerminalEnableAutoApprove +pfm_title +ChatToolsTerminalEnableAutoApprove +pfm_type +boolean + +pfm_default + +pfm_description +Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options. +pfm_name +EnableFeedback +pfm_title +EnableFeedback +pfm_type +boolean + +pfm_default +all +pfm_description +Controls the level of telemetry. +pfm_name +TelemetryLevel +pfm_title +TelemetryLevel +pfm_type +string +pfm_range_list + + all + error + crash + off + + +pfm_default +none +pfm_description +Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service. +pfm_name +UpdateMode +pfm_title +UpdateMode +pfm_type +string +pfm_range_list + + none + manual + start + default + + + + pfm_title + Code - OSS + pfm_unique + + pfm_version + 1 + + \ No newline at end of file diff --git a/build/lib/test/fixtures/policies/darwin/fr-fr/com.visualstudio.code.oss.plist b/build/lib/test/fixtures/policies/darwin/fr-fr/com.visualstudio.code.oss.plist new file mode 100644 index 0000000000000..60f244be3b512 --- /dev/null +++ b/build/lib/test/fixtures/policies/darwin/fr-fr/com.visualstudio.code.oss.plist @@ -0,0 +1,274 @@ + + + + + pfm_app_url + https://code.visualstudio.com/ + pfm_description + Code - OSS Managed Settings + pfm_documentation_url + https://code.visualstudio.com/docs/setup/enterprise + pfm_domain + com.visualstudio.code.oss + pfm_format_version + 1 + pfm_interaction + combined + pfm_last_modified + 2025-10-21T23:04:34Z + pfm_platforms + + macOS + + pfm_subkeys + + + + pfm_default + Configure Code - OSS + pfm_name + PayloadDescription + pfm_title + Payload Description + pfm_type + string + + + pfm_default + Code - OSS + pfm_name + PayloadDisplayName + pfm_require + always + pfm_title + Payload Display Name + pfm_type + string + + + pfm_default + com.visualstudio.code.oss + pfm_name + PayloadIdentifier + pfm_require + always + pfm_title + Payload Identifier + pfm_type + string + + + pfm_default + com.visualstudio.code.oss + pfm_name + PayloadType + pfm_require + always + pfm_title + Payload Type + pfm_type + string + + + pfm_default + + pfm_name + PayloadUUID + pfm_require + always + pfm_title + Payload UUID + pfm_type + string + + + pfm_default + 1 + pfm_name + PayloadVersion + pfm_range_list + + 1 + + pfm_require + always + pfm_title + Payload Version + pfm_type + integer + + + pfm_default + Microsoft + pfm_name + PayloadOrganization + pfm_title + Payload Organization + pfm_type + string + + +pfm_default + +pfm_description +Autorisez l’utilisation d’outils fournis par des extensions tierces. +pfm_name +ChatAgentExtensionTools +pfm_title +ChatAgentExtensionTools +pfm_type +boolean + +pfm_default + +pfm_description +Activez le mode Assistant pour la conversation. Lorsque cette option est activée, le mode Assistant peut être activé via la liste déroulante de la vue. +pfm_name +ChatAgentMode +pfm_title +ChatAgentMode +pfm_type +boolean + +pfm_default +none +pfm_description +Contrôle l’accès aux serveurs de protocole de contexte du modèle. +pfm_name +ChatMCP +pfm_title +ChatMCP +pfm_type +string +pfm_range_list + + none + registry + all + + +pfm_default + +pfm_description +Active les fichiers d’instruction et de requête réutilisables dans les sessions Conversation. +pfm_name +ChatPromptFiles +pfm_title +ChatPromptFiles +pfm_type +boolean + +pfm_default + +pfm_description +L’approbation automatique globale, également appelée « mode YOLO », désactive complètement l’approbation manuelle pour tous les outils dans tous les espaces de travail, permettant à l’agent d’agir de manière totalement autonome. Ceci est extrêmement dangereux et est *jamais* recommandé, même dans des environnements conteneurisés comme [Codespaces](https://github.com/features/codespaces) et [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), où des clés utilisateur sont transférées dans le conteneur et pourraient être compromises. + +Cette fonctionnalité désactive [les protections de sécurité critiques](https://code.visualstudio.com/docs/copilot/security) et facilite considérablement la compromission de la machine par un attaquant. +pfm_name +ChatToolsAutoApprove +pfm_title +ChatToolsAutoApprove +pfm_type +boolean + +pfm_default + +pfm_description +Configurer l’URL du service de la galerie MCP à laquelle se connecter +pfm_name +McpGalleryServiceUrl +pfm_title +McpGalleryServiceUrl +pfm_type +string + +pfm_default + +pfm_description +Spécifiez une liste d’extensions autorisées. Cela permet de maintenir un environnement de développement sécurisé et cohérent en limitant l’utilisation d’extensions non autorisées. Plus d’informations : https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions +pfm_name +AllowedExtensions +pfm_title +AllowedExtensions +pfm_type +string + + +pfm_default + +pfm_description +Configurer l’URL du service Place de marché à laquelle se connecter +pfm_name +ExtensionGalleryServiceUrl +pfm_title +ExtensionGalleryServiceUrl +pfm_type +string + +pfm_default + +pfm_description +Contrôle s’il faut autoriser l’approbation automatique lors de l’exécution dans l’outil terminal. +pfm_name +ChatToolsTerminalEnableAutoApprove +pfm_title +ChatToolsTerminalEnableAutoApprove +pfm_type +boolean + +pfm_default + +pfm_description +Activez les mécanismes de commentaires tels que le système de rapport de problèmes, les sondages et autres options de commentaires. +pfm_name +EnableFeedback +pfm_title +EnableFeedback +pfm_type +boolean + +pfm_default +all +pfm_description +Contrôle le niveau de télémétrie. +pfm_name +TelemetryLevel +pfm_title +TelemetryLevel +pfm_type +string +pfm_range_list + + all + error + crash + off + + +pfm_default +none +pfm_description +Choisissez si vous voulez recevoir des mises à jour automatiques. Nécessite un redémarrage après le changement. Les mises à jour sont récupérées auprès d'un service en ligne Microsoft. +pfm_name +UpdateMode +pfm_title +UpdateMode +pfm_type +string +pfm_range_list + + none + manual + start + default + + + + pfm_title + Code - OSS + pfm_unique + + pfm_version + 1 + + \ No newline at end of file diff --git a/build/lib/test/fixtures/policies/linux/policy.json b/build/lib/test/fixtures/policies/linux/policy.json new file mode 100644 index 0000000000000..3a941999da6b1 --- /dev/null +++ b/build/lib/test/fixtures/policies/linux/policy.json @@ -0,0 +1,14 @@ +{ + "ChatAgentExtensionTools": false, + "ChatAgentMode": false, + "ChatMCP": "none", + "ChatPromptFiles": false, + "ChatToolsAutoApprove": false, + "McpGalleryServiceUrl": "", + "AllowedExtensions": "", + "ExtensionGalleryServiceUrl": "", + "ChatToolsTerminalEnableAutoApprove": false, + "EnableFeedback": false, + "TelemetryLevel": "all", + "UpdateMode": "none" +} \ No newline at end of file diff --git a/build/lib/test/fixtures/policies/win32/CodeOSS.admx b/build/lib/test/fixtures/policies/win32/CodeOSS.admx new file mode 100644 index 0000000000000..c78eaebaf28d2 --- /dev/null +++ b/build/lib/test/fixtures/policies/win32/CodeOSS.admx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + none + registry + all + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + error + crash + off + + + + + + + + + none + manual + start + default + + + + + diff --git a/build/lib/test/fixtures/policies/win32/en-us/CodeOSS.adml b/build/lib/test/fixtures/policies/win32/en-us/CodeOSS.adml new file mode 100644 index 0000000000000..39b05ddc72db2 --- /dev/null +++ b/build/lib/test/fixtures/policies/win32/en-us/CodeOSS.adml @@ -0,0 +1,71 @@ + + + + + + + Code - OSS + Code - OSS >= 1.101 + Code - OSS >= 1.104 + Code - OSS >= 1.67 + Code - OSS >= 1.96 + Code - OSS >= 1.99 + Chat + Extensions + Integrated Terminal + Telemetry + Update + ChatAgentExtensionTools + Enable using tools contributed by third-party extensions. + ChatAgentMode + Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view. + ChatMCP + Controls access to installed Model Context Protocol servers. + No access to MCP servers. + Allows access to MCP servers installed from the registry that VS Code is connected to. + Allow access to any installed MCP server. + ChatPromptFiles + Enables reusable prompt and instruction files in Chat sessions. + ChatToolsAutoApprove + Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised. + +This feature disables critical security protections and makes it much easier for an attacker to compromise the machine. + McpGalleryServiceUrl + Configure the MCP Gallery service URL to connect to + AllowedExtensions + Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions + ExtensionGalleryServiceUrl + Configure the Marketplace service URL to connect to + ChatToolsTerminalEnableAutoApprove + Controls whether to allow auto approval in the run in terminal tool. + EnableFeedback + Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options. + TelemetryLevel + Controls the level of telemetry. + Sends usage data, errors, and crash reports. + Sends general error telemetry and crash reports. + Sends OS level crash reports. + Disables all product telemetry. + UpdateMode + Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service. + Disable updates. + Disable automatic background update checks. Updates will be available if you manually check for updates. + Check for updates only on startup. Disable automatic background update checks. + Enable automatic update checks. Code will check for updates automatically and periodically. + + + ChatAgentExtensionTools + ChatAgentMode + + ChatPromptFiles + ChatToolsAutoApprove + + + + ChatToolsTerminalEnableAutoApprove + EnableFeedback + + + + + diff --git a/build/lib/test/fixtures/policies/win32/fr-fr/CodeOSS.adml b/build/lib/test/fixtures/policies/win32/fr-fr/CodeOSS.adml new file mode 100644 index 0000000000000..eab50282b2dec --- /dev/null +++ b/build/lib/test/fixtures/policies/win32/fr-fr/CodeOSS.adml @@ -0,0 +1,71 @@ + + + + + + + Code - OSS + Code - OSS >= 1.101 + Code - OSS >= 1.104 + Code - OSS >= 1.67 + Code - OSS >= 1.96 + Code - OSS >= 1.99 + Session interactive + Extensions + Terminal intégré + Télémétrie + Mettre à jour + ChatAgentExtensionTools + Autorisez l’utilisation d’outils fournis par des extensions tierces. + ChatAgentMode + Activez le mode Assistant pour la conversation. Lorsque cette option est activée, le mode Assistant peut être activé via la liste déroulante de la vue. + ChatMCP + Contrôle l’accès aux serveurs de protocole de contexte du modèle. + Aucun accès aux serveurs MCP. + Autorise l’accès aux serveurs MCP installés à partir du registre auquel VS Code est connecté. + Autorisez l’accès à tout serveur MCP installé. + ChatPromptFiles + Active les fichiers d’instruction et de requête réutilisables dans les sessions Conversation. + ChatToolsAutoApprove + L’approbation automatique globale, également appelée « mode YOLO », désactive complètement l’approbation manuelle pour tous les outils dans tous les espaces de travail, permettant à l’agent d’agir de manière totalement autonome. Ceci est extrêmement dangereux et est *jamais* recommandé, même dans des environnements conteneurisés comme [Codespaces](https://github.com/features/codespaces) et [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), où des clés utilisateur sont transférées dans le conteneur et pourraient être compromises. + +Cette fonctionnalité désactive [les protections de sécurité critiques](https://code.visualstudio.com/docs/copilot/security) et facilite considérablement la compromission de la machine par un attaquant. + McpGalleryServiceUrl + Configurer l’URL du service de la galerie MCP à laquelle se connecter + AllowedExtensions + Spécifiez une liste d’extensions autorisées. Cela permet de maintenir un environnement de développement sécurisé et cohérent en limitant l’utilisation d’extensions non autorisées. Plus d’informations : https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions + ExtensionGalleryServiceUrl + Configurer l’URL du service Place de marché à laquelle se connecter + ChatToolsTerminalEnableAutoApprove + Contrôle s’il faut autoriser l’approbation automatique lors de l’exécution dans l’outil terminal. + EnableFeedback + Activez les mécanismes de commentaires tels que le système de rapport de problèmes, les sondages et autres options de commentaires. + TelemetryLevel + Contrôle le niveau de télémétrie. + Envoie les données d'utilisation, les erreurs et les rapports d'erreur. + Envoie la télémétrie d'erreur générale et les rapports de plantage. + Envoie des rapports de plantage au niveau du système d'exploitation. + Désactive toutes les données de télémétrie du produit. + UpdateMode + Choisissez si vous voulez recevoir des mises à jour automatiques. Nécessite un redémarrage après le changement. Les mises à jour sont récupérées auprès d'un service en ligne Microsoft. + Aucun + Désactivez la recherche de mises à jour automatique en arrière-plan. Les mises à jour sont disponibles si vous les rechercher manuellement. + Démarrer + Système + + + ChatAgentExtensionTools + ChatAgentMode + + ChatPromptFiles + ChatToolsAutoApprove + + + + ChatToolsTerminalEnableAutoApprove + EnableFeedback + + + + + diff --git a/build/lib/test/i18n.test.js b/build/lib/test/i18n.test.js deleted file mode 100644 index 41aa8a7f668d8..0000000000000 --- a/build/lib/test/i18n.test.js +++ /dev/null @@ -1,77 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const assert_1 = __importDefault(require("assert")); -const i18n = __importStar(require("../i18n")); -suite('XLF Parser Tests', () => { - const sampleXlf = 'Key #1Key #2 &'; - const sampleTranslatedXlf = 'Key #1Кнопка #1Key #2 &Кнопка #2 &'; - const name = 'vs/base/common/keybinding'; - const keys = ['key1', 'key2']; - const messages = ['Key #1', 'Key #2 &']; - const translatedMessages = { key1: 'Кнопка #1', key2: 'Кнопка #2 &' }; - test('Keys & messages to XLF conversion', () => { - const xlf = new i18n.XLF('vscode-workbench'); - xlf.addFile(name, keys, messages); - const xlfString = xlf.toString(); - assert_1.default.strictEqual(xlfString.replace(/\s{2,}/g, ''), sampleXlf); - }); - test('XLF to keys & messages conversion', () => { - i18n.XLF.parse(sampleTranslatedXlf).then(function (resolvedFiles) { - assert_1.default.deepStrictEqual(resolvedFiles[0].messages, translatedMessages); - assert_1.default.strictEqual(resolvedFiles[0].name, name); - }); - }); - test('JSON file source path to Transifex resource match', () => { - const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench'; - const platform = { name: 'vs/platform', project: editorProject }, editorContrib = { name: 'vs/editor/contrib', project: editorProject }, editor = { name: 'vs/editor', project: editorProject }, base = { name: 'vs/base', project: editorProject }, code = { name: 'vs/code', project: workbenchProject }, workbenchParts = { name: 'vs/workbench/contrib/html', project: workbenchProject }, workbenchServices = { name: 'vs/workbench/services/textfile', project: workbenchProject }, workbench = { name: 'vs/workbench', project: workbenchProject }; - assert_1.default.deepStrictEqual(i18n.getResource('vs/platform/actions/browser/menusExtensionPoint'), platform); - assert_1.default.deepStrictEqual(i18n.getResource('vs/editor/contrib/clipboard/browser/clipboard'), editorContrib); - assert_1.default.deepStrictEqual(i18n.getResource('vs/editor/common/modes/modesRegistry'), editor); - assert_1.default.deepStrictEqual(i18n.getResource('vs/base/common/errorMessage'), base); - assert_1.default.deepStrictEqual(i18n.getResource('vs/code/electron-main/window'), code); - assert_1.default.deepStrictEqual(i18n.getResource('vs/workbench/contrib/html/browser/webview'), workbenchParts); - assert_1.default.deepStrictEqual(i18n.getResource('vs/workbench/services/textfile/node/testFileService'), workbenchServices); - assert_1.default.deepStrictEqual(i18n.getResource('vs/workbench/browser/parts/panel/panelActions'), workbench); - }); -}); -//# sourceMappingURL=i18n.test.js.map \ No newline at end of file diff --git a/build/lib/test/i18n.test.ts b/build/lib/test/i18n.test.ts index 4e4545548b863..7d5bb0433feed 100644 --- a/build/lib/test/i18n.test.ts +++ b/build/lib/test/i18n.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import assert from 'assert'; -import * as i18n from '../i18n'; +import * as i18n from '../i18n.ts'; suite('XLF Parser Tests', () => { const sampleXlf = 'Key #1Key #2 &'; diff --git a/build/lib/test/numberPolicy.test.ts b/build/lib/test/numberPolicy.test.ts new file mode 100644 index 0000000000000..503403ca5c07d --- /dev/null +++ b/build/lib/test/numberPolicy.test.ts @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { NumberPolicy } from '../policies/numberPolicy.ts'; +import { type LanguageTranslations, PolicyType } from '../policies/types.ts'; +import type { CategoryDto, PolicyDto } from '../policies/policyDto.ts'; + +suite('NumberPolicy', () => { + const mockCategory: CategoryDto = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + + const mockPolicy: PolicyDto = { + key: 'test.number.policy', + name: 'TestNumberPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'number', + default: 42, + localization: { + description: { key: 'test.policy.description', value: 'Test number policy description' } + } + }; + + test('should create NumberPolicy from factory method', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + assert.strictEqual(policy.name, 'TestNumberPolicy'); + assert.strictEqual(policy.minimumVersion, '1.0'); + assert.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert.strictEqual(policy.category.name.value, mockCategory.name.value); + assert.strictEqual(policy.type, PolicyType.Number); + }); + + test('should render ADMX elements correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admx = policy.renderADMX('TestKey'); + + assert.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\t', + '' + ]); + }); + + test('should render ADML strings correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admlStrings = policy.renderADMLStrings(); + + assert.deepStrictEqual(admlStrings, [ + 'TestNumberPolicy', + 'Test number policy description' + ]); + }); + + test('should render ADML strings with translations', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated description' + } + }; + + const admlStrings = policy.renderADMLStrings(translations); + + assert.deepStrictEqual(admlStrings, [ + 'TestNumberPolicy', + 'Translated description' + ]); + }); + + test('should render ADML presentation correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const presentation = policy.renderADMLPresentation(); + + assert.strictEqual(presentation, 'TestNumberPolicy'); + }); + + test('should render JSON value correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const jsonValue = policy.renderJsonValue(); + + assert.strictEqual(jsonValue, 42); + }); + + test('should render profile value correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profileValue = policy.renderProfileValue(); + + assert.strictEqual(profileValue, '42'); + }); + + test('should render profile correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profile = policy.renderProfile(); + + assert.strictEqual(profile.length, 2); + assert.strictEqual(profile[0], 'TestNumberPolicy'); + assert.strictEqual(profile[1], '42'); + }); + + test('should render profile manifest value correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifestValue = policy.renderProfileManifestValue(); + + assert.strictEqual(manifestValue, 'pfm_default\n42\npfm_description\nTest number policy description\npfm_name\nTestNumberPolicy\npfm_title\nTestNumberPolicy\npfm_type\ninteger'); + }); + + test('should render profile manifest value with translations', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + + const manifestValue = policy.renderProfileManifestValue(translations); + + assert.strictEqual(manifestValue, 'pfm_default\n42\npfm_description\nTranslated manifest description\npfm_name\nTestNumberPolicy\npfm_title\nTestNumberPolicy\npfm_type\ninteger'); + }); + + test('should render profile manifest correctly', () => { + const policy = NumberPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifest = policy.renderProfileManifest(); + + assert.strictEqual(manifest, '\npfm_default\n42\npfm_description\nTest number policy description\npfm_name\nTestNumberPolicy\npfm_title\nTestNumberPolicy\npfm_type\ninteger\n'); + }); +}); diff --git a/build/lib/test/objectPolicy.test.ts b/build/lib/test/objectPolicy.test.ts new file mode 100644 index 0000000000000..8e688d19b8f7d --- /dev/null +++ b/build/lib/test/objectPolicy.test.ts @@ -0,0 +1,166 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { ObjectPolicy } from '../policies/objectPolicy.ts'; +import { type LanguageTranslations, PolicyType } from '../policies/types.ts'; +import type { CategoryDto, PolicyDto } from '../policies/policyDto.ts'; + +suite('ObjectPolicy', () => { + const mockCategory: CategoryDto = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + + const mockPolicy: PolicyDto = { + key: 'test.object.policy', + name: 'TestObjectPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'object', + localization: { + description: { key: 'test.policy.description', value: 'Test policy description' } + } + }; + + test('should create ObjectPolicy from factory method', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + assert.strictEqual(policy.name, 'TestObjectPolicy'); + assert.strictEqual(policy.minimumVersion, '1.0'); + assert.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert.strictEqual(policy.category.name.value, mockCategory.name.value); + assert.strictEqual(policy.type, PolicyType.Object); + }); + + test('should render ADMX elements correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admx = policy.renderADMX('TestKey'); + + assert.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\t', + '' + ]); + }); + + test('should render ADML strings correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admlStrings = policy.renderADMLStrings(); + + assert.deepStrictEqual(admlStrings, [ + 'TestObjectPolicy', + 'Test policy description' + ]); + }); + + test('should render ADML strings with translations', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated description' + } + }; + + const admlStrings = policy.renderADMLStrings(translations); + + assert.deepStrictEqual(admlStrings, [ + 'TestObjectPolicy', + 'Translated description' + ]); + }); + + test('should render ADML presentation correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const presentation = policy.renderADMLPresentation(); + + assert.strictEqual(presentation, ''); + }); + + test('should render JSON value correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const jsonValue = policy.renderJsonValue(); + + assert.strictEqual(jsonValue, ''); + }); + + test('should render profile value correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profileValue = policy.renderProfileValue(); + + assert.strictEqual(profileValue, ''); + }); + + test('should render profile correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profile = policy.renderProfile(); + + assert.strictEqual(profile.length, 2); + assert.strictEqual(profile[0], 'TestObjectPolicy'); + assert.strictEqual(profile[1], ''); + }); + + test('should render profile manifest value correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifestValue = policy.renderProfileManifestValue(); + + assert.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTest policy description\npfm_name\nTestObjectPolicy\npfm_title\nTestObjectPolicy\npfm_type\nstring\n'); + }); + + test('should render profile manifest value with translations', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + + const manifestValue = policy.renderProfileManifestValue(translations); + + assert.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTranslated manifest description\npfm_name\nTestObjectPolicy\npfm_title\nTestObjectPolicy\npfm_type\nstring\n'); + }); + + test('should render profile manifest correctly', () => { + const policy = ObjectPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifest = policy.renderProfileManifest(); + + assert.strictEqual(manifest, '\npfm_default\n\npfm_description\nTest policy description\npfm_name\nTestObjectPolicy\npfm_title\nTestObjectPolicy\npfm_type\nstring\n\n'); + }); +}); diff --git a/build/lib/test/policyConversion.test.ts b/build/lib/test/policyConversion.test.ts new file mode 100644 index 0000000000000..bb4036a7ab954 --- /dev/null +++ b/build/lib/test/policyConversion.test.ts @@ -0,0 +1,508 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { promises as fs } from 'fs'; +import path from 'path'; +import type { ExportedPolicyDataDto, CategoryDto } from '../policies/policyDto.ts'; +import { BooleanPolicy } from '../policies/booleanPolicy.ts'; +import { NumberPolicy } from '../policies/numberPolicy.ts'; +import { ObjectPolicy } from '../policies/objectPolicy.ts'; +import { StringEnumPolicy } from '../policies/stringEnumPolicy.ts'; +import { StringPolicy } from '../policies/stringPolicy.ts'; +import type { Policy, ProductJson } from '../policies/types.ts'; +import { renderGP, renderMacOSPolicy, renderJsonPolicies } from '../policies/render.ts'; + +const PolicyTypes = [ + BooleanPolicy, + NumberPolicy, + StringEnumPolicy, + StringPolicy, + ObjectPolicy +]; + +function parsePolicies(policyData: ExportedPolicyDataDto): Policy[] { + const categories = new Map(); + for (const category of policyData.categories) { + categories.set(category.key, category); + } + + const policies: Policy[] = []; + for (const policy of policyData.policies) { + const category = categories.get(policy.category); + if (!category) { + throw new Error(`Unknown category: ${policy.category}`); + } + + let result: Policy | undefined; + for (const policyType of PolicyTypes) { + if (result = policyType.from(category, policy)) { + break; + } + } + + if (!result) { + throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`); + } + + policies.push(result); + } + + // Sort policies first by category name, then by policy name + policies.sort((a, b) => { + const categoryCompare = a.category.name.value.localeCompare(b.category.name.value); + if (categoryCompare !== 0) { + return categoryCompare; + } + return a.name.localeCompare(b.name); + }); + + return policies; +} + +/** + * This is a snapshot of the data taken on Oct. 20 2025 as part of the + * policy refactor effort. Let's make sure that nothing has regressed. + */ +const policies: ExportedPolicyDataDto = { + categories: [ + { + key: 'Extensions', + name: { + key: 'extensionsConfigurationTitle', + value: 'Extensions' + } + }, + { + key: 'IntegratedTerminal', + name: { + key: 'terminalIntegratedConfigurationTitle', + value: 'Integrated Terminal' + } + }, + { + key: 'InteractiveSession', + name: { + key: 'interactiveSessionConfigurationTitle', + value: 'Chat' + } + }, + { + key: 'Telemetry', + name: { + key: 'telemetryConfigurationTitle', + value: 'Telemetry' + } + }, + { + key: 'Update', + name: { + key: 'updateConfigurationTitle', + value: 'Update' + } + } + ], + policies: [ + { + key: 'chat.mcp.gallery.serviceUrl', + name: 'McpGalleryServiceUrl', + category: 'InteractiveSession', + minimumVersion: '1.101', + localization: { + description: { + key: 'mcp.gallery.serviceUrl', + value: 'Configure the MCP Gallery service URL to connect to' + } + }, + type: 'string', + default: '' + }, + { + key: 'extensions.gallery.serviceUrl', + name: 'ExtensionGalleryServiceUrl', + category: 'Extensions', + minimumVersion: '1.99', + localization: { + description: { + key: 'extensions.gallery.serviceUrl', + value: 'Configure the Marketplace service URL to connect to' + } + }, + type: 'string', + default: '' + }, + { + key: 'extensions.allowed', + name: 'AllowedExtensions', + category: 'Extensions', + minimumVersion: '1.96', + localization: { + description: { + key: 'extensions.allowed.policy', + value: 'Specify a list of extensions that are allowed to use. This helps maintain a secure and consistent development environment by restricting the use of unauthorized extensions. More information: https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions' + } + }, + type: 'object', + default: '*' + }, + { + key: 'chat.tools.global.autoApprove', + name: 'ChatToolsAutoApprove', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'autoApprove2.description', + value: 'Global auto approve also known as "YOLO mode" disables manual approval completely for all tools in all workspaces, allowing the agent to act fully autonomously. This is extremely dangerous and is *never* recommended, even containerized environments like Codespaces and Dev Containers have user keys forwarded into the container that could be compromised.\n\nThis feature disables critical security protections and makes it much easier for an attacker to compromise the machine.' + } + }, + type: 'boolean', + default: false + }, + { + key: 'chat.mcp.access', + name: 'ChatMCP', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'chat.mcp.access', + value: 'Controls access to installed Model Context Protocol servers.' + }, + enumDescriptions: [ + { + key: 'chat.mcp.access.none', + value: 'No access to MCP servers.' + }, + { + key: 'chat.mcp.access.registry', + value: 'Allows access to MCP servers installed from the registry that VS Code is connected to.' + }, + { + key: 'chat.mcp.access.any', + value: 'Allow access to any installed MCP server.' + } + ] + }, + type: 'string', + default: 'all', + enum: [ + 'none', + 'registry', + 'all' + ] + }, + { + key: 'chat.extensionTools.enabled', + name: 'ChatAgentExtensionTools', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'chat.extensionToolsEnabled', + value: 'Enable using tools contributed by third-party extensions.' + } + }, + type: 'boolean', + default: true + }, + { + key: 'chat.agent.enabled', + name: 'ChatAgentMode', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'chat.agent.enabled.description', + value: 'Enable agent mode for chat. When this is enabled, agent mode can be activated via the dropdown in the view.' + } + }, + type: 'boolean', + default: true + }, + { + key: 'chat.promptFiles', + name: 'ChatPromptFiles', + category: 'InteractiveSession', + minimumVersion: '1.99', + localization: { + description: { + key: 'chat.promptFiles.policy', + value: 'Enables reusable prompt and instruction files in Chat sessions.' + } + }, + type: 'boolean', + default: true + }, + { + key: 'chat.tools.terminal.enableAutoApprove', + name: 'ChatToolsTerminalEnableAutoApprove', + category: 'IntegratedTerminal', + minimumVersion: '1.104', + localization: { + description: { + key: 'autoApproveMode.description', + value: 'Controls whether to allow auto approval in the run in terminal tool.' + } + }, + type: 'boolean', + default: true + }, + { + key: 'update.mode', + name: 'UpdateMode', + category: 'Update', + minimumVersion: '1.67', + localization: { + description: { + key: 'updateMode', + value: 'Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service.' + }, + enumDescriptions: [ + { + key: 'none', + value: 'Disable updates.' + }, + { + key: 'manual', + value: 'Disable automatic background update checks. Updates will be available if you manually check for updates.' + }, + { + key: 'start', + value: 'Check for updates only on startup. Disable automatic background update checks.' + }, + { + key: 'default', + value: 'Enable automatic update checks. Code will check for updates automatically and periodically.' + } + ] + }, + type: 'string', + default: 'default', + enum: [ + 'none', + 'manual', + 'start', + 'default' + ] + }, + { + key: 'telemetry.telemetryLevel', + name: 'TelemetryLevel', + category: 'Telemetry', + minimumVersion: '1.99', + localization: { + description: { + key: 'telemetry.telemetryLevel.policyDescription', + value: 'Controls the level of telemetry.' + }, + enumDescriptions: [ + { + key: 'telemetry.telemetryLevel.default', + value: 'Sends usage data, errors, and crash reports.' + }, + { + key: 'telemetry.telemetryLevel.error', + value: 'Sends general error telemetry and crash reports.' + }, + { + key: 'telemetry.telemetryLevel.crash', + value: 'Sends OS level crash reports.' + }, + { + key: 'telemetry.telemetryLevel.off', + value: 'Disables all product telemetry.' + } + ] + }, + type: 'string', + default: 'all', + enum: [ + 'all', + 'error', + 'crash', + 'off' + ] + }, + { + key: 'telemetry.feedback.enabled', + name: 'EnableFeedback', + category: 'Telemetry', + minimumVersion: '1.99', + localization: { + description: { + key: 'telemetry.feedback.enabled', + value: 'Enable feedback mechanisms such as the issue reporter, surveys, and other feedback options.' + } + }, + type: 'boolean', + default: true + } + ] +}; + +const mockProduct: ProductJson = { + nameLong: 'Code - OSS', + darwinBundleIdentifier: 'com.visualstudio.code.oss', + darwinProfilePayloadUUID: 'CF808BE7-53F3-46C6-A7E2-7EDB98A5E959', + darwinProfileUUID: '47827DD9-4734-49A0-AF80-7E19B11495CC', + win32RegValueName: 'CodeOSS' +}; + +const frenchTranslations = [ + { + languageId: 'fr-fr', + languageTranslations: { + '': { + 'interactiveSessionConfigurationTitle': 'Session interactive', + 'extensionsConfigurationTitle': 'Extensions', + 'terminalIntegratedConfigurationTitle': 'Terminal intégré', + 'telemetryConfigurationTitle': 'Télémétrie', + 'updateConfigurationTitle': 'Mettre à jour', + 'chat.extensionToolsEnabled': 'Autorisez l’utilisation d’outils fournis par des extensions tierces.', + 'chat.agent.enabled.description': 'Activez le mode Assistant pour la conversation. Lorsque cette option est activée, le mode Assistant peut être activé via la liste déroulante de la vue.', + 'chat.mcp.access': 'Contrôle l’accès aux serveurs de protocole de contexte du modèle.', + 'chat.mcp.access.none': 'Aucun accès aux serveurs MCP.', + 'chat.mcp.access.registry': `Autorise l’accès aux serveurs MCP installés à partir du registre auquel VS Code est connecté.`, + 'chat.mcp.access.any': 'Autorisez l’accès à tout serveur MCP installé.', + 'chat.promptFiles.policy': 'Active les fichiers d’instruction et de requête réutilisables dans les sessions Conversation.', + 'autoApprove2.description': `L’approbation automatique globale, également appelée « mode YOLO », désactive complètement l’approbation manuelle pour tous les outils dans tous les espaces de travail, permettant à l’agent d’agir de manière totalement autonome. Ceci est extrêmement dangereux et est *jamais* recommandé, même dans des environnements conteneurisés comme [Codespaces](https://github.com/features/codespaces) et [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers), où des clés utilisateur sont transférées dans le conteneur et pourraient être compromises. + +Cette fonctionnalité désactive [les protections de sécurité critiques](https://code.visualstudio.com/docs/copilot/security) et facilite considérablement la compromission de la machine par un attaquant.`, + 'mcp.gallery.serviceUrl': 'Configurer l’URL du service de la galerie MCP à laquelle se connecter', + 'extensions.allowed.policy': 'Spécifiez une liste d’extensions autorisées. Cela permet de maintenir un environnement de développement sécurisé et cohérent en limitant l’utilisation d’extensions non autorisées. Plus d’informations : https://code.visualstudio.com/docs/setup/enterprise#_configure-allowed-extensions', + 'extensions.gallery.serviceUrl': 'Configurer l’URL du service Place de marché à laquelle se connecter', + 'autoApproveMode.description': 'Contrôle s’il faut autoriser l’approbation automatique lors de l’exécution dans l’outil terminal.', + 'telemetry.feedback.enabled': 'Activez les mécanismes de commentaires tels que le système de rapport de problèmes, les sondages et autres options de commentaires.', + 'telemetry.telemetryLevel.policyDescription': 'Contrôle le niveau de télémétrie.', + 'telemetry.telemetryLevel.default': `Envoie les données d'utilisation, les erreurs et les rapports d'erreur.`, + 'telemetry.telemetryLevel.error': `Envoie la télémétrie d'erreur générale et les rapports de plantage.`, + 'telemetry.telemetryLevel.crash': `Envoie des rapports de plantage au niveau du système d'exploitation.`, + 'telemetry.telemetryLevel.off': 'Désactive toutes les données de télémétrie du produit.', + 'updateMode': `Choisissez si vous voulez recevoir des mises à jour automatiques. Nécessite un redémarrage après le changement. Les mises à jour sont récupérées auprès d'un service en ligne Microsoft.`, + 'none': 'Aucun', + 'manual': 'Désactivez la recherche de mises à jour automatique en arrière-plan. Les mises à jour sont disponibles si vous les rechercher manuellement.', + 'start': 'Démarrer', + 'default': 'Système' + } + } + } +]; + +suite('Policy E2E conversion', () => { + + test('should render macOS policy profile from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderMacOSPolicy(mockProduct, parsedPolicies, []); + + // Load the expected fixture file + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'darwin', 'com.visualstudio.code.oss.mobileconfig'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + + // Compare the rendered profile with the fixture + assert.strictEqual(result.profile, expectedContent, 'macOS policy profile should match the fixture'); + }); + + test('should render macOS manifest from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderMacOSPolicy(mockProduct, parsedPolicies, []); + + // Load the expected fixture file + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'darwin', 'en-us', 'com.visualstudio.code.oss.plist'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + + // Find the en-us manifest + const enUsManifest = result.manifests.find(m => m.languageId === 'en-us'); + assert.ok(enUsManifest, 'en-us manifest should exist'); + + // Compare the rendered manifest with the fixture, ignoring the timestamp + // The pfm_last_modified field contains a timestamp that will differ each time + const normalizeTimestamp = (content: string) => content.replace(/.*?<\/date>/, 'TIMESTAMP'); + assert.strictEqual( + normalizeTimestamp(enUsManifest.contents), + normalizeTimestamp(expectedContent), + 'macOS manifest should match the fixture (ignoring timestamp)' + ); + }); + + test('should render Windows ADMX from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderGP(mockProduct, parsedPolicies, []); + + // Load the expected fixture file + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'win32', 'CodeOSS.admx'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + + // Compare the rendered ADMX with the fixture + assert.strictEqual(result.admx, expectedContent, 'Windows ADMX should match the fixture'); + }); + + test('should render Windows ADML from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderGP(mockProduct, parsedPolicies, []); + + // Load the expected fixture file + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'win32', 'en-us', 'CodeOSS.adml'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + + // Find the en-us ADML + const enUsAdml = result.adml.find(a => a.languageId === 'en-us'); + assert.ok(enUsAdml, 'en-us ADML should exist'); + + // Compare the rendered ADML with the fixture + assert.strictEqual(enUsAdml.contents, expectedContent, 'Windows ADML should match the fixture'); + }); + + test('should render macOS manifest with fr-fr locale', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderMacOSPolicy(mockProduct, parsedPolicies, frenchTranslations); + + // Load the expected fixture file + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'darwin', 'fr-fr', 'com.visualstudio.code.oss.plist'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + + // Find the fr-fr manifest + const frFrManifest = result.manifests.find(m => m.languageId === 'fr-fr'); + assert.ok(frFrManifest, 'fr-fr manifest should exist'); + + // Compare the rendered manifest with the fixture, ignoring the timestamp + const normalizeTimestamp = (content: string) => content.replace(/.*?<\/date>/, 'TIMESTAMP'); + assert.strictEqual( + normalizeTimestamp(frFrManifest.contents), + normalizeTimestamp(expectedContent), + 'macOS fr-fr manifest should match the fixture (ignoring timestamp)' + ); + }); + + test('should render Windows ADML with fr-fr locale', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderGP(mockProduct, parsedPolicies, frenchTranslations); + + // Load the expected fixture file + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'win32', 'fr-fr', 'CodeOSS.adml'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + + // Find the fr-fr ADML + const frFrAdml = result.adml.find(a => a.languageId === 'fr-fr'); + assert.ok(frFrAdml, 'fr-fr ADML should exist'); + + // Compare the rendered ADML with the fixture + assert.strictEqual(frFrAdml.contents, expectedContent, 'Windows fr-fr ADML should match the fixture'); + }); + + test('should render Linux policy JSON from policies list', async () => { + const parsedPolicies = parsePolicies(policies); + const result = renderJsonPolicies(parsedPolicies); + + // Load the expected fixture file + const fixturePath = path.join(import.meta.dirname, 'fixtures', 'policies', 'linux', 'policy.json'); + const expectedContent = await fs.readFile(fixturePath, 'utf-8'); + const expectedJson = JSON.parse(expectedContent); + + // Compare the rendered JSON with the fixture + assert.deepStrictEqual(result, expectedJson, 'Linux policy JSON should match the fixture'); + }); + +}); diff --git a/build/lib/test/render.test.ts b/build/lib/test/render.test.ts new file mode 100644 index 0000000000000..130bbc781325b --- /dev/null +++ b/build/lib/test/render.test.ts @@ -0,0 +1,1029 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { renderADMLString, renderProfileString, renderADMX, renderADML, renderProfileManifest, renderMacOSPolicy, renderGP, renderJsonPolicies } from '../policies/render.ts'; +import { type NlsString, type LanguageTranslations, type Category, type Policy, PolicyType } from '../policies/types.ts'; + +suite('Render Functions', () => { + + suite('renderADMLString', () => { + + test('should render ADML string without translations', () => { + const nlsString: NlsString = { + value: 'Test description', + nlsKey: 'test.description' + }; + + const result = renderADMLString('TestPrefix', 'testModule', nlsString); + + assert.strictEqual(result, 'Test description'); + }); + + test('should replace dots with underscores in nls key', () => { + const nlsString: NlsString = { + value: 'Test value', + nlsKey: 'my.test.nls.key' + }; + + const result = renderADMLString('Prefix', 'testModule', nlsString); + + assert.ok(result.includes('id="Prefix_my_test_nls_key"')); + }); + + test('should use translation when available', () => { + const nlsString: NlsString = { + value: 'Original value', + nlsKey: 'test.key' + }; + + const translations: LanguageTranslations = { + 'testModule': { + 'test.key': 'Translated value' + } + }; + + const result = renderADMLString('TestPrefix', 'testModule', nlsString, translations); + + assert.ok(result.includes('>Translated value')); + }); + + test('should fallback to original value when translation not found', () => { + const nlsString: NlsString = { + value: 'Original value', + nlsKey: 'test.key' + }; + + const translations: LanguageTranslations = { + 'testModule': { + 'other.key': 'Other translation' + } + }; + + const result = renderADMLString('TestPrefix', 'testModule', nlsString, translations); + + assert.ok(result.includes('>Original value')); + }); + }); + + suite('renderProfileString', () => { + + test('should render profile string without translations', () => { + const nlsString: NlsString = { + value: 'Profile description', + nlsKey: 'profile.description' + }; + + const result = renderProfileString('ProfilePrefix', 'testModule', nlsString); + + assert.strictEqual(result, 'Profile description'); + }); + + test('should use translation when available', () => { + const nlsString: NlsString = { + value: 'Original profile value', + nlsKey: 'profile.key' + }; + + const translations: LanguageTranslations = { + 'testModule': { + 'profile.key': 'Translated profile value' + } + }; + + const result = renderProfileString('ProfilePrefix', 'testModule', nlsString, translations); + + assert.strictEqual(result, 'Translated profile value'); + }); + + test('should fallback to original value when translation not found', () => { + const nlsString: NlsString = { + value: 'Original profile value', + nlsKey: 'profile.key' + }; + + const translations: LanguageTranslations = { + 'testModule': { + 'other.key': 'Other translation' + } + }; + + const result = renderProfileString('ProfilePrefix', 'testModule', nlsString, translations); + + assert.strictEqual(result, 'Original profile value'); + }); + }); + + suite('renderADMX', () => { + + const mockCategory: Category = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + + const mockPolicy: Policy = { + name: 'TestPolicy', + type: PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.85', + renderADMX: (regKey: string) => [ + ``, + ` `, + `` + ], + renderADMLStrings: () => ['Test Policy'], + renderADMLPresentation: () => '', + renderProfile: () => ['TestPolicy', ''], + renderProfileManifest: () => 'pfm_nameTestPolicy', + renderJsonValue: () => null + }; + + test('should render ADMX with correct XML structure', () => { + const result = renderADMX('VSCode', ['1.85'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('')); + assert.ok(result.includes('')); + }); + + test('should include policy namespaces with regKey', () => { + const result = renderADMX('TestApp', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes(' { + const result = renderADMX('VSCode', ['1.85.0', '1.90.1'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('Supported_1_85_0')); + assert.ok(result.includes('Supported_1_90_1')); + assert.ok(!result.includes('Supported_1.85.0')); + }); + + test('should include categories in correct structure', () => { + const result = renderADMX('VSCode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('')); + assert.ok(result.includes('')); + }); + + test('should include policies section', () => { + const result = renderADMX('VSCode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('')); + assert.ok(result.includes('TestPolicy')); + assert.ok(result.includes('')); + }); + + test('should handle multiple versions', () => { + const result = renderADMX('VSCode', ['1.0', '1.5', '2.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('Supported_1_0')); + assert.ok(result.includes('Supported_1_5')); + assert.ok(result.includes('Supported_2_0')); + }); + + test('should handle multiple categories', () => { + const category1: Category = { moduleName: 'testModule', name: { value: 'Cat1', nlsKey: 'cat1' } }; + const category2: Category = { moduleName: 'testModule', name: { value: 'Cat2', nlsKey: 'cat2' } }; + + const result = renderADMX('VSCode', ['1.0'], [category1, category2], [mockPolicy]); + + assert.ok(result.includes('Category_cat1')); + assert.ok(result.includes('Category_cat2')); + }); + + test('should handle multiple policies', () => { + const policy2: Policy = { + name: 'TestPolicy2', + type: PolicyType.String, + category: mockCategory, + minimumVersion: '1.85', + renderADMX: (regKey: string) => [ + ``, + ` `, + `` + ], + renderADMLStrings: () => ['Test Policy 2'], + renderADMLPresentation: () => '', + renderProfile: () => ['TestPolicy2', ''], + renderProfileManifest: () => 'pfm_nameTestPolicy2', + renderJsonValue: () => null + }; + const result = renderADMX('VSCode', ['1.0'], [mockCategory], [mockPolicy, policy2]); + + assert.ok(result.includes('TestPolicy')); + assert.ok(result.includes('TestPolicy2')); + }); + }); + + suite('renderADML', () => { + + const mockCategory: Category = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + + const mockPolicy: Policy = { + name: 'TestPolicy', + type: PolicyType.String, + category: mockCategory, + minimumVersion: '1.85', + renderADMX: () => [], + renderADMLStrings: (translations?: LanguageTranslations) => [ + `Test Policy ${translations?.['testModule']?.['test.policy'] || 'Default'}` + ], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => null + }; + + test('should render ADML with correct XML structure', () => { + const result = renderADML('VS Code', ['1.85'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('')); + assert.ok(result.includes('')); + }); + + test('should include application name', () => { + const result = renderADML('My Application', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('My Application')); + }); + + test('should include supported versions with escaped greater-than', () => { + const result = renderADML('VS Code', ['1.85', '1.90'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('VS Code >= 1.85')); + assert.ok(result.includes('VS Code >= 1.90')); + }); + + test('should include category strings', () => { + const result = renderADML('VS Code', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('Category_test_category')); + }); + + test('should include policy strings', () => { + const result = renderADML('VS Code', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('TestPolicy')); + assert.ok(result.includes('Test Policy Default')); + }); + + test('should include policy presentations', () => { + const result = renderADML('VS Code', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('')); + assert.ok(result.includes('')); + assert.ok(result.includes('')); + }); + + test('should pass translations to policy strings', () => { + const translations: LanguageTranslations = { + 'testModule': { + 'test.policy': 'Translated' + } + }; + + const result = renderADML('VS Code', ['1.0'], [mockCategory], [mockPolicy], translations); + + assert.ok(result.includes('Test Policy Translated')); + }); + + test('should handle multiple categories', () => { + const category1: Category = { moduleName: 'testModule', name: { value: 'Cat1', nlsKey: 'cat1' } }; + const category2: Category = { moduleName: 'testModule', name: { value: 'Cat2', nlsKey: 'cat2' } }; + + const result = renderADML('VS Code', ['1.0'], [category1, category2], [mockPolicy]); + + assert.ok(result.includes('Category_cat1')); + assert.ok(result.includes('Category_cat2')); + }); + }); + + suite('renderProfileManifest', () => { + + const mockCategory: Category = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + + const mockPolicy: Policy = { + name: 'TestPolicy', + type: PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: (translations?: LanguageTranslations) => ` +pfm_name +TestPolicy +pfm_description +${translations?.['testModule']?.['test.desc'] || 'Default Desc'} +`, + renderJsonValue: () => null + }; + + test('should render profile manifest with correct XML structure', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('')); + assert.ok(result.includes('')); + assert.ok(result.includes('')); + }); + + test('should include app name', () => { + const result = renderProfileManifest('My App', 'com.example.myapp', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('My App Managed Settings')); + assert.ok(result.includes('My App')); + }); + + test('should include bundle identifier', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('com.microsoft.vscode')); + }); + + test('should include required payload fields', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('PayloadDescription')); + assert.ok(result.includes('PayloadDisplayName')); + assert.ok(result.includes('PayloadIdentifier')); + assert.ok(result.includes('PayloadType')); + assert.ok(result.includes('PayloadUUID')); + assert.ok(result.includes('PayloadVersion')); + assert.ok(result.includes('PayloadOrganization')); + }); + + test('should include policy manifests in subkeys', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('pfm_subkeys')); + assert.ok(result.includes('TestPolicy')); + assert.ok(result.includes('Default Desc')); + }); + + test('should pass translations to policy manifests', () => { + const translations: LanguageTranslations = { + 'testModule': { + 'test.desc': 'Translated Description' + } + }; + + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy], translations); + + assert.ok(result.includes('Translated Description')); + }); + + test('should include VS Code specific URLs', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('https://code.visualstudio.com/')); + assert.ok(result.includes('https://code.visualstudio.com/docs/setup/enterprise')); + }); + + test('should include last modified date', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('pfm_last_modified')); + assert.ok(result.includes('')); + }); + + test('should mark manifest as unique', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('pfm_unique')); + assert.ok(result.includes('')); + }); + + test('should handle multiple policies', () => { + const policy2: Policy = { + ...mockPolicy, + name: 'TestPolicy2', + renderProfileManifest: () => ` +pfm_name +TestPolicy2 +` + }; + + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy, policy2]); + + assert.ok(result.includes('TestPolicy')); + assert.ok(result.includes('TestPolicy2')); + }); + + test('should set format version to 1', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('pfm_format_version')); + assert.ok(result.includes('1')); + }); + + test('should set interaction to combined', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('pfm_interaction')); + assert.ok(result.includes('combined')); + }); + + test('should set platform to macOS', () => { + const result = renderProfileManifest('VS Code', 'com.microsoft.vscode', ['1.0'], [mockCategory], [mockPolicy]); + + assert.ok(result.includes('pfm_platforms')); + assert.ok(result.includes('macOS')); + }); + }); + + suite('renderMacOSPolicy', () => { + + const mockCategory: Category = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + + const mockPolicy: Policy = { + name: 'TestPolicy', + type: PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => ['TestPolicy', ''], + renderProfileManifest: (translations?: LanguageTranslations) => ` +pfm_name +TestPolicy +pfm_description +${translations?.['testModule']?.['test.desc'] || 'Default Desc'} +`, + renderJsonValue: () => null + }; + + test('should render complete macOS policy profile', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderMacOSPolicy(product, [mockPolicy], []); + + const expected = ` + + + + PayloadContent + + + PayloadDisplayName + VS Code + PayloadIdentifier + com.microsoft.vscode.uuid + PayloadType + com.microsoft.vscode + PayloadUUID + uuid + PayloadVersion + 1 + TestPolicy + + + + PayloadDescription + This profile manages VS Code. For more information see https://code.visualstudio.com/docs/setup/enterprise + PayloadDisplayName + VS Code + PayloadIdentifier + com.microsoft.vscode + PayloadOrganization + Microsoft + PayloadType + Configuration + PayloadUUID + payload-uuid + PayloadVersion + 1 + TargetDeviceType + 5 + +`; + + assert.strictEqual(result.profile, expected); + }); + + test('should include en-us manifest by default', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderMacOSPolicy(product, [mockPolicy], []); + + assert.strictEqual(result.manifests.length, 1); + assert.strictEqual(result.manifests[0].languageId, 'en-us'); + assert.ok(result.manifests[0].contents.includes('VS Code Managed Settings')); + }); + + test('should include translations', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const translations = [ + { languageId: 'fr-fr', languageTranslations: { 'testModule': { 'test.desc': 'Description Française' } } }, + { languageId: 'de-de', languageTranslations: { 'testModule': { 'test.desc': 'Deutsche Beschreibung' } } } + ]; + + const result = renderMacOSPolicy(product, [mockPolicy], translations); + + assert.strictEqual(result.manifests.length, 3); // en-us + 2 translations + assert.strictEqual(result.manifests[0].languageId, 'en-us'); + assert.strictEqual(result.manifests[1].languageId, 'fr-fr'); + assert.strictEqual(result.manifests[2].languageId, 'de-de'); + + assert.ok(result.manifests[1].contents.includes('Description Française')); + assert.ok(result.manifests[2].contents.includes('Deutsche Beschreibung')); + }); + + test('should handle multiple policies with correct indentation', () => { + const policy2: Policy = { + ...mockPolicy, + name: 'TestPolicy2', + renderProfile: () => ['TestPolicy2', 'test value'] + }; + + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderMacOSPolicy(product, [mockPolicy, policy2], []); + + assert.ok(result.profile.includes('TestPolicy')); + assert.ok(result.profile.includes('')); + assert.ok(result.profile.includes('TestPolicy2')); + assert.ok(result.profile.includes('test value')); + }); + + test('should use provided UUIDs in profile', () => { + const product = { + nameLong: 'My App', + darwinBundleIdentifier: 'com.example.app', + darwinProfilePayloadUUID: 'custom-payload-uuid', + darwinProfileUUID: 'custom-uuid', + win32RegValueName: 'VSCode' + }; + const result = renderMacOSPolicy(product, [mockPolicy], []); + + assert.ok(result.profile.includes('custom-payload-uuid')); + assert.ok(result.profile.includes('custom-uuid')); + assert.ok(result.profile.includes('com.example.app.custom-uuid')); + }); + + test('should include enterprise documentation link', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderMacOSPolicy(product, [mockPolicy], []); + + assert.ok(result.profile.includes('https://code.visualstudio.com/docs/setup/enterprise')); + }); + + test('should set TargetDeviceType to 5', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderMacOSPolicy(product, [mockPolicy], []); + + assert.ok(result.profile.includes('TargetDeviceType')); + assert.ok(result.profile.includes('5')); + }); + }); + + suite('renderGP', () => { + + const mockCategory: Category = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + + const mockPolicy: Policy = { + name: 'TestPolicy', + type: PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.85', + renderADMX: (regKey: string) => [ + ``, + ` `, + `` + ], + renderADMLStrings: (translations?: LanguageTranslations) => [ + `${translations?.['testModule']?.['test.policy'] || 'Test Policy'}` + ], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => null + }; + + test('should render complete GP with ADMX and ADML', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.admx); + assert.ok(result.adml); + assert.ok(Array.isArray(result.adml)); + }); + + test('should include regKey in ADMX', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'CustomRegKey' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.admx.includes('CustomRegKey')); + assert.ok(result.admx.includes('Software\\Policies\\Microsoft\\CustomRegKey')); + }); + + test('should include en-us ADML by default', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.strictEqual(result.adml.length, 1); + assert.strictEqual(result.adml[0].languageId, 'en-us'); + assert.ok(result.adml[0].contents.includes('VS Code')); + }); + + test('should include translations in ADML', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const translations = [ + { languageId: 'fr-fr', languageTranslations: { 'testModule': { 'test.policy': 'Politique de test' } } }, + { languageId: 'de-de', languageTranslations: { 'testModule': { 'test.policy': 'Testrichtlinie' } } } + ]; + + const result = renderGP(product, [mockPolicy], translations); + + assert.strictEqual(result.adml.length, 3); // en-us + 2 translations + assert.strictEqual(result.adml[0].languageId, 'en-us'); + assert.strictEqual(result.adml[1].languageId, 'fr-fr'); + assert.strictEqual(result.adml[2].languageId, 'de-de'); + + assert.ok(result.adml[1].contents.includes('Politique de test')); + assert.ok(result.adml[2].contents.includes('Testrichtlinie')); + }); + + test('should pass versions to ADMX', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.admx.includes('Supported_1_85')); + }); + + test('should pass versions to ADML', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.adml[0].contents.includes('VS Code >= 1.85')); + }); + + test('should pass categories to ADMX', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.admx.includes('test.category')); + }); + + test('should pass categories to ADML', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.adml[0].contents.includes('Category_test_category')); + }); + + test('should handle multiple policies', () => { + const policy2: Policy = { + ...mockPolicy, + name: 'TestPolicy2', + renderADMX: (regKey: string) => [ + ``, + ` `, + `` + ], + renderADMLStrings: () => ['Test Policy 2'] + }; + + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy, policy2], []); + + assert.ok(result.admx.includes('TestPolicy')); + assert.ok(result.admx.includes('TestPolicy2')); + assert.ok(result.adml[0].contents.includes('TestPolicy')); + assert.ok(result.adml[0].contents.includes('TestPolicy2')); + }); + + test('should include app name in ADML', () => { + const product = { + nameLong: 'My Custom App', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok(result.adml[0].contents.includes('My Custom App')); + }); + + test('should return structured result with admx and adml properties', () => { + const product = { + nameLong: 'VS Code', + darwinBundleIdentifier: 'com.microsoft.vscode', + darwinProfilePayloadUUID: 'payload-uuid', + darwinProfileUUID: 'uuid', + win32RegValueName: 'VSCode' + }; + const result = renderGP(product, [mockPolicy], []); + + assert.ok('admx' in result); + assert.ok('adml' in result); + assert.strictEqual(typeof result.admx, 'string'); + assert.ok(Array.isArray(result.adml)); + }); + }); + + suite('renderJsonPolicies', () => { + + const mockCategory: Category = { + moduleName: 'testModule', + name: { value: 'Test Category', nlsKey: 'test.category' } + }; + + test('should render boolean policy JSON value', () => { + const booleanPolicy: Policy = { + name: 'BooleanPolicy', + type: PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => false + }; + + const result = renderJsonPolicies([booleanPolicy]); + + assert.deepStrictEqual(result, { BooleanPolicy: false }); + }); + + test('should render number policy JSON value', () => { + const numberPolicy: Policy = { + name: 'NumberPolicy', + type: PolicyType.Number, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => 42 + }; + + const result = renderJsonPolicies([numberPolicy]); + + assert.deepStrictEqual(result, { NumberPolicy: 42 }); + }); + + test('should render string policy JSON value', () => { + const stringPolicy: Policy = { + name: 'StringPolicy', + type: PolicyType.String, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => '' + }; + + const result = renderJsonPolicies([stringPolicy]); + + assert.deepStrictEqual(result, { StringPolicy: '' }); + }); + + test('should render string enum policy JSON value', () => { + const stringEnumPolicy: Policy = { + name: 'StringEnumPolicy', + type: PolicyType.StringEnum, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => 'auto' + }; + + const result = renderJsonPolicies([stringEnumPolicy]); + + assert.deepStrictEqual(result, { StringEnumPolicy: 'auto' }); + }); + + test('should render object policy JSON value', () => { + const objectPolicy: Policy = { + name: 'ObjectPolicy', + type: PolicyType.Object, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => '' + }; + + const result = renderJsonPolicies([objectPolicy]); + + assert.deepStrictEqual(result, { ObjectPolicy: '' }); + }); + + test('should render multiple policies', () => { + const booleanPolicy: Policy = { + name: 'BooleanPolicy', + type: PolicyType.Boolean, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => true + }; + + const numberPolicy: Policy = { + name: 'NumberPolicy', + type: PolicyType.Number, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => 100 + }; + + const stringPolicy: Policy = { + name: 'StringPolicy', + type: PolicyType.String, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => 'test-value' + }; + + const result = renderJsonPolicies([booleanPolicy, numberPolicy, stringPolicy]); + + assert.deepStrictEqual(result, { + BooleanPolicy: true, + NumberPolicy: 100, + StringPolicy: 'test-value' + }); + }); + + test('should handle empty policies array', () => { + const result = renderJsonPolicies([]); + + assert.deepStrictEqual(result, {}); + }); + + test('should handle null JSON value', () => { + const nullPolicy: Policy = { + name: 'NullPolicy', + type: PolicyType.String, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => null + }; + + const result = renderJsonPolicies([nullPolicy]); + + assert.deepStrictEqual(result, { NullPolicy: null }); + }); + + test('should handle object JSON value', () => { + const objectPolicy: Policy = { + name: 'ComplexObjectPolicy', + type: PolicyType.Object, + category: mockCategory, + minimumVersion: '1.0', + renderADMX: () => [], + renderADMLStrings: () => [], + renderADMLPresentation: () => '', + renderProfile: () => [], + renderProfileManifest: () => '', + renderJsonValue: () => ({ nested: { value: 123 } }) + }; + + const result = renderJsonPolicies([objectPolicy]); + + assert.deepStrictEqual(result, { ComplexObjectPolicy: { nested: { value: 123 } } }); + }); + }); +}); diff --git a/build/lib/test/stringEnumPolicy.test.ts b/build/lib/test/stringEnumPolicy.test.ts new file mode 100644 index 0000000000000..f27f9ec0a175a --- /dev/null +++ b/build/lib/test/stringEnumPolicy.test.ts @@ -0,0 +1,184 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { StringEnumPolicy } from '../policies/stringEnumPolicy.ts'; +import { PolicyType, type LanguageTranslations } from '../policies/types.ts'; +import type { CategoryDto, PolicyDto } from '../policies/policyDto.ts'; + +suite('StringEnumPolicy', () => { + const mockCategory: CategoryDto = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + + const mockPolicy: PolicyDto = { + key: 'test.stringenum.policy', + name: 'TestStringEnumPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'string', + localization: { + description: { key: 'test.policy.description', value: 'Test policy description' }, + enumDescriptions: [ + { key: 'test.option.one', value: 'Option One' }, + { key: 'test.option.two', value: 'Option Two' }, + { key: 'test.option.three', value: 'Option Three' } + ] + }, + enum: ['auto', 'manual', 'disabled'] + }; + + test('should create StringEnumPolicy from factory method', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + assert.strictEqual(policy.name, 'TestStringEnumPolicy'); + assert.strictEqual(policy.minimumVersion, '1.0'); + assert.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert.strictEqual(policy.category.name.value, mockCategory.name.value); + assert.strictEqual(policy.type, PolicyType.StringEnum); + }); + + test('should render ADMX elements correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admx = policy.renderADMX('TestKey'); + + assert.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\tauto', + '\tmanual', + '\tdisabled', + '', + '\t', + '' + ]); + }); + + test('should render ADML strings correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admlStrings = policy.renderADMLStrings(); + + assert.deepStrictEqual(admlStrings, [ + 'TestStringEnumPolicy', + 'Test policy description', + 'Option One', + 'Option Two', + 'Option Three' + ]); + }); + + test('should render ADML strings with translations', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated description', + 'test.option.one': 'Translated Option One', + 'test.option.two': 'Translated Option Two' + } + }; + + const admlStrings = policy.renderADMLStrings(translations); + + assert.deepStrictEqual(admlStrings, [ + 'TestStringEnumPolicy', + 'Translated description', + 'Translated Option One', + 'Translated Option Two', + 'Option Three' + ]); + }); + + test('should render ADML presentation correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const presentation = policy.renderADMLPresentation(); + + assert.strictEqual(presentation, ''); + }); + + test('should render JSON value correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const jsonValue = policy.renderJsonValue(); + + assert.strictEqual(jsonValue, 'auto'); + }); + + test('should render profile value correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profileValue = policy.renderProfileValue(); + + assert.strictEqual(profileValue, 'auto'); + }); + + test('should render profile correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profile = policy.renderProfile(); + + assert.strictEqual(profile.length, 2); + assert.strictEqual(profile[0], 'TestStringEnumPolicy'); + assert.strictEqual(profile[1], 'auto'); + }); + + test('should render profile manifest value correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifestValue = policy.renderProfileManifestValue(); + + assert.strictEqual(manifestValue, 'pfm_default\nauto\npfm_description\nTest policy description\npfm_name\nTestStringEnumPolicy\npfm_title\nTestStringEnumPolicy\npfm_type\nstring\npfm_range_list\n\n\tauto\n\tmanual\n\tdisabled\n'); + }); + + test('should render profile manifest value with translations', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + + const manifestValue = policy.renderProfileManifestValue(translations); + + assert.strictEqual(manifestValue, 'pfm_default\nauto\npfm_description\nTranslated manifest description\npfm_name\nTestStringEnumPolicy\npfm_title\nTestStringEnumPolicy\npfm_type\nstring\npfm_range_list\n\n\tauto\n\tmanual\n\tdisabled\n'); + }); + + test('should render profile manifest correctly', () => { + const policy = StringEnumPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifest = policy.renderProfileManifest(); + + assert.strictEqual(manifest, '\npfm_default\nauto\npfm_description\nTest policy description\npfm_name\nTestStringEnumPolicy\npfm_title\nTestStringEnumPolicy\npfm_type\nstring\npfm_range_list\n\n\tauto\n\tmanual\n\tdisabled\n\n'); + }); +}); diff --git a/build/lib/test/stringPolicy.test.ts b/build/lib/test/stringPolicy.test.ts new file mode 100644 index 0000000000000..7f69da33869e2 --- /dev/null +++ b/build/lib/test/stringPolicy.test.ts @@ -0,0 +1,167 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { StringPolicy } from '../policies/stringPolicy.ts'; +import { PolicyType, type LanguageTranslations } from '../policies/types.ts'; +import type { CategoryDto, PolicyDto } from '../policies/policyDto.ts'; + +suite('StringPolicy', () => { + const mockCategory: CategoryDto = { + key: 'test.category', + name: { value: 'Category1', key: 'test.category' }, + }; + + const mockPolicy: PolicyDto = { + key: 'test.string.policy', + name: 'TestStringPolicy', + category: 'Category1', + minimumVersion: '1.0', + type: 'string', + default: '', + localization: { + description: { key: 'test.policy.description', value: 'Test string policy description' } + } + }; + + test('should create StringPolicy from factory method', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + assert.strictEqual(policy.name, 'TestStringPolicy'); + assert.strictEqual(policy.minimumVersion, '1.0'); + assert.strictEqual(policy.category.name.nlsKey, mockCategory.name.key); + assert.strictEqual(policy.category.name.value, mockCategory.name.value); + assert.strictEqual(policy.type, PolicyType.String); + }); + + test('should render ADMX elements correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admx = policy.renderADMX('TestKey'); + + assert.deepStrictEqual(admx, [ + '', + '\t', + '\t', + '\t', + '', + '\t', + '' + ]); + }); + + test('should render ADML strings correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const admlStrings = policy.renderADMLStrings(); + + assert.deepStrictEqual(admlStrings, [ + 'TestStringPolicy', + 'Test string policy description' + ]); + }); + + test('should render ADML strings with translations', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated description' + } + }; + + const admlStrings = policy.renderADMLStrings(translations); + + assert.deepStrictEqual(admlStrings, [ + 'TestStringPolicy', + 'Translated description' + ]); + }); + + test('should render ADML presentation correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const presentation = policy.renderADMLPresentation(); + + assert.strictEqual(presentation, ''); + }); + + test('should render JSON value correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const jsonValue = policy.renderJsonValue(); + + assert.strictEqual(jsonValue, ''); + }); + + test('should render profile value correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profileValue = policy.renderProfileValue(); + + assert.strictEqual(profileValue, ''); + }); + + test('should render profile correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const profile = policy.renderProfile(); + + assert.strictEqual(profile.length, 2); + assert.strictEqual(profile[0], 'TestStringPolicy'); + assert.strictEqual(profile[1], ''); + }); + + test('should render profile manifest value correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifestValue = policy.renderProfileManifestValue(); + + assert.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTest string policy description\npfm_name\nTestStringPolicy\npfm_title\nTestStringPolicy\npfm_type\nstring'); + }); + + test('should render profile manifest value with translations', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const translations: LanguageTranslations = { + '': { + 'test.policy.description': 'Translated manifest description' + } + }; + + const manifestValue = policy.renderProfileManifestValue(translations); + + assert.strictEqual(manifestValue, 'pfm_default\n\npfm_description\nTranslated manifest description\npfm_name\nTestStringPolicy\npfm_title\nTestStringPolicy\npfm_type\nstring'); + }); + + test('should render profile manifest correctly', () => { + const policy = StringPolicy.from(mockCategory, mockPolicy); + + assert.ok(policy); + + const manifest = policy.renderProfileManifest(); + + assert.strictEqual(manifest, '\npfm_default\n\npfm_description\nTest string policy description\npfm_name\nTestStringPolicy\npfm_title\nTestStringPolicy\npfm_type\nstring\n'); + }); +}); diff --git a/build/lib/treeshaking.js b/build/lib/treeshaking.js deleted file mode 100644 index d51eee91f1e35..0000000000000 --- a/build/lib/treeshaking.js +++ /dev/null @@ -1,911 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ShakeLevel = void 0; -exports.toStringShakeLevel = toStringShakeLevel; -exports.shake = shake; -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const TYPESCRIPT_LIB_FOLDER = path_1.default.dirname(require.resolve('typescript/lib/lib.d.ts')); -var ShakeLevel; -(function (ShakeLevel) { - ShakeLevel[ShakeLevel["Files"] = 0] = "Files"; - ShakeLevel[ShakeLevel["InnerFile"] = 1] = "InnerFile"; - ShakeLevel[ShakeLevel["ClassMembers"] = 2] = "ClassMembers"; -})(ShakeLevel || (exports.ShakeLevel = ShakeLevel = {})); -function toStringShakeLevel(shakeLevel) { - switch (shakeLevel) { - case 0 /* ShakeLevel.Files */: - return 'Files (0)'; - case 1 /* ShakeLevel.InnerFile */: - return 'InnerFile (1)'; - case 2 /* ShakeLevel.ClassMembers */: - return 'ClassMembers (2)'; - } -} -function printDiagnostics(options, diagnostics) { - for (const diag of diagnostics) { - let result = ''; - if (diag.file) { - result += `${path_1.default.join(options.sourcesRoot, diag.file.fileName)}`; - } - if (diag.file && diag.start) { - const location = diag.file.getLineAndCharacterOfPosition(diag.start); - result += `:${location.line + 1}:${location.character}`; - } - result += ` - ` + JSON.stringify(diag.messageText); - console.log(result); - } -} -function shake(options) { - const ts = require('typescript'); - const languageService = createTypeScriptLanguageService(ts, options); - const program = languageService.getProgram(); - const globalDiagnostics = program.getGlobalDiagnostics(); - if (globalDiagnostics.length > 0) { - printDiagnostics(options, globalDiagnostics); - throw new Error(`Compilation Errors encountered.`); - } - const syntacticDiagnostics = program.getSyntacticDiagnostics(); - if (syntacticDiagnostics.length > 0) { - printDiagnostics(options, syntacticDiagnostics); - throw new Error(`Compilation Errors encountered.`); - } - const semanticDiagnostics = program.getSemanticDiagnostics(); - if (semanticDiagnostics.length > 0) { - printDiagnostics(options, semanticDiagnostics); - throw new Error(`Compilation Errors encountered.`); - } - markNodes(ts, languageService, options); - return generateResult(ts, languageService, options.shakeLevel); -} -//#region Discovery, LanguageService & Setup -function createTypeScriptLanguageService(ts, options) { - // Discover referenced files - const FILES = discoverAndReadFiles(ts, options); - // Add fake usage files - options.inlineEntryPoints.forEach((inlineEntryPoint, index) => { - FILES[`inlineEntryPoint.${index}.ts`] = inlineEntryPoint; - }); - // Add additional typings - options.typings.forEach((typing) => { - const filePath = path_1.default.join(options.sourcesRoot, typing); - FILES[typing] = fs_1.default.readFileSync(filePath).toString(); - }); - // Resolve libs - const RESOLVED_LIBS = processLibFiles(ts, options); - const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options; - const host = new TypeScriptLanguageServiceHost(ts, RESOLVED_LIBS, FILES, compilerOptions); - return ts.createLanguageService(host); -} -/** - * Read imports and follow them until all files have been handled - */ -function discoverAndReadFiles(ts, options) { - const FILES = {}; - const in_queue = Object.create(null); - const queue = []; - const enqueue = (moduleId) => { - // To make the treeshaker work on windows... - moduleId = moduleId.replace(/\\/g, '/'); - if (in_queue[moduleId]) { - return; - } - in_queue[moduleId] = true; - queue.push(moduleId); - }; - options.entryPoints.forEach((entryPoint) => enqueue(entryPoint)); - while (queue.length > 0) { - const moduleId = queue.shift(); - let redirectedModuleId = moduleId; - if (options.redirects[moduleId]) { - redirectedModuleId = options.redirects[moduleId]; - } - const dts_filename = path_1.default.join(options.sourcesRoot, redirectedModuleId + '.d.ts'); - if (fs_1.default.existsSync(dts_filename)) { - const dts_filecontents = fs_1.default.readFileSync(dts_filename).toString(); - FILES[`${moduleId}.d.ts`] = dts_filecontents; - continue; - } - const js_filename = path_1.default.join(options.sourcesRoot, redirectedModuleId + '.js'); - if (fs_1.default.existsSync(js_filename)) { - // This is an import for a .js file, so ignore it... - continue; - } - const ts_filename = path_1.default.join(options.sourcesRoot, redirectedModuleId + '.ts'); - const ts_filecontents = fs_1.default.readFileSync(ts_filename).toString(); - const info = ts.preProcessFile(ts_filecontents); - for (let i = info.importedFiles.length - 1; i >= 0; i--) { - const importedFileName = info.importedFiles[i].fileName; - if (options.importIgnorePattern.test(importedFileName)) { - // Ignore *.css imports - continue; - } - let importedModuleId = importedFileName; - if (/(^\.\/)|(^\.\.\/)/.test(importedModuleId)) { - importedModuleId = path_1.default.join(path_1.default.dirname(moduleId), importedModuleId); - if (importedModuleId.endsWith('.js')) { // ESM: code imports require to be relative and have a '.js' file extension - importedModuleId = importedModuleId.substr(0, importedModuleId.length - 3); - } - } - enqueue(importedModuleId); - } - FILES[`${moduleId}.ts`] = ts_filecontents; - } - return FILES; -} -/** - * Read lib files and follow lib references - */ -function processLibFiles(ts, options) { - const stack = [...options.compilerOptions.lib]; - const result = {}; - while (stack.length > 0) { - const filename = `lib.${stack.shift().toLowerCase()}.d.ts`; - const key = `defaultLib:${filename}`; - if (!result[key]) { - // add this file - const filepath = path_1.default.join(TYPESCRIPT_LIB_FOLDER, filename); - const sourceText = fs_1.default.readFileSync(filepath).toString(); - result[key] = sourceText; - // precess dependencies and "recurse" - const info = ts.preProcessFile(sourceText); - for (const ref of info.libReferenceDirectives) { - stack.push(ref.fileName); - } - } - } - return result; -} -/** - * A TypeScript language service host - */ -class TypeScriptLanguageServiceHost { - _ts; - _libs; - _files; - _compilerOptions; - constructor(ts, libs, files, compilerOptions) { - this._ts = ts; - this._libs = libs; - this._files = files; - this._compilerOptions = compilerOptions; - } - // --- language service host --------------- - getCompilationSettings() { - return this._compilerOptions; - } - getScriptFileNames() { - return ([] - .concat(Object.keys(this._libs)) - .concat(Object.keys(this._files))); - } - getScriptVersion(_fileName) { - return '1'; - } - getProjectVersion() { - return '1'; - } - getScriptSnapshot(fileName) { - if (this._files.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files[fileName]); - } - else if (this._libs.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs[fileName]); - } - else { - return this._ts.ScriptSnapshot.fromString(''); - } - } - getScriptKind(_fileName) { - return this._ts.ScriptKind.TS; - } - getCurrentDirectory() { - return ''; - } - getDefaultLibFileName(_options) { - return 'defaultLib:lib.d.ts'; - } - isDefaultLibFileName(fileName) { - return fileName === this.getDefaultLibFileName(this._compilerOptions); - } - readFile(path, _encoding) { - return this._files[path] || this._libs[path]; - } - fileExists(path) { - return path in this._files || path in this._libs; - } -} -//#endregion -//#region Tree Shaking -var NodeColor; -(function (NodeColor) { - NodeColor[NodeColor["White"] = 0] = "White"; - NodeColor[NodeColor["Gray"] = 1] = "Gray"; - NodeColor[NodeColor["Black"] = 2] = "Black"; -})(NodeColor || (NodeColor = {})); -function getColor(node) { - return node.$$$color || 0 /* NodeColor.White */; -} -function setColor(node, color) { - node.$$$color = color; -} -function markNeededSourceFile(node) { - node.$$$neededSourceFile = true; -} -function isNeededSourceFile(node) { - return Boolean(node.$$$neededSourceFile); -} -function nodeOrParentIsBlack(node) { - while (node) { - const color = getColor(node); - if (color === 2 /* NodeColor.Black */) { - return true; - } - node = node.parent; - } - return false; -} -function nodeOrChildIsBlack(node) { - if (getColor(node) === 2 /* NodeColor.Black */) { - return true; - } - for (const child of node.getChildren()) { - if (nodeOrChildIsBlack(child)) { - return true; - } - } - return false; -} -function isSymbolWithDeclarations(symbol) { - return !!(symbol && symbol.declarations); -} -function isVariableStatementWithSideEffects(ts, node) { - if (!ts.isVariableStatement(node)) { - return false; - } - let hasSideEffects = false; - const visitNode = (node) => { - if (hasSideEffects) { - // no need to go on - return; - } - if (ts.isCallExpression(node) || ts.isNewExpression(node)) { - // TODO: assuming `createDecorator` and `refineServiceDecorator` calls are side-effect free - const isSideEffectFree = /(createDecorator|refineServiceDecorator)/.test(node.expression.getText()); - if (!isSideEffectFree) { - hasSideEffects = true; - } - } - node.forEachChild(visitNode); - }; - node.forEachChild(visitNode); - return hasSideEffects; -} -function isStaticMemberWithSideEffects(ts, node) { - if (!ts.isPropertyDeclaration(node)) { - return false; - } - if (!node.modifiers) { - return false; - } - if (!node.modifiers.some(mod => mod.kind === ts.SyntaxKind.StaticKeyword)) { - return false; - } - let hasSideEffects = false; - const visitNode = (node) => { - if (hasSideEffects) { - // no need to go on - return; - } - if (ts.isCallExpression(node) || ts.isNewExpression(node)) { - hasSideEffects = true; - } - node.forEachChild(visitNode); - }; - node.forEachChild(visitNode); - return hasSideEffects; -} -function markNodes(ts, languageService, options) { - const program = languageService.getProgram(); - if (!program) { - throw new Error('Could not get program from language service'); - } - if (options.shakeLevel === 0 /* ShakeLevel.Files */) { - // Mark all source files Black - program.getSourceFiles().forEach((sourceFile) => { - setColor(sourceFile, 2 /* NodeColor.Black */); - }); - return; - } - const black_queue = []; - const gray_queue = []; - const export_import_queue = []; - const sourceFilesLoaded = {}; - function enqueueTopLevelModuleStatements(sourceFile) { - sourceFile.forEachChild((node) => { - if (ts.isImportDeclaration(node)) { - if (!node.importClause && ts.isStringLiteral(node.moduleSpecifier)) { - setColor(node, 2 /* NodeColor.Black */); - enqueueImport(node, node.moduleSpecifier.text); - } - return; - } - if (ts.isExportDeclaration(node)) { - if (!node.exportClause && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) { - // export * from "foo"; - setColor(node, 2 /* NodeColor.Black */); - enqueueImport(node, node.moduleSpecifier.text); - } - if (node.exportClause && ts.isNamedExports(node.exportClause)) { - for (const exportSpecifier of node.exportClause.elements) { - export_import_queue.push(exportSpecifier); - } - } - return; - } - if (isVariableStatementWithSideEffects(ts, node)) { - enqueue_black(node); - } - if (ts.isExpressionStatement(node) - || ts.isIfStatement(node) - || ts.isIterationStatement(node, true) - || ts.isExportAssignment(node)) { - enqueue_black(node); - } - if (ts.isImportEqualsDeclaration(node)) { - if (/export/.test(node.getFullText(sourceFile))) { - // e.g. "export import Severity = BaseSeverity;" - enqueue_black(node); - } - } - }); - } - /** - * Return the parent of `node` which is an ImportDeclaration - */ - function findParentImportDeclaration(node) { - let _node = node; - do { - if (ts.isImportDeclaration(_node)) { - return _node; - } - _node = _node.parent; - } while (_node); - return null; - } - function enqueue_gray(node) { - if (nodeOrParentIsBlack(node) || getColor(node) === 1 /* NodeColor.Gray */) { - return; - } - setColor(node, 1 /* NodeColor.Gray */); - gray_queue.push(node); - } - function enqueue_black(node) { - const previousColor = getColor(node); - if (previousColor === 2 /* NodeColor.Black */) { - return; - } - if (previousColor === 1 /* NodeColor.Gray */) { - // remove from gray queue - gray_queue.splice(gray_queue.indexOf(node), 1); - setColor(node, 0 /* NodeColor.White */); - // add to black queue - enqueue_black(node); - // move from one queue to the other - // black_queue.push(node); - // setColor(node, NodeColor.Black); - return; - } - if (nodeOrParentIsBlack(node)) { - return; - } - const fileName = node.getSourceFile().fileName; - if (/^defaultLib:/.test(fileName) || /\.d\.ts$/.test(fileName)) { - setColor(node, 2 /* NodeColor.Black */); - return; - } - const sourceFile = node.getSourceFile(); - if (!sourceFilesLoaded[sourceFile.fileName]) { - sourceFilesLoaded[sourceFile.fileName] = true; - enqueueTopLevelModuleStatements(sourceFile); - } - if (ts.isSourceFile(node)) { - return; - } - setColor(node, 2 /* NodeColor.Black */); - black_queue.push(node); - if (options.shakeLevel === 2 /* ShakeLevel.ClassMembers */ && (ts.isMethodDeclaration(node) || ts.isMethodSignature(node) || ts.isPropertySignature(node) || ts.isPropertyDeclaration(node) || ts.isGetAccessor(node) || ts.isSetAccessor(node))) { - const references = languageService.getReferencesAtPosition(node.getSourceFile().fileName, node.name.pos + node.name.getLeadingTriviaWidth()); - if (references) { - for (let i = 0, len = references.length; i < len; i++) { - const reference = references[i]; - const referenceSourceFile = program.getSourceFile(reference.fileName); - if (!referenceSourceFile) { - continue; - } - const referenceNode = getTokenAtPosition(ts, referenceSourceFile, reference.textSpan.start, false, false); - if (ts.isMethodDeclaration(referenceNode.parent) - || ts.isPropertyDeclaration(referenceNode.parent) - || ts.isGetAccessor(referenceNode.parent) - || ts.isSetAccessor(referenceNode.parent)) { - enqueue_gray(referenceNode.parent); - } - } - } - } - } - function enqueueFile(filename) { - const sourceFile = program.getSourceFile(filename); - if (!sourceFile) { - console.warn(`Cannot find source file ${filename}`); - return; - } - // This source file should survive even if it is empty - markNeededSourceFile(sourceFile); - enqueue_black(sourceFile); - } - function enqueueImport(node, importText) { - if (options.importIgnorePattern.test(importText)) { - // this import should be ignored - return; - } - const nodeSourceFile = node.getSourceFile(); - let fullPath; - if (/(^\.\/)|(^\.\.\/)/.test(importText)) { - if (importText.endsWith('.js')) { // ESM: code imports require to be relative and to have a '.js' file extension - importText = importText.substr(0, importText.length - 3); - } - fullPath = path_1.default.join(path_1.default.dirname(nodeSourceFile.fileName), importText) + '.ts'; - } - else { - fullPath = importText + '.ts'; - } - enqueueFile(fullPath); - } - options.entryPoints.forEach(moduleId => enqueueFile(moduleId + '.ts')); - // Add fake usage files - options.inlineEntryPoints.forEach((_, index) => enqueueFile(`inlineEntryPoint.${index}.ts`)); - let step = 0; - const checker = program.getTypeChecker(); - while (black_queue.length > 0 || gray_queue.length > 0) { - ++step; - let node; - if (step % 100 === 0) { - console.log(`Treeshaking - ${Math.floor(100 * step / (step + black_queue.length + gray_queue.length))}% - ${step}/${step + black_queue.length + gray_queue.length} (${black_queue.length}, ${gray_queue.length})`); - } - if (black_queue.length === 0) { - for (let i = 0; i < gray_queue.length; i++) { - const node = gray_queue[i]; - const nodeParent = node.parent; - if ((ts.isClassDeclaration(nodeParent) || ts.isInterfaceDeclaration(nodeParent)) && nodeOrChildIsBlack(nodeParent)) { - gray_queue.splice(i, 1); - black_queue.push(node); - setColor(node, 2 /* NodeColor.Black */); - i--; - } - } - } - if (black_queue.length > 0) { - node = black_queue.shift(); - } - else { - // only gray nodes remaining... - break; - } - const nodeSourceFile = node.getSourceFile(); - const loop = (node) => { - const symbols = getRealNodeSymbol(ts, checker, node); - for (const { symbol, symbolImportNode } of symbols) { - if (symbolImportNode) { - setColor(symbolImportNode, 2 /* NodeColor.Black */); - const importDeclarationNode = findParentImportDeclaration(symbolImportNode); - if (importDeclarationNode && ts.isStringLiteral(importDeclarationNode.moduleSpecifier)) { - enqueueImport(importDeclarationNode, importDeclarationNode.moduleSpecifier.text); - } - } - if (isSymbolWithDeclarations(symbol) && !nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol)) { - for (let i = 0, len = symbol.declarations.length; i < len; i++) { - const declaration = symbol.declarations[i]; - if (ts.isSourceFile(declaration)) { - // Do not enqueue full source files - // (they can be the declaration of a module import) - continue; - } - if (options.shakeLevel === 2 /* ShakeLevel.ClassMembers */ && (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && !isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration)) { - enqueue_black(declaration.name); - for (let j = 0; j < declaration.members.length; j++) { - const member = declaration.members[j]; - const memberName = member.name ? member.name.getText() : null; - if (ts.isConstructorDeclaration(member) - || ts.isConstructSignatureDeclaration(member) - || ts.isIndexSignatureDeclaration(member) - || ts.isCallSignatureDeclaration(member) - || memberName === '[Symbol.iterator]' - || memberName === '[Symbol.toStringTag]' - || memberName === 'toJSON' - || memberName === 'toString' - || memberName === 'dispose' // TODO: keeping all `dispose` methods - || /^_(.*)Brand$/.test(memberName || '') // TODO: keeping all members ending with `Brand`... - ) { - enqueue_black(member); - } - if (isStaticMemberWithSideEffects(ts, member)) { - enqueue_black(member); - } - } - // queue the heritage clauses - if (declaration.heritageClauses) { - for (const heritageClause of declaration.heritageClauses) { - enqueue_black(heritageClause); - } - } - } - else { - enqueue_black(declaration); - } - } - } - } - node.forEachChild(loop); - }; - node.forEachChild(loop); - } - while (export_import_queue.length > 0) { - const node = export_import_queue.shift(); - if (nodeOrParentIsBlack(node)) { - continue; - } - const symbol = node.symbol; - if (!symbol) { - continue; - } - const aliased = checker.getAliasedSymbol(symbol); - if (aliased.declarations && aliased.declarations.length > 0) { - if (nodeOrParentIsBlack(aliased.declarations[0]) || nodeOrChildIsBlack(aliased.declarations[0])) { - setColor(node, 2 /* NodeColor.Black */); - } - } - } -} -function nodeIsInItsOwnDeclaration(nodeSourceFile, node, symbol) { - for (let i = 0, len = symbol.declarations.length; i < len; i++) { - const declaration = symbol.declarations[i]; - const declarationSourceFile = declaration.getSourceFile(); - if (nodeSourceFile === declarationSourceFile) { - if (declaration.pos <= node.pos && node.end <= declaration.end) { - return true; - } - } - } - return false; -} -function generateResult(ts, languageService, shakeLevel) { - const program = languageService.getProgram(); - if (!program) { - throw new Error('Could not get program from language service'); - } - const result = {}; - const writeFile = (filePath, contents) => { - result[filePath] = contents; - }; - program.getSourceFiles().forEach((sourceFile) => { - const fileName = sourceFile.fileName; - if (/^defaultLib:/.test(fileName)) { - return; - } - const destination = fileName; - if (/\.d\.ts$/.test(fileName)) { - if (nodeOrChildIsBlack(sourceFile)) { - writeFile(destination, sourceFile.text); - } - return; - } - const text = sourceFile.text; - let result = ''; - function keep(node) { - result += text.substring(node.pos, node.end); - } - function write(data) { - result += data; - } - function writeMarkedNodes(node) { - if (getColor(node) === 2 /* NodeColor.Black */) { - return keep(node); - } - // Always keep certain top-level statements - if (ts.isSourceFile(node.parent)) { - if (ts.isExpressionStatement(node) && ts.isStringLiteral(node.expression) && node.expression.text === 'use strict') { - return keep(node); - } - if (ts.isVariableStatement(node) && nodeOrChildIsBlack(node)) { - return keep(node); - } - } - // Keep the entire import in import * as X cases - if (ts.isImportDeclaration(node)) { - if (node.importClause && node.importClause.namedBindings) { - if (ts.isNamespaceImport(node.importClause.namedBindings)) { - if (getColor(node.importClause.namedBindings) === 2 /* NodeColor.Black */) { - return keep(node); - } - } - else { - const survivingImports = []; - for (const importNode of node.importClause.namedBindings.elements) { - if (getColor(importNode) === 2 /* NodeColor.Black */) { - survivingImports.push(importNode.getFullText(sourceFile)); - } - } - const leadingTriviaWidth = node.getLeadingTriviaWidth(); - const leadingTrivia = sourceFile.text.substr(node.pos, leadingTriviaWidth); - if (survivingImports.length > 0) { - if (node.importClause && node.importClause.name && getColor(node.importClause) === 2 /* NodeColor.Black */) { - return write(`${leadingTrivia}import ${node.importClause.name.text}, {${survivingImports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); - } - return write(`${leadingTrivia}import {${survivingImports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); - } - else { - if (node.importClause && node.importClause.name && getColor(node.importClause) === 2 /* NodeColor.Black */) { - return write(`${leadingTrivia}import ${node.importClause.name.text} from${node.moduleSpecifier.getFullText(sourceFile)};`); - } - } - } - } - else { - if (node.importClause && getColor(node.importClause) === 2 /* NodeColor.Black */) { - return keep(node); - } - } - } - if (ts.isExportDeclaration(node)) { - if (node.exportClause && node.moduleSpecifier && ts.isNamedExports(node.exportClause)) { - const survivingExports = []; - for (const exportSpecifier of node.exportClause.elements) { - if (getColor(exportSpecifier) === 2 /* NodeColor.Black */) { - survivingExports.push(exportSpecifier.getFullText(sourceFile)); - } - } - const leadingTriviaWidth = node.getLeadingTriviaWidth(); - const leadingTrivia = sourceFile.text.substr(node.pos, leadingTriviaWidth); - if (survivingExports.length > 0) { - return write(`${leadingTrivia}export {${survivingExports.join(',')} } from${node.moduleSpecifier.getFullText(sourceFile)};`); - } - } - } - if (shakeLevel === 2 /* ShakeLevel.ClassMembers */ && (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) && nodeOrChildIsBlack(node)) { - let toWrite = node.getFullText(); - for (let i = node.members.length - 1; i >= 0; i--) { - const member = node.members[i]; - if (getColor(member) === 2 /* NodeColor.Black */ || !member.name) { - // keep method - continue; - } - const pos = member.pos - node.pos; - const end = member.end - node.pos; - toWrite = toWrite.substring(0, pos) + toWrite.substring(end); - } - return write(toWrite); - } - if (ts.isFunctionDeclaration(node)) { - // Do not go inside functions if they haven't been marked - return; - } - node.forEachChild(writeMarkedNodes); - } - if (getColor(sourceFile) !== 2 /* NodeColor.Black */) { - if (!nodeOrChildIsBlack(sourceFile)) { - // none of the elements are reachable - if (isNeededSourceFile(sourceFile)) { - // this source file must be written, even if nothing is used from it - // because there is an import somewhere for it. - // However, TS complains with empty files with the error "x" is not a module, - // so we will export a dummy variable - result = 'export const __dummy = 0;'; - } - else { - // don't write this file at all! - return; - } - } - else { - sourceFile.forEachChild(writeMarkedNodes); - result += sourceFile.endOfFileToken.getFullText(sourceFile); - } - } - else { - result = text; - } - writeFile(destination, result); - }); - return result; -} -//#endregion -//#region Utils -function isLocalCodeExtendingOrInheritingFromDefaultLibSymbol(ts, program, checker, declaration) { - if (!program.isSourceFileDefaultLibrary(declaration.getSourceFile()) && declaration.heritageClauses) { - for (const heritageClause of declaration.heritageClauses) { - for (const type of heritageClause.types) { - const symbol = findSymbolFromHeritageType(ts, checker, type); - if (symbol) { - const decl = symbol.valueDeclaration || (symbol.declarations && symbol.declarations[0]); - if (decl && program.isSourceFileDefaultLibrary(decl.getSourceFile())) { - return true; - } - } - } - } - } - return false; -} -function findSymbolFromHeritageType(ts, checker, type) { - if (ts.isExpressionWithTypeArguments(type)) { - return findSymbolFromHeritageType(ts, checker, type.expression); - } - if (ts.isIdentifier(type)) { - const tmp = getRealNodeSymbol(ts, checker, type); - return (tmp.length > 0 ? tmp[0].symbol : null); - } - if (ts.isPropertyAccessExpression(type)) { - return findSymbolFromHeritageType(ts, checker, type.name); - } - return null; -} -class SymbolImportTuple { - symbol; - symbolImportNode; - constructor(symbol, symbolImportNode) { - this.symbol = symbol; - this.symbolImportNode = symbolImportNode; - } -} -/** - * Returns the node's symbol and the `import` node (if the symbol resolved from a different module) - */ -function getRealNodeSymbol(ts, checker, node) { - const getPropertySymbolsFromContextualType = ts.getPropertySymbolsFromContextualType; - const getContainingObjectLiteralElement = ts.getContainingObjectLiteralElement; - const getNameFromPropertyName = ts.getNameFromPropertyName; - // Go to the original declaration for cases: - // - // (1) when the aliased symbol was declared in the location(parent). - // (2) when the aliased symbol is originating from an import. - // - function shouldSkipAlias(node, declaration) { - if (!ts.isShorthandPropertyAssignment(node) && node.kind !== ts.SyntaxKind.Identifier) { - return false; - } - if (node.parent === declaration) { - return true; - } - switch (declaration.kind) { - case ts.SyntaxKind.ImportClause: - case ts.SyntaxKind.ImportEqualsDeclaration: - return true; - case ts.SyntaxKind.ImportSpecifier: - return declaration.parent.kind === ts.SyntaxKind.NamedImports; - default: - return false; - } - } - if (!ts.isShorthandPropertyAssignment(node)) { - if (node.getChildCount() !== 0) { - return []; - } - } - const { parent } = node; - let symbol = (ts.isShorthandPropertyAssignment(node) - ? checker.getShorthandAssignmentValueSymbol(node) - : checker.getSymbolAtLocation(node)); - let importNode = null; - // If this is an alias, and the request came at the declaration location - // get the aliased symbol instead. This allows for goto def on an import e.g. - // import {A, B} from "mod"; - // to jump to the implementation directly. - if (symbol && symbol.flags & ts.SymbolFlags.Alias && symbol.declarations && shouldSkipAlias(node, symbol.declarations[0])) { - const aliased = checker.getAliasedSymbol(symbol); - if (aliased.declarations) { - // We should mark the import as visited - importNode = symbol.declarations[0]; - symbol = aliased; - } - } - if (symbol) { - // Because name in short-hand property assignment has two different meanings: property name and property value, - // using go-to-definition at such position should go to the variable declaration of the property value rather than - // go to the declaration of the property name (in this case stay at the same position). However, if go-to-definition - // is performed at the location of property access, we would like to go to definition of the property in the short-hand - // assignment. This case and others are handled by the following code. - if (node.parent.kind === ts.SyntaxKind.ShorthandPropertyAssignment) { - symbol = checker.getShorthandAssignmentValueSymbol(symbol.valueDeclaration); - } - // If the node is the name of a BindingElement within an ObjectBindingPattern instead of just returning the - // declaration the symbol (which is itself), we should try to get to the original type of the ObjectBindingPattern - // and return the property declaration for the referenced property. - // For example: - // import('./foo').then(({ b/*goto*/ar }) => undefined); => should get use to the declaration in file "./foo" - // - // function bar(onfulfilled: (value: T) => void) { //....} - // interface Test { - // pr/*destination*/op1: number - // } - // bar(({pr/*goto*/op1})=>{}); - if (ts.isPropertyName(node) && ts.isBindingElement(parent) && ts.isObjectBindingPattern(parent.parent) && - (node === (parent.propertyName || parent.name))) { - const name = getNameFromPropertyName(node); - const type = checker.getTypeAtLocation(parent.parent); - if (name && type) { - if (type.isUnion()) { - return generateMultipleSymbols(type, name, importNode); - } - else { - const prop = type.getProperty(name); - if (prop) { - symbol = prop; - } - } - } - } - // If the current location we want to find its definition is in an object literal, try to get the contextual type for the - // object literal, lookup the property symbol in the contextual type, and use this for goto-definition. - // For example - // interface Props{ - // /*first*/prop1: number - // prop2: boolean - // } - // function Foo(arg: Props) {} - // Foo( { pr/*1*/op1: 10, prop2: false }) - const element = getContainingObjectLiteralElement(node); - if (element) { - const contextualType = element && checker.getContextualType(element.parent); - if (contextualType) { - const propertySymbols = getPropertySymbolsFromContextualType(element, checker, contextualType, /*unionSymbolOk*/ false); - if (propertySymbols) { - symbol = propertySymbols[0]; - } - } - } - } - if (symbol && symbol.declarations) { - return [new SymbolImportTuple(symbol, importNode)]; - } - return []; - function generateMultipleSymbols(type, name, importNode) { - const result = []; - for (const t of type.types) { - const prop = t.getProperty(name); - if (prop && prop.declarations) { - result.push(new SymbolImportTuple(prop, importNode)); - } - } - return result; - } -} -/** Get the token whose text contains the position */ -function getTokenAtPosition(ts, sourceFile, position, allowPositionInLeadingTrivia, includeEndPosition) { - let current = sourceFile; - outer: while (true) { - // find the child that contains 'position' - for (const child of current.getChildren()) { - const start = allowPositionInLeadingTrivia ? child.getFullStart() : child.getStart(sourceFile, /*includeJsDoc*/ true); - if (start > position) { - // If this child begins after position, then all subsequent children will as well. - break; - } - const end = child.getEnd(); - if (position < end || (position === end && (child.kind === ts.SyntaxKind.EndOfFileToken || includeEndPosition))) { - current = child; - continue outer; - } - } - return current; - } -} -//#endregion -//# sourceMappingURL=treeshaking.js.map \ No newline at end of file diff --git a/build/lib/treeshaking.ts b/build/lib/treeshaking.ts index ac71bb205da74..463e701f73f67 100644 --- a/build/lib/treeshaking.ts +++ b/build/lib/treeshaking.ts @@ -5,15 +5,16 @@ import fs from 'fs'; import path from 'path'; -import type * as ts from 'typescript'; +import * as ts from 'typescript'; +import { type IFileMap, TypeScriptLanguageServiceHost } from './typeScriptLanguageServiceHost.ts'; -const TYPESCRIPT_LIB_FOLDER = path.dirname(require.resolve('typescript/lib/lib.d.ts')); +const ShakeLevel = Object.freeze({ + Files: 0, + InnerFile: 1, + ClassMembers: 2 +}); -export const enum ShakeLevel { - Files = 0, - InnerFile = 1, - ClassMembers = 2 -} +type ShakeLevel = typeof ShakeLevel[keyof typeof ShakeLevel]; export function toStringShakeLevel(shakeLevel: ShakeLevel): string { switch (shakeLevel) { @@ -56,8 +57,6 @@ export interface ITreeShakingOptions { * regex pattern to ignore certain imports e.g. `.css` imports */ importIgnorePattern: RegExp; - - redirects: { [module: string]: string }; } export interface ITreeShakingResult { @@ -80,7 +79,6 @@ function printDiagnostics(options: ITreeShakingOptions, diagnostics: ReadonlyArr } export function shake(options: ITreeShakingOptions): ITreeShakingResult { - const ts = require('typescript') as typeof import('typescript'); const languageService = createTypeScriptLanguageService(ts, options); const program = languageService.getProgram()!; @@ -110,213 +108,67 @@ export function shake(options: ITreeShakingOptions): ITreeShakingResult { //#region Discovery, LanguageService & Setup function createTypeScriptLanguageService(ts: typeof import('typescript'), options: ITreeShakingOptions): ts.LanguageService { // Discover referenced files - const FILES = discoverAndReadFiles(ts, options); + const FILES: IFileMap = new Map(); + + // Add entrypoints + options.entryPoints.forEach(entryPoint => { + const filePath = path.join(options.sourcesRoot, entryPoint); + FILES.set(path.normalize(filePath), fs.readFileSync(filePath).toString()); + }); // Add fake usage files options.inlineEntryPoints.forEach((inlineEntryPoint, index) => { - FILES[`inlineEntryPoint.${index}.ts`] = inlineEntryPoint; + FILES.set(path.normalize(path.join(options.sourcesRoot, `inlineEntryPoint.${index}.ts`)), inlineEntryPoint); }); // Add additional typings options.typings.forEach((typing) => { const filePath = path.join(options.sourcesRoot, typing); - FILES[typing] = fs.readFileSync(filePath).toString(); + FILES.set(path.normalize(filePath), fs.readFileSync(filePath).toString()); }); - // Resolve libs - const RESOLVED_LIBS = processLibFiles(ts, options); - - const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, options.sourcesRoot).options; - - const host = new TypeScriptLanguageServiceHost(ts, RESOLVED_LIBS, FILES, compilerOptions); + const basePath = path.join(options.sourcesRoot, '..'); + const compilerOptions = ts.convertCompilerOptionsFromJson(options.compilerOptions, basePath).options; + const host = new TypeScriptLanguageServiceHost(ts, FILES, compilerOptions); return ts.createLanguageService(host); } -/** - * Read imports and follow them until all files have been handled - */ -function discoverAndReadFiles(ts: typeof import('typescript'), options: ITreeShakingOptions): IFileMap { - const FILES: IFileMap = {}; - - const in_queue: { [module: string]: boolean } = Object.create(null); - const queue: string[] = []; - - const enqueue = (moduleId: string) => { - // To make the treeshaker work on windows... - moduleId = moduleId.replace(/\\/g, '/'); - if (in_queue[moduleId]) { - return; - } - in_queue[moduleId] = true; - queue.push(moduleId); - }; - - options.entryPoints.forEach((entryPoint) => enqueue(entryPoint)); - - while (queue.length > 0) { - const moduleId = queue.shift()!; - let redirectedModuleId: string = moduleId; - if (options.redirects[moduleId]) { - redirectedModuleId = options.redirects[moduleId]; - } - - const dts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.d.ts'); - if (fs.existsSync(dts_filename)) { - const dts_filecontents = fs.readFileSync(dts_filename).toString(); - FILES[`${moduleId}.d.ts`] = dts_filecontents; - continue; - } - - - const js_filename = path.join(options.sourcesRoot, redirectedModuleId + '.js'); - if (fs.existsSync(js_filename)) { - // This is an import for a .js file, so ignore it... - continue; - } - - const ts_filename = path.join(options.sourcesRoot, redirectedModuleId + '.ts'); - - const ts_filecontents = fs.readFileSync(ts_filename).toString(); - const info = ts.preProcessFile(ts_filecontents); - for (let i = info.importedFiles.length - 1; i >= 0; i--) { - const importedFileName = info.importedFiles[i].fileName; - - if (options.importIgnorePattern.test(importedFileName)) { - // Ignore *.css imports - continue; - } - - let importedModuleId = importedFileName; - if (/(^\.\/)|(^\.\.\/)/.test(importedModuleId)) { - importedModuleId = path.join(path.dirname(moduleId), importedModuleId); - if (importedModuleId.endsWith('.js')) { // ESM: code imports require to be relative and have a '.js' file extension - importedModuleId = importedModuleId.substr(0, importedModuleId.length - 3); - } - } - enqueue(importedModuleId); - } - - FILES[`${moduleId}.ts`] = ts_filecontents; - } - - return FILES; -} - -/** - * Read lib files and follow lib references - */ -function processLibFiles(ts: typeof import('typescript'), options: ITreeShakingOptions): ILibMap { - - const stack: string[] = [...options.compilerOptions.lib]; - const result: ILibMap = {}; - - while (stack.length > 0) { - const filename = `lib.${stack.shift()!.toLowerCase()}.d.ts`; - const key = `defaultLib:${filename}`; - if (!result[key]) { - // add this file - const filepath = path.join(TYPESCRIPT_LIB_FOLDER, filename); - const sourceText = fs.readFileSync(filepath).toString(); - result[key] = sourceText; - - // precess dependencies and "recurse" - const info = ts.preProcessFile(sourceText); - for (const ref of info.libReferenceDirectives) { - stack.push(ref.fileName); - } - } - } - - return result; -} +//#endregion -interface ILibMap { [libName: string]: string } -interface IFileMap { [fileName: string]: string } +//#region Tree Shaking -/** - * A TypeScript language service host - */ -class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { - - private readonly _ts: typeof import('typescript'); - private readonly _libs: ILibMap; - private readonly _files: IFileMap; - private readonly _compilerOptions: ts.CompilerOptions; - - constructor(ts: typeof import('typescript'), libs: ILibMap, files: IFileMap, compilerOptions: ts.CompilerOptions) { - this._ts = ts; - this._libs = libs; - this._files = files; - this._compilerOptions = compilerOptions; - } +const NodeColor = Object.freeze({ + White: 0, + Gray: 1, + Black: 2 +}); +type NodeColor = typeof NodeColor[keyof typeof NodeColor]; - // --- language service host --------------- +type ObjectLiteralElementWithName = ts.ObjectLiteralElement & { name: ts.PropertyName; parent: ts.ObjectLiteralExpression | ts.JsxAttributes }; - getCompilationSettings(): ts.CompilerOptions { - return this._compilerOptions; - } - getScriptFileNames(): string[] { - return ( - ([] as string[]) - .concat(Object.keys(this._libs)) - .concat(Object.keys(this._files)) - ); - } - getScriptVersion(_fileName: string): string { - return '1'; - } - getProjectVersion(): string { - return '1'; - } - getScriptSnapshot(fileName: string): ts.IScriptSnapshot { - if (this._files.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._files[fileName]); - } else if (this._libs.hasOwnProperty(fileName)) { - return this._ts.ScriptSnapshot.fromString(this._libs[fileName]); - } else { - return this._ts.ScriptSnapshot.fromString(''); - } +declare module 'typescript' { + interface Node { + $$$color?: NodeColor; + $$$neededSourceFile?: boolean; + symbol?: ts.Symbol; } - getScriptKind(_fileName: string): ts.ScriptKind { - return this._ts.ScriptKind.TS; - } - getCurrentDirectory(): string { - return ''; - } - getDefaultLibFileName(_options: ts.CompilerOptions): string { - return 'defaultLib:lib.d.ts'; - } - isDefaultLibFileName(fileName: string): boolean { - return fileName === this.getDefaultLibFileName(this._compilerOptions); - } - readFile(path: string, _encoding?: string): string | undefined { - return this._files[path] || this._libs[path]; - } - fileExists(path: string): boolean { - return path in this._files || path in this._libs; - } -} -//#endregion - -//#region Tree Shaking -const enum NodeColor { - White = 0, - Gray = 1, - Black = 2 + function getContainingObjectLiteralElement(node: ts.Node): ObjectLiteralElementWithName | undefined; + function getNameFromPropertyName(name: ts.PropertyName): string | undefined; + function getPropertySymbolsFromContextualType(node: ObjectLiteralElementWithName, checker: ts.TypeChecker, contextualType: ts.Type, unionSymbolOk: boolean): ReadonlyArray; } function getColor(node: ts.Node): NodeColor { - return (node).$$$color || NodeColor.White; + return node.$$$color || NodeColor.White; } function setColor(node: ts.Node, color: NodeColor): void { - (node).$$$color = color; + node.$$$color = color; } function markNeededSourceFile(node: ts.SourceFile): void { - (node).$$$neededSourceFile = true; + node.$$$neededSourceFile = true; } function isNeededSourceFile(node: ts.SourceFile): boolean { - return Boolean((node).$$$neededSourceFile); + return Boolean(node.$$$neededSourceFile); } function nodeOrParentIsBlack(node: ts.Node): boolean { while (node) { @@ -573,16 +425,23 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language if (importText.endsWith('.js')) { // ESM: code imports require to be relative and to have a '.js' file extension importText = importText.substr(0, importText.length - 3); } - fullPath = path.join(path.dirname(nodeSourceFile.fileName), importText) + '.ts'; + fullPath = path.join(path.dirname(nodeSourceFile.fileName), importText); } else { - fullPath = importText + '.ts'; + fullPath = importText; } + + if (fs.existsSync(fullPath + '.ts')) { + fullPath = fullPath + '.ts'; + } else { + fullPath = fullPath + '.js'; + } + enqueueFile(fullPath); } - options.entryPoints.forEach(moduleId => enqueueFile(moduleId + '.ts')); + options.entryPoints.forEach(moduleId => enqueueFile(path.join(options.sourcesRoot, moduleId))); // Add fake usage files - options.inlineEntryPoints.forEach((_, index) => enqueueFile(`inlineEntryPoint.${index}.ts`)); + options.inlineEntryPoints.forEach((_, index) => enqueueFile(path.join(options.sourcesRoot, `inlineEntryPoint.${index}.ts`))); let step = 0; @@ -684,11 +543,10 @@ function markNodes(ts: typeof import('typescript'), languageService: ts.Language if (nodeOrParentIsBlack(node)) { continue; } - const symbol: ts.Symbol | undefined = (node).symbol; - if (!symbol) { + if (!node.symbol) { continue; } - const aliased = checker.getAliasedSymbol(symbol); + const aliased = checker.getAliasedSymbol(node.symbol); if (aliased.declarations && aliased.declarations.length > 0) { if (nodeOrParentIsBlack(aliased.declarations[0]) || nodeOrChildIsBlack(aliased.declarations[0])) { setColor(node, NodeColor.Black); @@ -899,10 +757,16 @@ function findSymbolFromHeritageType(ts: typeof import('typescript'), checker: ts } class SymbolImportTuple { + public readonly symbol: ts.Symbol | null; + public readonly symbolImportNode: ts.Declaration | null; + constructor( - public readonly symbol: ts.Symbol | null, - public readonly symbolImportNode: ts.Declaration | null - ) { } + symbol: ts.Symbol | null, + symbolImportNode: ts.Declaration | null + ) { + this.symbol = symbol; + this.symbolImportNode = symbolImportNode; + } } /** @@ -910,12 +774,6 @@ class SymbolImportTuple { */ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChecker, node: ts.Node): SymbolImportTuple[] { - // Use some TypeScript internals to avoid code duplication - type ObjectLiteralElementWithName = ts.ObjectLiteralElement & { name: ts.PropertyName; parent: ts.ObjectLiteralExpression | ts.JsxAttributes }; - const getPropertySymbolsFromContextualType: (node: ObjectLiteralElementWithName, checker: ts.TypeChecker, contextualType: ts.Type, unionSymbolOk: boolean) => ReadonlyArray = (ts).getPropertySymbolsFromContextualType; - const getContainingObjectLiteralElement: (node: ts.Node) => ObjectLiteralElementWithName | undefined = (ts).getContainingObjectLiteralElement; - const getNameFromPropertyName: (name: ts.PropertyName) => string | undefined = (ts).getNameFromPropertyName; - // Go to the original declaration for cases: // // (1) when the aliased symbol was declared in the location(parent). @@ -990,7 +848,7 @@ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChec // bar(({pr/*goto*/op1})=>{}); if (ts.isPropertyName(node) && ts.isBindingElement(parent) && ts.isObjectBindingPattern(parent.parent) && (node === (parent.propertyName || parent.name))) { - const name = getNameFromPropertyName(node); + const name = ts.getNameFromPropertyName(node); const type = checker.getTypeAtLocation(parent.parent); if (name && type) { if (type.isUnion()) { @@ -1013,11 +871,11 @@ function getRealNodeSymbol(ts: typeof import('typescript'), checker: ts.TypeChec // } // function Foo(arg: Props) {} // Foo( { pr/*1*/op1: 10, prop2: false }) - const element = getContainingObjectLiteralElement(node); + const element = ts.getContainingObjectLiteralElement(node); if (element) { const contextualType = element && checker.getContextualType(element.parent); if (contextualType) { - const propertySymbols = getPropertySymbolsFromContextualType(element, checker, contextualType, /*unionSymbolOk*/ false); + const propertySymbols = ts.getPropertySymbolsFromContextualType(element, checker, contextualType, /*unionSymbolOk*/ false); if (propertySymbols) { symbol = propertySymbols[0]; } diff --git a/build/lib/tsb/builder.js b/build/lib/tsb/builder.js deleted file mode 100644 index b149cf5ade64a..0000000000000 --- a/build/lib/tsb/builder.js +++ /dev/null @@ -1,662 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.CancellationToken = void 0; -exports.createTypeScriptBuilder = createTypeScriptBuilder; -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const crypto_1 = __importDefault(require("crypto")); -const utils = __importStar(require("./utils")); -const ansi_colors_1 = __importDefault(require("ansi-colors")); -const typescript_1 = __importDefault(require("typescript")); -const vinyl_1 = __importDefault(require("vinyl")); -const source_map_1 = require("source-map"); -var CancellationToken; -(function (CancellationToken) { - CancellationToken.None = { - isCancellationRequested() { return false; } - }; -})(CancellationToken || (exports.CancellationToken = CancellationToken = {})); -function normalize(path) { - return path.replace(/\\/g, '/'); -} -function createTypeScriptBuilder(config, projectFile, cmd) { - const _log = config.logFn; - const host = new LanguageServiceHost(cmd, projectFile, _log); - const outHost = new LanguageServiceHost({ ...cmd, options: { ...cmd.options, sourceRoot: cmd.options.outDir } }, cmd.options.outDir ?? '', _log); - const toBeCheckedForCycles = []; - const service = typescript_1.default.createLanguageService(host, typescript_1.default.createDocumentRegistry()); - const lastBuildVersion = Object.create(null); - const lastDtsHash = Object.create(null); - const userWantsDeclarations = cmd.options.declaration; - let oldErrors = Object.create(null); - let headUsed = process.memoryUsage().heapUsed; - let emitSourceMapsInStream = true; - // always emit declaraction files - host.getCompilationSettings().declaration = true; - function file(file) { - // support gulp-sourcemaps - if (file.sourceMap) { - emitSourceMapsInStream = false; - } - if (!file.contents) { - host.removeScriptSnapshot(file.path); - delete lastBuildVersion[normalize(file.path)]; - } - else { - host.addScriptSnapshot(file.path, new VinylScriptSnapshot(file)); - } - } - function baseFor(snapshot) { - if (snapshot instanceof VinylScriptSnapshot) { - return cmd.options.outDir || snapshot.getBase(); - } - else { - return ''; - } - } - function isExternalModule(sourceFile) { - return sourceFile.externalModuleIndicator - || /declare\s+module\s+('|")(.+)\1/.test(sourceFile.getText()); - } - function build(out, onError, token = CancellationToken.None) { - function checkSyntaxSoon(fileName) { - return new Promise(resolve => { - process.nextTick(function () { - if (!host.getScriptSnapshot(fileName, false)) { - resolve([]); // no script, no problems - } - else { - resolve(service.getSyntacticDiagnostics(fileName)); - } - }); - }); - } - function checkSemanticsSoon(fileName) { - return new Promise(resolve => { - process.nextTick(function () { - if (!host.getScriptSnapshot(fileName, false)) { - resolve([]); // no script, no problems - } - else { - resolve(service.getSemanticDiagnostics(fileName)); - } - }); - }); - } - function emitSoon(fileName) { - return new Promise(resolve => { - process.nextTick(function () { - if (/\.d\.ts$/.test(fileName)) { - // if it's already a d.ts file just emit it signature - const snapshot = host.getScriptSnapshot(fileName); - const signature = crypto_1.default.createHash('sha256') - .update(snapshot.getText(0, snapshot.getLength())) - .digest('base64'); - return resolve({ - fileName, - signature, - files: [] - }); - } - const output = service.getEmitOutput(fileName); - const files = []; - let signature; - for (const file of output.outputFiles) { - if (!emitSourceMapsInStream && /\.js\.map$/.test(file.name)) { - continue; - } - if (/\.d\.ts$/.test(file.name)) { - signature = crypto_1.default.createHash('sha256') - .update(file.text) - .digest('base64'); - if (!userWantsDeclarations) { - // don't leak .d.ts files if users don't want them - continue; - } - } - const vinyl = new vinyl_1.default({ - path: file.name, - contents: Buffer.from(file.text), - base: !config._emitWithoutBasePath && baseFor(host.getScriptSnapshot(fileName)) || undefined - }); - if (!emitSourceMapsInStream && /\.js$/.test(file.name)) { - const sourcemapFile = output.outputFiles.filter(f => /\.js\.map$/.test(f.name))[0]; - if (sourcemapFile) { - const extname = path_1.default.extname(vinyl.relative); - const basename = path_1.default.basename(vinyl.relative, extname); - const dirname = path_1.default.dirname(vinyl.relative); - const tsname = (dirname === '.' ? '' : dirname + '/') + basename + '.ts'; - let sourceMap = JSON.parse(sourcemapFile.text); - sourceMap.sources[0] = tsname.replace(/\\/g, '/'); - // check for an "input source" map and combine them - // in step 1 we extract all line edit from the input source map, and - // in step 2 we apply the line edits to the typescript source map - const snapshot = host.getScriptSnapshot(fileName); - if (snapshot instanceof VinylScriptSnapshot && snapshot.sourceMap) { - const inputSMC = new source_map_1.SourceMapConsumer(snapshot.sourceMap); - const tsSMC = new source_map_1.SourceMapConsumer(sourceMap); - let didChange = false; - const smg = new source_map_1.SourceMapGenerator({ - file: sourceMap.file, - sourceRoot: sourceMap.sourceRoot - }); - // step 1 - const lineEdits = new Map(); - inputSMC.eachMapping(m => { - if (m.originalLine === m.generatedLine) { - // same line mapping - let array = lineEdits.get(m.originalLine); - if (!array) { - array = []; - lineEdits.set(m.originalLine, array); - } - array.push([m.originalColumn, m.generatedColumn]); - } - else { - // NOT SUPPORTED - } - }); - // step 2 - tsSMC.eachMapping(m => { - didChange = true; - const edits = lineEdits.get(m.originalLine); - let originalColumnDelta = 0; - if (edits) { - for (const [from, to] of edits) { - if (to >= m.originalColumn) { - break; - } - originalColumnDelta = from - to; - } - } - smg.addMapping({ - source: m.source, - name: m.name, - generated: { line: m.generatedLine, column: m.generatedColumn }, - original: { line: m.originalLine, column: m.originalColumn + originalColumnDelta } - }); - }); - if (didChange) { - [tsSMC, inputSMC].forEach((consumer) => { - consumer.sources.forEach((sourceFile) => { - smg._sources.add(sourceFile); - const sourceContent = consumer.sourceContentFor(sourceFile); - if (sourceContent !== null) { - smg.setSourceContent(sourceFile, sourceContent); - } - }); - }); - sourceMap = JSON.parse(smg.toString()); - // const filename = '/Users/jrieken/Code/vscode/src2/' + vinyl.relative + '.map'; - // fs.promises.mkdir(path.dirname(filename), { recursive: true }).then(async () => { - // await fs.promises.writeFile(filename, smg.toString()); - // await fs.promises.writeFile('/Users/jrieken/Code/vscode/src2/' + vinyl.relative, vinyl.contents); - // }); - } - } - vinyl.sourceMap = sourceMap; - } - } - files.push(vinyl); - } - resolve({ - fileName, - signature, - files - }); - }); - }); - } - const newErrors = Object.create(null); - const t1 = Date.now(); - const toBeEmitted = []; - const toBeCheckedSyntactically = []; - const toBeCheckedSemantically = []; - const filesWithChangedSignature = []; - const dependentFiles = []; - const newLastBuildVersion = new Map(); - for (const fileName of host.getScriptFileNames()) { - if (lastBuildVersion[fileName] !== host.getScriptVersion(fileName)) { - toBeEmitted.push(fileName); - toBeCheckedSyntactically.push(fileName); - toBeCheckedSemantically.push(fileName); - } - } - return new Promise(resolve => { - const semanticCheckInfo = new Map(); - const seenAsDependentFile = new Set(); - function workOnNext() { - let promise; - // let fileName: string; - // someone told us to stop this - if (token.isCancellationRequested()) { - _log('[CANCEL]', '>>This compile run was cancelled<<'); - newLastBuildVersion.clear(); - resolve(); - return; - } - // (1st) emit code - else if (toBeEmitted.length) { - const fileName = toBeEmitted.pop(); - promise = emitSoon(fileName).then(value => { - for (const file of value.files) { - _log('[emit code]', file.path); - out(file); - } - // remember when this was build - newLastBuildVersion.set(fileName, host.getScriptVersion(fileName)); - // remeber the signature - if (value.signature && lastDtsHash[fileName] !== value.signature) { - lastDtsHash[fileName] = value.signature; - filesWithChangedSignature.push(fileName); - } - // line up for cycle check - const jsValue = value.files.find(candidate => candidate.basename.endsWith('.js')); - if (jsValue) { - outHost.addScriptSnapshot(jsValue.path, new ScriptSnapshot(String(jsValue.contents), new Date())); - toBeCheckedForCycles.push(normalize(jsValue.path)); - } - }).catch(e => { - // can't just skip this or make a result up.. - host.error(`ERROR emitting ${fileName}`); - host.error(e); - }); - } - // (2nd) check syntax - else if (toBeCheckedSyntactically.length) { - const fileName = toBeCheckedSyntactically.pop(); - _log('[check syntax]', fileName); - promise = checkSyntaxSoon(fileName).then(diagnostics => { - delete oldErrors[fileName]; - if (diagnostics.length > 0) { - diagnostics.forEach(d => onError(d)); - newErrors[fileName] = diagnostics; - // stop the world when there are syntax errors - toBeCheckedSyntactically.length = 0; - toBeCheckedSemantically.length = 0; - filesWithChangedSignature.length = 0; - } - }); - } - // (3rd) check semantics - else if (toBeCheckedSemantically.length) { - let fileName = toBeCheckedSemantically.pop(); - while (fileName && semanticCheckInfo.has(fileName)) { - fileName = toBeCheckedSemantically.pop(); - } - if (fileName) { - _log('[check semantics]', fileName); - promise = checkSemanticsSoon(fileName).then(diagnostics => { - delete oldErrors[fileName]; - semanticCheckInfo.set(fileName, diagnostics.length); - if (diagnostics.length > 0) { - diagnostics.forEach(d => onError(d)); - newErrors[fileName] = diagnostics; - } - }); - } - } - // (4th) check dependents - else if (filesWithChangedSignature.length) { - while (filesWithChangedSignature.length) { - const fileName = filesWithChangedSignature.pop(); - if (!isExternalModule(service.getProgram().getSourceFile(fileName))) { - _log('[check semantics*]', fileName + ' is an internal module and it has changed shape -> check whatever hasn\'t been checked yet'); - toBeCheckedSemantically.push(...host.getScriptFileNames()); - filesWithChangedSignature.length = 0; - dependentFiles.length = 0; - break; - } - host.collectDependents(fileName, dependentFiles); - } - } - // (5th) dependents contd - else if (dependentFiles.length) { - let fileName = dependentFiles.pop(); - while (fileName && seenAsDependentFile.has(fileName)) { - fileName = dependentFiles.pop(); - } - if (fileName) { - seenAsDependentFile.add(fileName); - const value = semanticCheckInfo.get(fileName); - if (value === 0) { - // already validated successfully -> look at dependents next - host.collectDependents(fileName, dependentFiles); - } - else if (typeof value === 'undefined') { - // first validate -> look at dependents next - dependentFiles.push(fileName); - toBeCheckedSemantically.push(fileName); - } - } - } - // (last) done - else { - resolve(); - return; - } - if (!promise) { - promise = Promise.resolve(); - } - promise.then(function () { - // change to change - process.nextTick(workOnNext); - }).catch(err => { - console.error(err); - }); - } - workOnNext(); - }).then(() => { - // check for cyclic dependencies - const cycles = outHost.getCyclicDependencies(toBeCheckedForCycles); - toBeCheckedForCycles.length = 0; - for (const [filename, error] of cycles) { - const cyclicDepErrors = []; - if (error) { - cyclicDepErrors.push({ - category: typescript_1.default.DiagnosticCategory.Error, - code: 1, - file: undefined, - start: undefined, - length: undefined, - messageText: `CYCLIC dependency: ${error}` - }); - } - newErrors[filename] = cyclicDepErrors; - } - }).then(() => { - // store the build versions to not rebuilt the next time - newLastBuildVersion.forEach((value, key) => { - lastBuildVersion[key] = value; - }); - // print old errors and keep them - for (const [key, value] of Object.entries(oldErrors)) { - value.forEach(diag => onError(diag)); - newErrors[key] = value; - } - oldErrors = newErrors; - // print stats - const headNow = process.memoryUsage().heapUsed; - const MB = 1024 * 1024; - _log('[tsb]', `time: ${ansi_colors_1.default.yellow((Date.now() - t1) + 'ms')} + \nmem: ${ansi_colors_1.default.cyan(Math.ceil(headNow / MB) + 'MB')} ${ansi_colors_1.default.bgCyan('delta: ' + Math.ceil((headNow - headUsed) / MB))}`); - headUsed = headNow; - }); - } - return { - file, - build, - languageService: service - }; -} -class ScriptSnapshot { - _text; - _mtime; - constructor(text, mtime) { - this._text = text; - this._mtime = mtime; - } - getVersion() { - return this._mtime.toUTCString(); - } - getText(start, end) { - return this._text.substring(start, end); - } - getLength() { - return this._text.length; - } - getChangeRange(_oldSnapshot) { - return undefined; - } -} -class VinylScriptSnapshot extends ScriptSnapshot { - _base; - sourceMap; - constructor(file) { - super(file.contents.toString(), file.stat.mtime); - this._base = file.base; - this.sourceMap = file.sourceMap; - } - getBase() { - return this._base; - } -} -class LanguageServiceHost { - _cmdLine; - _projectPath; - _log; - _snapshots; - _filesInProject; - _filesAdded; - _dependencies; - _dependenciesRecomputeList; - _fileNameToDeclaredModule; - _projectVersion; - constructor(_cmdLine, _projectPath, _log) { - this._cmdLine = _cmdLine; - this._projectPath = _projectPath; - this._log = _log; - this._snapshots = Object.create(null); - this._filesInProject = new Set(_cmdLine.fileNames); - this._filesAdded = new Set(); - this._dependencies = new utils.graph.Graph(); - this._dependenciesRecomputeList = []; - this._fileNameToDeclaredModule = Object.create(null); - this._projectVersion = 1; - } - log(_s) { - // console.log(s); - } - trace(_s) { - // console.log(s); - } - error(s) { - console.error(s); - } - getCompilationSettings() { - return this._cmdLine.options; - } - getProjectVersion() { - return String(this._projectVersion); - } - getScriptFileNames() { - const res = Object.keys(this._snapshots).filter(path => this._filesInProject.has(path) || this._filesAdded.has(path)); - return res; - } - getScriptVersion(filename) { - filename = normalize(filename); - const result = this._snapshots[filename]; - if (result) { - return result.getVersion(); - } - return 'UNKNWON_FILE_' + Math.random().toString(16).slice(2); - } - getScriptSnapshot(filename, resolve = true) { - filename = normalize(filename); - let result = this._snapshots[filename]; - if (!result && resolve) { - try { - result = new VinylScriptSnapshot(new vinyl_1.default({ - path: filename, - contents: fs_1.default.readFileSync(filename), - base: this.getCompilationSettings().outDir, - stat: fs_1.default.statSync(filename) - })); - this.addScriptSnapshot(filename, result); - } - catch (e) { - // ignore - } - } - return result; - } - static _declareModule = /declare\s+module\s+('|")(.+)\1/g; - addScriptSnapshot(filename, snapshot) { - this._projectVersion++; - filename = normalize(filename); - const old = this._snapshots[filename]; - if (!old && !this._filesInProject.has(filename) && !filename.endsWith('.d.ts')) { - // ^^^^^^^^^^^^^^^^^^^^^^^^^^ - // not very proper! - this._filesAdded.add(filename); - } - if (!old || old.getVersion() !== snapshot.getVersion()) { - this._dependenciesRecomputeList.push(filename); - // (cheap) check for declare module - LanguageServiceHost._declareModule.lastIndex = 0; - let match; - while ((match = LanguageServiceHost._declareModule.exec(snapshot.getText(0, snapshot.getLength())))) { - let declaredModules = this._fileNameToDeclaredModule[filename]; - if (!declaredModules) { - this._fileNameToDeclaredModule[filename] = declaredModules = []; - } - declaredModules.push(match[2]); - } - } - this._snapshots[filename] = snapshot; - return old; - } - removeScriptSnapshot(filename) { - filename = normalize(filename); - this._log('removeScriptSnapshot', filename); - this._filesInProject.delete(filename); - this._filesAdded.delete(filename); - this._projectVersion++; - delete this._fileNameToDeclaredModule[filename]; - return delete this._snapshots[filename]; - } - getCurrentDirectory() { - return path_1.default.dirname(this._projectPath); - } - getDefaultLibFileName(options) { - return typescript_1.default.getDefaultLibFilePath(options); - } - directoryExists = typescript_1.default.sys.directoryExists; - getDirectories = typescript_1.default.sys.getDirectories; - fileExists = typescript_1.default.sys.fileExists; - readFile = typescript_1.default.sys.readFile; - readDirectory = typescript_1.default.sys.readDirectory; - // ---- dependency management - collectDependents(filename, target) { - while (this._dependenciesRecomputeList.length) { - this._processFile(this._dependenciesRecomputeList.pop()); - } - filename = normalize(filename); - const node = this._dependencies.lookup(filename); - if (node) { - node.incoming.forEach(entry => target.push(entry.data)); - } - } - getCyclicDependencies(filenames) { - // Ensure dependencies are up to date - while (this._dependenciesRecomputeList.length) { - this._processFile(this._dependenciesRecomputeList.pop()); - } - const cycles = this._dependencies.findCycles(filenames.sort((a, b) => a.localeCompare(b))); - const result = new Map(); - for (const [key, value] of cycles) { - result.set(key, value?.join(' -> ')); - } - return result; - } - _processFile(filename) { - if (filename.match(/.*\.d\.ts$/)) { - return; - } - filename = normalize(filename); - const snapshot = this.getScriptSnapshot(filename); - if (!snapshot) { - this._log('processFile', `Missing snapshot for: ${filename}`); - return; - } - const info = typescript_1.default.preProcessFile(snapshot.getText(0, snapshot.getLength()), true); - // (0) clear out old dependencies - this._dependencies.resetNode(filename); - // (1) ///-references - info.referencedFiles.forEach(ref => { - const resolvedPath = path_1.default.resolve(path_1.default.dirname(filename), ref.fileName); - const normalizedPath = normalize(resolvedPath); - this._dependencies.inertEdge(filename, normalizedPath); - }); - // (2) import-require statements - info.importedFiles.forEach(ref => { - if (!ref.fileName.startsWith('.')) { - // node module? - return; - } - if (ref.fileName.endsWith('.css')) { - return; - } - const stopDirname = normalize(this.getCurrentDirectory()); - let dirname = filename; - let found = false; - while (!found && dirname.indexOf(stopDirname) === 0) { - dirname = path_1.default.dirname(dirname); - let resolvedPath = path_1.default.resolve(dirname, ref.fileName); - if (resolvedPath.endsWith('.js')) { - resolvedPath = resolvedPath.slice(0, -3); - } - const normalizedPath = normalize(resolvedPath); - if (this.getScriptSnapshot(normalizedPath + '.ts')) { - this._dependencies.inertEdge(filename, normalizedPath + '.ts'); - found = true; - } - else if (this.getScriptSnapshot(normalizedPath + '.d.ts')) { - this._dependencies.inertEdge(filename, normalizedPath + '.d.ts'); - found = true; - } - else if (this.getScriptSnapshot(normalizedPath + '.js')) { - this._dependencies.inertEdge(filename, normalizedPath + '.js'); - found = true; - } - } - if (!found) { - for (const key in this._fileNameToDeclaredModule) { - if (this._fileNameToDeclaredModule[key] && ~this._fileNameToDeclaredModule[key].indexOf(ref.fileName)) { - this._dependencies.inertEdge(filename, key); - } - } - } - }); - } -} -//# sourceMappingURL=builder.js.map \ No newline at end of file diff --git a/build/lib/tsb/builder.ts b/build/lib/tsb/builder.ts index 79ae06d73f7c5..628afc054276c 100644 --- a/build/lib/tsb/builder.ts +++ b/build/lib/tsb/builder.ts @@ -6,11 +6,11 @@ import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; -import * as utils from './utils'; +import * as utils from './utils.ts'; import colors from 'ansi-colors'; import ts from 'typescript'; import Vinyl from 'vinyl'; -import { RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map'; +import { type RawSourceMap, SourceMapConsumer, SourceMapGenerator } from 'source-map'; export interface IConfiguration { logFn: (topic: string, message: string) => void; @@ -21,11 +21,11 @@ export interface CancellationToken { isCancellationRequested(): boolean; } -export namespace CancellationToken { - export const None: CancellationToken = { +export const CancellationToken = new class { + None: CancellationToken = { isCancellationRequested() { return false; } }; -} +}; export interface ITypeScriptBuilder { build(out: (file: Vinyl) => void, onError: (err: ts.Diagnostic) => void, token?: CancellationToken): Promise; @@ -59,7 +59,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str function file(file: Vinyl): void { // support gulp-sourcemaps - if ((file).sourceMap) { + if (file.sourceMap) { emitSourceMapsInStream = false; } @@ -80,7 +80,10 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str } function isExternalModule(sourceFile: ts.SourceFile): boolean { - return (sourceFile).externalModuleIndicator + interface SourceFileWithModuleIndicator extends ts.SourceFile { + externalModuleIndicator?: unknown; + } + return !!(sourceFile as SourceFileWithModuleIndicator).externalModuleIndicator || /declare\s+module\s+('|")(.+)\1/.test(sourceFile.getText()); } @@ -164,7 +167,7 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str const dirname = path.dirname(vinyl.relative); const tsname = (dirname === '.' ? '' : dirname + '/') + basename + '.ts'; - let sourceMap = JSON.parse(sourcemapFile.text); + let sourceMap = JSON.parse(sourcemapFile.text) as RawSourceMap; sourceMap.sources[0] = tsname.replace(/\\/g, '/'); // check for an "input source" map and combine them @@ -219,17 +222,19 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str if (didChange) { + interface SourceMapGeneratorWithSources extends SourceMapGenerator { + _sources: { add(source: string): void }; + } + [tsSMC, inputSMC].forEach((consumer) => { - (consumer).sources.forEach((sourceFile: any) => { - (smg)._sources.add(sourceFile); + (consumer as SourceMapConsumer & { sources: string[] }).sources.forEach((sourceFile: string) => { + (smg as SourceMapGeneratorWithSources)._sources.add(sourceFile); const sourceContent = consumer.sourceContentFor(sourceFile); if (sourceContent !== null) { smg.setSourceContent(sourceFile, sourceContent); } }); - }); - - sourceMap = JSON.parse(smg.toString()); + }); sourceMap = JSON.parse(smg.toString()); // const filename = '/Users/jrieken/Code/vscode/src2/' + vinyl.relative + '.map'; // fs.promises.mkdir(path.dirname(filename), { recursive: true }).then(async () => { @@ -239,11 +244,9 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str } } - (vinyl).sourceMap = sourceMap; + (vinyl as Vinyl & { sourceMap?: RawSourceMap }).sourceMap = sourceMap; } - } - - files.push(vinyl); + } files.push(vinyl); } resolve({ @@ -440,7 +443,9 @@ export function createTypeScriptBuilder(config: IConfiguration, projectFile: str messageText: `CYCLIC dependency: ${error}` }); } + delete oldErrors[filename]; newErrors[filename] = cyclicDepErrors; + cyclicDepErrors.forEach(d => onError(d)); } }).then(() => { @@ -524,19 +529,25 @@ class LanguageServiceHost implements ts.LanguageServiceHost { private readonly _snapshots: { [path: string]: ScriptSnapshot }; private readonly _filesInProject: Set; private readonly _filesAdded: Set; - private readonly _dependencies: utils.graph.Graph; + private readonly _dependencies: InstanceType>; private readonly _dependenciesRecomputeList: string[]; private readonly _fileNameToDeclaredModule: { [path: string]: string[] }; private _projectVersion: number; + private readonly _cmdLine: ts.ParsedCommandLine; + private readonly _projectPath: string; + private readonly _log: (topic: string, message: string) => void; constructor( - private readonly _cmdLine: ts.ParsedCommandLine, - private readonly _projectPath: string, - private readonly _log: (topic: string, message: string) => void + cmdLine: ts.ParsedCommandLine, + projectPath: string, + log: (topic: string, message: string) => void ) { + this._cmdLine = cmdLine; + this._projectPath = projectPath; + this._log = log; this._snapshots = Object.create(null); - this._filesInProject = new Set(_cmdLine.fileNames); + this._filesInProject = new Set(this._cmdLine.fileNames); this._filesAdded = new Set(); this._dependencies = new utils.graph.Graph(); this._dependenciesRecomputeList = []; @@ -584,7 +595,7 @@ class LanguageServiceHost implements ts.LanguageServiceHost { let result = this._snapshots[filename]; if (!result && resolve) { try { - result = new VinylScriptSnapshot(new Vinyl({ + result = new VinylScriptSnapshot(new Vinyl({ path: filename, contents: fs.readFileSync(filename), base: this.getCompilationSettings().outDir, @@ -660,7 +671,7 @@ class LanguageServiceHost implements ts.LanguageServiceHost { filename = normalize(filename); const node = this._dependencies.lookup(filename); if (node) { - node.incoming.forEach(entry => target.push(entry.data)); + node.incoming.forEach((entry: any) => target.push(entry.data)); } } diff --git a/build/lib/tsb/index.js b/build/lib/tsb/index.js deleted file mode 100644 index 552eea5014f72..0000000000000 --- a/build/lib/tsb/index.js +++ /dev/null @@ -1,171 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || (function () { - var ownKeys = function(o) { - ownKeys = Object.getOwnPropertyNames || function (o) { - var ar = []; - for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; - return ar; - }; - return ownKeys(o); - }; - return function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); - __setModuleDefault(result, mod); - return result; - }; -})(); -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.create = create; -const vinyl_1 = __importDefault(require("vinyl")); -const through_1 = __importDefault(require("through")); -const builder = __importStar(require("./builder")); -const typescript_1 = __importDefault(require("typescript")); -const stream_1 = require("stream"); -const path_1 = require("path"); -const utils_1 = require("./utils"); -const fs_1 = require("fs"); -const fancy_log_1 = __importDefault(require("fancy-log")); -const transpiler_1 = require("./transpiler"); -const colors = require("ansi-colors"); -class EmptyDuplex extends stream_1.Duplex { - _write(_chunk, _encoding, callback) { callback(); } - _read() { this.push(null); } -} -function createNullCompiler() { - const result = function () { return new EmptyDuplex(); }; - result.src = () => new EmptyDuplex(); - return result; -} -const _defaultOnError = (err) => console.log(JSON.stringify(err, null, 4)); -function create(projectPath, existingOptions, config, onError = _defaultOnError) { - function printDiagnostic(diag) { - if (diag instanceof Error) { - onError(diag.message); - } - else if (!diag.file || !diag.start) { - onError(typescript_1.default.flattenDiagnosticMessageText(diag.messageText, '\n')); - } - else { - const lineAndCh = diag.file.getLineAndCharacterOfPosition(diag.start); - onError(utils_1.strings.format('{0}({1},{2}): {3}', diag.file.fileName, lineAndCh.line + 1, lineAndCh.character + 1, typescript_1.default.flattenDiagnosticMessageText(diag.messageText, '\n'))); - } - } - const parsed = typescript_1.default.readConfigFile(projectPath, typescript_1.default.sys.readFile); - if (parsed.error) { - printDiagnostic(parsed.error); - return createNullCompiler(); - } - const cmdLine = typescript_1.default.parseJsonConfigFileContent(parsed.config, typescript_1.default.sys, (0, path_1.dirname)(projectPath), existingOptions); - if (cmdLine.errors.length > 0) { - cmdLine.errors.forEach(printDiagnostic); - return createNullCompiler(); - } - function logFn(topic, message) { - if (config.verbose) { - (0, fancy_log_1.default)(colors.cyan(topic), message); - } - } - // FULL COMPILE stream doing transpile, syntax and semantic diagnostics - function createCompileStream(builder, token) { - return (0, through_1.default)(function (file) { - // give the file to the compiler - if (file.isStream()) { - this.emit('error', 'no support for streams'); - return; - } - builder.file(file); - }, function () { - // start the compilation process - builder.build(file => this.queue(file), printDiagnostic, token).catch(e => console.error(e)).then(() => this.queue(null)); - }); - } - // TRANSPILE ONLY stream doing just TS to JS conversion - function createTranspileStream(transpiler) { - return (0, through_1.default)(function (file) { - // give the file to the compiler - if (file.isStream()) { - this.emit('error', 'no support for streams'); - return; - } - if (!file.contents) { - return; - } - if (!config.transpileOnlyIncludesDts && file.path.endsWith('.d.ts')) { - return; - } - if (!transpiler.onOutfile) { - transpiler.onOutfile = file => this.queue(file); - } - transpiler.transpile(file); - }, function () { - transpiler.join().then(() => { - this.queue(null); - transpiler.onOutfile = undefined; - }); - }); - } - let result; - if (config.transpileOnly) { - const transpiler = !config.transpileWithEsbuild - ? new transpiler_1.TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) - : new transpiler_1.ESBuildTranspiler(logFn, printDiagnostic, projectPath, cmdLine); - result = (() => createTranspileStream(transpiler)); - } - else { - const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); - result = ((token) => createCompileStream(_builder, token)); - } - result.src = (opts) => { - let _pos = 0; - const _fileNames = cmdLine.fileNames.slice(0); - return new class extends stream_1.Readable { - constructor() { - super({ objectMode: true }); - } - _read() { - let more = true; - let path; - for (; more && _pos < _fileNames.length; _pos++) { - path = _fileNames[_pos]; - more = this.push(new vinyl_1.default({ - path, - contents: (0, fs_1.readFileSync)(path), - stat: (0, fs_1.statSync)(path), - cwd: opts && opts.cwd, - base: opts && opts.base || (0, path_1.dirname)(projectPath) - })); - } - if (_pos >= _fileNames.length) { - this.push(null); - } - } - }; - }; - return result; -} -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/build/lib/tsb/index.ts b/build/lib/tsb/index.ts index 5399a2ead03f3..31c1c3f15f8c1 100644 --- a/build/lib/tsb/index.ts +++ b/build/lib/tsb/index.ts @@ -5,15 +5,15 @@ import Vinyl from 'vinyl'; import through from 'through'; -import * as builder from './builder'; +import * as builder from './builder.ts'; import ts from 'typescript'; import { Readable, Writable, Duplex } from 'stream'; import { dirname } from 'path'; -import { strings } from './utils'; +import { strings } from './utils.ts'; import { readFileSync, statSync } from 'fs'; import log from 'fancy-log'; -import { ESBuildTranspiler, ITranspiler, TscTranspiler } from './transpiler'; -import colors = require('ansi-colors'); +import { ESBuildTranspiler, type ITranspiler, TscTranspiler } from './transpiler.ts'; +import colors from 'ansi-colors'; export interface IncrementalCompiler { (token?: any): Readable & Writable; @@ -131,10 +131,10 @@ export function create( const transpiler = !config.transpileWithEsbuild ? new TscTranspiler(logFn, printDiagnostic, projectPath, cmdLine) : new ESBuildTranspiler(logFn, printDiagnostic, projectPath, cmdLine); - result = (() => createTranspileStream(transpiler)); + result = (() => createTranspileStream(transpiler)) as IncrementalCompiler; } else { const _builder = builder.createTypeScriptBuilder({ logFn }, projectPath, cmdLine); - result = ((token: builder.CancellationToken) => createCompileStream(_builder, token)); + result = ((token: builder.CancellationToken) => createCompileStream(_builder, token)) as IncrementalCompiler; } result.src = (opts?: { cwd?: string; base?: string }) => { @@ -164,5 +164,5 @@ export function create( }; }; - return result; + return result as IncrementalCompiler; } diff --git a/build/lib/tsb/transpiler.js b/build/lib/tsb/transpiler.js deleted file mode 100644 index adccb10441665..0000000000000 --- a/build/lib/tsb/transpiler.js +++ /dev/null @@ -1,306 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.ESBuildTranspiler = exports.TscTranspiler = void 0; -const esbuild_1 = __importDefault(require("esbuild")); -const typescript_1 = __importDefault(require("typescript")); -const node_worker_threads_1 = __importDefault(require("node:worker_threads")); -const vinyl_1 = __importDefault(require("vinyl")); -const node_os_1 = require("node:os"); -function transpile(tsSrc, options) { - const isAmd = /\n(import|export)/m.test(tsSrc); - if (!isAmd && options.compilerOptions?.module === typescript_1.default.ModuleKind.AMD) { - // enforce NONE module-system for not-amd cases - options = { ...options, ...{ compilerOptions: { ...options.compilerOptions, module: typescript_1.default.ModuleKind.None } } }; - } - const out = typescript_1.default.transpileModule(tsSrc, options); - return { - jsSrc: out.outputText, - diag: out.diagnostics ?? [] - }; -} -if (!node_worker_threads_1.default.isMainThread) { - // WORKER - node_worker_threads_1.default.parentPort?.addListener('message', (req) => { - const res = { - jsSrcs: [], - diagnostics: [] - }; - for (const tsSrc of req.tsSrcs) { - const out = transpile(tsSrc, req.options); - res.jsSrcs.push(out.jsSrc); - res.diagnostics.push(out.diag); - } - node_worker_threads_1.default.parentPort.postMessage(res); - }); -} -class OutputFileNameOracle { - getOutputFileName; - constructor(cmdLine, configFilePath) { - this.getOutputFileName = (file) => { - try { - // windows: path-sep normalizing - file = typescript_1.default.normalizePath(file); - if (!cmdLine.options.configFilePath) { - // this is needed for the INTERNAL getOutputFileNames-call below... - cmdLine.options.configFilePath = configFilePath; - } - const isDts = file.endsWith('.d.ts'); - if (isDts) { - file = file.slice(0, -5) + '.ts'; - cmdLine.fileNames.push(file); - } - const outfile = typescript_1.default.getOutputFileNames(cmdLine, file, true)[0]; - if (isDts) { - cmdLine.fileNames.pop(); - } - return outfile; - } - catch (err) { - console.error(file, cmdLine.fileNames); - console.error(err); - throw err; - } - }; - } -} -class TranspileWorker { - static pool = 1; - id = TranspileWorker.pool++; - _worker = new node_worker_threads_1.default.Worker(__filename); - _pending; - _durations = []; - constructor(outFileFn) { - this._worker.addListener('message', (res) => { - if (!this._pending) { - console.error('RECEIVING data WITHOUT request'); - return; - } - const [resolve, reject, files, options, t1] = this._pending; - const outFiles = []; - const diag = []; - for (let i = 0; i < res.jsSrcs.length; i++) { - // inputs and outputs are aligned across the arrays - const file = files[i]; - const jsSrc = res.jsSrcs[i]; - const diag = res.diagnostics[i]; - if (diag.length > 0) { - diag.push(...diag); - continue; - } - let SuffixTypes; - (function (SuffixTypes) { - SuffixTypes[SuffixTypes["Dts"] = 5] = "Dts"; - SuffixTypes[SuffixTypes["Ts"] = 3] = "Ts"; - SuffixTypes[SuffixTypes["Unknown"] = 0] = "Unknown"; - })(SuffixTypes || (SuffixTypes = {})); - const suffixLen = file.path.endsWith('.d.ts') ? 5 /* SuffixTypes.Dts */ - : file.path.endsWith('.ts') ? 3 /* SuffixTypes.Ts */ - : 0 /* SuffixTypes.Unknown */; - // check if output of a DTS-files isn't just "empty" and iff so - // skip this file - if (suffixLen === 5 /* SuffixTypes.Dts */ && _isDefaultEmpty(jsSrc)) { - continue; - } - const outBase = options.compilerOptions?.outDir ?? file.base; - const outPath = outFileFn(file.path); - outFiles.push(new vinyl_1.default({ - path: outPath, - base: outBase, - contents: Buffer.from(jsSrc), - })); - } - this._pending = undefined; - this._durations.push(Date.now() - t1); - if (diag.length > 0) { - reject(diag); - } - else { - resolve(outFiles); - } - }); - } - terminate() { - // console.log(`Worker#${this.id} ENDS after ${this._durations.length} jobs (total: ${this._durations.reduce((p, c) => p + c, 0)}, avg: ${this._durations.reduce((p, c) => p + c, 0) / this._durations.length})`); - this._worker.terminate(); - } - get isBusy() { - return this._pending !== undefined; - } - next(files, options) { - if (this._pending !== undefined) { - throw new Error('BUSY'); - } - return new Promise((resolve, reject) => { - this._pending = [resolve, reject, files, options, Date.now()]; - const req = { - options, - tsSrcs: files.map(file => String(file.contents)) - }; - this._worker.postMessage(req); - }); - } -} -class TscTranspiler { - _onError; - _cmdLine; - static P = Math.floor((0, node_os_1.cpus)().length * .5); - _outputFileNames; - onOutfile; - _workerPool = []; - _queue = []; - _allJobs = []; - constructor(logFn, _onError, configFilePath, _cmdLine) { - this._onError = _onError; - this._cmdLine = _cmdLine; - logFn('Transpile', `will use ${TscTranspiler.P} transpile worker`); - this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); - } - async join() { - // wait for all penindg jobs - this._consumeQueue(); - await Promise.allSettled(this._allJobs); - this._allJobs.length = 0; - // terminate all worker - this._workerPool.forEach(w => w.terminate()); - this._workerPool.length = 0; - } - transpile(file) { - if (this._cmdLine.options.noEmit) { - // not doing ANYTHING here - return; - } - const newLen = this._queue.push(file); - if (newLen > TscTranspiler.P ** 2) { - this._consumeQueue(); - } - } - _consumeQueue() { - if (this._queue.length === 0) { - // no work... - return; - } - // kinda LAZYily create workers - if (this._workerPool.length === 0) { - for (let i = 0; i < TscTranspiler.P; i++) { - this._workerPool.push(new TranspileWorker(file => this._outputFileNames.getOutputFileName(file))); - } - } - const freeWorker = this._workerPool.filter(w => !w.isBusy); - if (freeWorker.length === 0) { - // OK, they will pick up work themselves - return; - } - for (const worker of freeWorker) { - if (this._queue.length === 0) { - break; - } - const job = new Promise(resolve => { - const consume = () => { - const files = this._queue.splice(0, TscTranspiler.P); - if (files.length === 0) { - // DONE - resolve(undefined); - return; - } - // work on the NEXT file - // const [inFile, outFn] = req; - worker.next(files, { compilerOptions: this._cmdLine.options }).then(outFiles => { - if (this.onOutfile) { - outFiles.map(this.onOutfile, this); - } - consume(); - }).catch(err => { - this._onError(err); - }); - }; - consume(); - }); - this._allJobs.push(job); - } - } -} -exports.TscTranspiler = TscTranspiler; -class ESBuildTranspiler { - _logFn; - _onError; - _cmdLine; - _outputFileNames; - _jobs = []; - onOutfile; - _transformOpts; - constructor(_logFn, _onError, configFilePath, _cmdLine) { - this._logFn = _logFn; - this._onError = _onError; - this._cmdLine = _cmdLine; - _logFn('Transpile', `will use ESBuild to transpile source files`); - this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); - const isExtension = configFilePath.includes('extensions'); - this._transformOpts = { - target: ['es2022'], - format: isExtension ? 'cjs' : 'esm', - platform: isExtension ? 'node' : undefined, - loader: 'ts', - sourcemap: 'inline', - tsconfigRaw: JSON.stringify({ - compilerOptions: { - ...this._cmdLine.options, - ...{ - module: isExtension ? typescript_1.default.ModuleKind.CommonJS : undefined - } - } - }), - supported: { - 'class-static-blocks': false, // SEE https://github.com/evanw/esbuild/issues/3823, - 'dynamic-import': !isExtension, // see https://github.com/evanw/esbuild/issues/1281 - 'class-field': !isExtension - } - }; - } - async join() { - const jobs = this._jobs.slice(); - this._jobs.length = 0; - await Promise.allSettled(jobs); - } - transpile(file) { - if (!(file.contents instanceof Buffer)) { - throw Error('file.contents must be a Buffer'); - } - const t1 = Date.now(); - this._jobs.push(esbuild_1.default.transform(file.contents, { - ...this._transformOpts, - sourcefile: file.path, - }).then(result => { - // check if output of a DTS-files isn't just "empty" and iff so - // skip this file - if (file.path.endsWith('.d.ts') && _isDefaultEmpty(result.code)) { - return; - } - const outBase = this._cmdLine.options.outDir ?? file.base; - const outPath = this._outputFileNames.getOutputFileName(file.path); - this.onOutfile(new vinyl_1.default({ - path: outPath, - base: outBase, - contents: Buffer.from(result.code), - })); - this._logFn('Transpile', `esbuild took ${Date.now() - t1}ms for ${file.path}`); - }).catch(err => { - this._onError(err); - })); - } -} -exports.ESBuildTranspiler = ESBuildTranspiler; -function _isDefaultEmpty(src) { - return src - .replace('"use strict";', '') - .replace(/\/\/# sourceMappingURL.*^/, '') - .replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1') - .trim().length === 0; -} -//# sourceMappingURL=transpiler.js.map \ No newline at end of file diff --git a/build/lib/tsb/transpiler.ts b/build/lib/tsb/transpiler.ts index 16a3b34753884..72883a2ab0c53 100644 --- a/build/lib/tsb/transpiler.ts +++ b/build/lib/tsb/transpiler.ts @@ -8,6 +8,7 @@ import ts from 'typescript'; import threads from 'node:worker_threads'; import Vinyl from 'vinyl'; import { cpus } from 'node:os'; +import { getTargetStringFromTsConfig } from '../tsconfigUtils.ts'; interface TranspileReq { readonly tsSrcs: string[]; @@ -64,7 +65,7 @@ class OutputFileNameOracle { try { // windows: path-sep normalizing - file = (ts).normalizePath(file); + file = (ts as InternalTsApi).normalizePath(file); if (!cmdLine.options.configFilePath) { // this is needed for the INTERNAL getOutputFileNames-call below... @@ -75,7 +76,7 @@ class OutputFileNameOracle { file = file.slice(0, -5) + '.ts'; cmdLine.fileNames.push(file); } - const outfile = (ts).getOutputFileNames(cmdLine, file, true)[0]; + const outfile = (ts as InternalTsApi).getOutputFileNames(cmdLine, file, true)[0]; if (isDts) { cmdLine.fileNames.pop(); } @@ -96,7 +97,7 @@ class TranspileWorker { readonly id = TranspileWorker.pool++; - private _worker = new threads.Worker(__filename); + private _worker = new threads.Worker(import.meta.filename); private _pending?: [resolve: Function, reject: Function, file: Vinyl[], options: ts.TranspileOptions, t1: number]; private _durations: number[] = []; @@ -123,11 +124,11 @@ class TranspileWorker { diag.push(...diag); continue; } - const enum SuffixTypes { - Dts = 5, - Ts = 3, - Unknown = 0 - } + const SuffixTypes = { + Dts: 5, + Ts: 3, + Unknown: 0 + } as const; const suffixLen = file.path.endsWith('.d.ts') ? SuffixTypes.Dts : file.path.endsWith('.ts') ? SuffixTypes.Ts : SuffixTypes.Unknown; @@ -200,16 +201,23 @@ export class TscTranspiler implements ITranspiler { private _workerPool: TranspileWorker[] = []; private _queue: Vinyl[] = []; - private _allJobs: Promise[] = []; + private _allJobs: Promise[] = []; + + private readonly _logFn: (topic: string, message: string) => void; + private readonly _onError: (err: any) => void; + private readonly _cmdLine: ts.ParsedCommandLine; constructor( logFn: (topic: string, message: string) => void, - private readonly _onError: (err: any) => void, + onError: (err: any) => void, configFilePath: string, - private readonly _cmdLine: ts.ParsedCommandLine + cmdLine: ts.ParsedCommandLine ) { - logFn('Transpile', `will use ${TscTranspiler.P} transpile worker`); - this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); + this._logFn = logFn; + this._onError = onError; + this._cmdLine = cmdLine; + this._logFn('Transpile', `will use ${TscTranspiler.P} transpile worker`); + this._outputFileNames = new OutputFileNameOracle(this._cmdLine, configFilePath); } async join() { @@ -299,20 +307,28 @@ export class ESBuildTranspiler implements ITranspiler { onOutfile?: ((file: Vinyl) => void) | undefined; private readonly _transformOpts: esbuild.TransformOptions; + private readonly _logFn: (topic: string, message: string) => void; + private readonly _onError: (err: any) => void; + private readonly _cmdLine: ts.ParsedCommandLine; constructor( - private readonly _logFn: (topic: string, message: string) => void, - private readonly _onError: (err: any) => void, + logFn: (topic: string, message: string) => void, + onError: (err: any) => void, configFilePath: string, - private readonly _cmdLine: ts.ParsedCommandLine + cmdLine: ts.ParsedCommandLine ) { - _logFn('Transpile', `will use ESBuild to transpile source files`); - this._outputFileNames = new OutputFileNameOracle(_cmdLine, configFilePath); + this._logFn = logFn; + this._onError = onError; + this._cmdLine = cmdLine; + this._logFn('Transpile', `will use ESBuild to transpile source files`); + this._outputFileNames = new OutputFileNameOracle(this._cmdLine, configFilePath); const isExtension = configFilePath.includes('extensions'); + const target = getTargetStringFromTsConfig(configFilePath); + this._transformOpts = { - target: ['es2022'], + target: [target], format: isExtension ? 'cjs' : 'esm', platform: isExtension ? 'node' : undefined, loader: 'ts', diff --git a/build/lib/tsb/utils.js b/build/lib/tsb/utils.js deleted file mode 100644 index 2ea820c6e6bec..0000000000000 --- a/build/lib/tsb/utils.js +++ /dev/null @@ -1,96 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.graph = exports.strings = void 0; -var strings; -(function (strings) { - function format(value, ...rest) { - return value.replace(/({\d+})/g, function (match) { - const index = Number(match.substring(1, match.length - 1)); - return String(rest[index]) || match; - }); - } - strings.format = format; -})(strings || (exports.strings = strings = {})); -var graph; -(function (graph) { - class Node { - data; - incoming = new Map(); - outgoing = new Map(); - constructor(data) { - this.data = data; - } - } - graph.Node = Node; - class Graph { - _nodes = new Map(); - inertEdge(from, to) { - const fromNode = this.lookupOrInsertNode(from); - const toNode = this.lookupOrInsertNode(to); - fromNode.outgoing.set(toNode.data, toNode); - toNode.incoming.set(fromNode.data, fromNode); - } - resetNode(data) { - const node = this._nodes.get(data); - if (!node) { - return; - } - for (const outDep of node.outgoing.values()) { - outDep.incoming.delete(node.data); - } - node.outgoing.clear(); - } - lookupOrInsertNode(data) { - let node = this._nodes.get(data); - if (!node) { - node = new Node(data); - this._nodes.set(data, node); - } - return node; - } - lookup(data) { - return this._nodes.get(data) ?? null; - } - findCycles(allData) { - const result = new Map(); - const checked = new Set(); - for (const data of allData) { - const node = this.lookup(data); - if (!node) { - continue; - } - const r = this._findCycle(node, checked, new Set()); - result.set(node.data, r); - } - return result; - } - _findCycle(node, checked, seen) { - if (checked.has(node.data)) { - return undefined; - } - let result; - for (const child of node.outgoing.values()) { - if (seen.has(child.data)) { - const seenArr = Array.from(seen); - const idx = seenArr.indexOf(child.data); - seenArr.push(child.data); - return idx > 0 ? seenArr.slice(idx) : seenArr; - } - seen.add(child.data); - result = this._findCycle(child, checked, seen); - seen.delete(child.data); - if (result) { - break; - } - } - checked.add(node.data); - return result; - } - } - graph.Graph = Graph; -})(graph || (exports.graph = graph = {})); -//# sourceMappingURL=utils.js.map \ No newline at end of file diff --git a/build/lib/tsb/utils.ts b/build/lib/tsb/utils.ts index 16f93d6838f99..4c5abb3e9c664 100644 --- a/build/lib/tsb/utils.ts +++ b/build/lib/tsb/utils.ts @@ -3,29 +3,32 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export namespace strings { +export const strings = (() => { - export function format(value: string, ...rest: any[]): string { - return value.replace(/({\d+})/g, function (match) { + function format(value: string, ...rest: unknown[]): string { + return value.replace(/(\{\d+\})/g, function (match) { const index = Number(match.substring(1, match.length - 1)); return String(rest[index]) || match; }); } -} -export namespace graph { + return { format }; +})(); - export class Node { +export const graph = (() => { + + class Node { readonly incoming = new Map>(); readonly outgoing = new Map>(); + readonly data: T; - constructor(readonly data: T) { - + constructor(data: T) { + this.data = data; } } - export class Graph { + class Graph { private _nodes = new Map>(); @@ -103,4 +106,5 @@ export namespace graph { } } -} + return { Node, Graph }; +})(); diff --git a/build/lib/tsconfigUtils.ts b/build/lib/tsconfigUtils.ts new file mode 100644 index 0000000000000..0afb2f02ae7c2 --- /dev/null +++ b/build/lib/tsconfigUtils.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { dirname } from 'path'; +import ts from 'typescript'; + +/** + * Get the target (e.g. 'ES2024') from a tsconfig.json file. + */ +export function getTargetStringFromTsConfig(configFilePath: string): string { + const parsed = ts.readConfigFile(configFilePath, ts.sys.readFile); + if (parsed.error) { + throw new Error(`Cannot determine target from ${configFilePath}. TS error: ${parsed.error.messageText}`); + } + + const cmdLine = ts.parseJsonConfigFileContent(parsed.config, ts.sys, dirname(configFilePath), {}); + const resolved = typeof cmdLine.options.target !== 'undefined' ? ts.ScriptTarget[cmdLine.options.target] : undefined; + if (!resolved) { + throw new Error(`Could not resolve target in ${configFilePath}`); + } + return resolved; +} + diff --git a/build/lib/typeScriptLanguageServiceHost.ts b/build/lib/typeScriptLanguageServiceHost.ts new file mode 100644 index 0000000000000..94c304fe09457 --- /dev/null +++ b/build/lib/typeScriptLanguageServiceHost.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import ts from 'typescript'; +import fs from 'node:fs'; +import { normalize } from 'node:path'; + +export type IFileMap = Map; + +function normalizePath(filePath: string): string { + return normalize(filePath); +} + +/** + * A TypeScript language service host + */ +export class TypeScriptLanguageServiceHost implements ts.LanguageServiceHost { + + private readonly ts: typeof import('typescript'); + private readonly topLevelFiles: IFileMap; + private readonly compilerOptions: ts.CompilerOptions; + + constructor( + ts: typeof import('typescript'), + topLevelFiles: IFileMap, + compilerOptions: ts.CompilerOptions, + ) { + this.ts = ts; + this.topLevelFiles = topLevelFiles; + this.compilerOptions = compilerOptions; + } + + // --- language service host --------------- + getCompilationSettings(): ts.CompilerOptions { + return this.compilerOptions; + } + getScriptFileNames(): string[] { + return [ + ...this.topLevelFiles.keys(), + this.ts.getDefaultLibFilePath(this.compilerOptions) + ]; + } + getScriptVersion(_fileName: string): string { + return '1'; + } + getProjectVersion(): string { + return '1'; + } + getScriptSnapshot(fileName: string): ts.IScriptSnapshot { + fileName = normalizePath(fileName); + + if (this.topLevelFiles.has(fileName)) { + return this.ts.ScriptSnapshot.fromString(this.topLevelFiles.get(fileName)!); + } else { + return ts.ScriptSnapshot.fromString(fs.readFileSync(fileName).toString()); + } + } + getScriptKind(_fileName: string): ts.ScriptKind { + return this.ts.ScriptKind.TS; + } + getCurrentDirectory(): string { + return ''; + } + getDefaultLibFileName(options: ts.CompilerOptions): string { + return this.ts.getDefaultLibFilePath(options); + } + readFile(path: string, encoding?: string): string | undefined { + path = normalizePath(path); + + if (this.topLevelFiles.get(path)) { + return this.topLevelFiles.get(path); + } + return ts.sys.readFile(path, encoding); + } + fileExists(path: string): boolean { + path = normalizePath(path); + + if (this.topLevelFiles.has(path)) { + return true; + } + return ts.sys.fileExists(path); + } +} diff --git a/build/lib/typings/@vscode/gulp-electron.d.ts b/build/lib/typings/@vscode/gulp-electron.d.ts new file mode 100644 index 0000000000000..aaf1b861a8743 --- /dev/null +++ b/build/lib/typings/@vscode/gulp-electron.d.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module '@vscode/gulp-electron' { + + interface MainFunction { + (options: any): NodeJS.ReadWriteStream; + dest(destination: string, options: any): NodeJS.ReadWriteStream; + } + + const main: MainFunction; + export default main; +} diff --git a/build/lib/typings/asar.d.ts b/build/lib/typings/asar.d.ts new file mode 100644 index 0000000000000..cdb5b6395c597 --- /dev/null +++ b/build/lib/typings/asar.d.ts @@ -0,0 +1,9 @@ +declare module 'asar/lib/filesystem.js' { + + export default class AsarFilesystem { + readonly header: unknown; + constructor(src: string); + insertDirectory(path: string, shouldUnpack?: boolean): unknown; + insertFile(path: string, shouldUnpack: boolean, file: { stat: { size: number; mode: number } }, options: {}): Promise; + } +} diff --git a/build/lib/typings/chromium-pickle-js.d.ts b/build/lib/typings/chromium-pickle-js.d.ts new file mode 100644 index 0000000000000..e2fcd8dc09633 --- /dev/null +++ b/build/lib/typings/chromium-pickle-js.d.ts @@ -0,0 +1,10 @@ +declare module 'chromium-pickle-js' { + export interface Pickle { + writeString(value: string): void; + writeUInt32(value: number): void; + + toBuffer(): Buffer; + } + + export function createEmpty(): Pickle; +} diff --git a/build/lib/typings/event-stream.d.ts b/build/lib/typings/event-stream.d.ts index 2b021ef258ee5..2b9679bfc82f0 100644 --- a/build/lib/typings/event-stream.d.ts +++ b/build/lib/typings/event-stream.d.ts @@ -23,5 +23,5 @@ declare module "event-stream" { function mapSync(cb: (data: I) => O): ThroughStream; function map(cb: (data: I, cb: (err?: Error, data?: O) => void) => O): ThroughStream; - function readable(asyncFunction: (this: ThroughStream, ...args: any[]) => any): any; -} \ No newline at end of file + function readable(asyncFunction: (this: ThroughStream, ...args: unknown[]) => any): any; +} diff --git a/build/lib/typings/github-releases.d.ts b/build/lib/typings/github-releases.d.ts deleted file mode 100644 index 5706a86b5e4ba..0000000000000 --- a/build/lib/typings/github-releases.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module 'github-releases' { - -} \ No newline at end of file diff --git a/build/lib/typings/gulp-azure-storage.d.ts b/build/lib/typings/gulp-azure-storage.d.ts new file mode 100644 index 0000000000000..4e9f560c8f2a7 --- /dev/null +++ b/build/lib/typings/gulp-azure-storage.d.ts @@ -0,0 +1,5 @@ +declare module 'gulp-azure-storage' { + import { ThroughStream } from 'event-stream'; + + export function upload(options: any): ThroughStream; +} diff --git a/build/lib/typings/gulp-flatmap.d.ts b/build/lib/typings/gulp-flatmap.d.ts deleted file mode 100644 index c99232c61cca9..0000000000000 --- a/build/lib/typings/gulp-flatmap.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -declare module 'gulp-flatmap' { - import File = require('vinyl'); - function f(fn:(stream:NodeJS.ReadWriteStream, file:File)=>NodeJS.ReadWriteStream): NodeJS.ReadWriteStream; - - /** - * This is required as per: - * https://github.com/microsoft/TypeScript/issues/5073 - */ - namespace f {} - - export = f; -} diff --git a/build/lib/typings/gulp-gunzip.d.ts b/build/lib/typings/gulp-gunzip.d.ts new file mode 100644 index 0000000000000..a68a350d5e7a5 --- /dev/null +++ b/build/lib/typings/gulp-gunzip.d.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'gulp-gunzip' { + import type { Transform } from 'stream'; + + /** + * Gunzip plugin for gulp + */ + function gunzip(): Transform; + + export = gunzip; +} diff --git a/build/lib/typings/gulp-untar.d.ts b/build/lib/typings/gulp-untar.d.ts new file mode 100644 index 0000000000000..b4007983cac4a --- /dev/null +++ b/build/lib/typings/gulp-untar.d.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'gulp-untar' { + import type { Transform } from 'stream'; + + function untar(): Transform; + + export = untar; +} diff --git a/build/lib/typings/gulp-vinyl-zip.d.ts b/build/lib/typings/gulp-vinyl-zip.d.ts new file mode 100644 index 0000000000000..d28166ffa7753 --- /dev/null +++ b/build/lib/typings/gulp-vinyl-zip.d.ts @@ -0,0 +1,4 @@ + +declare module 'gulp-vinyl-zip' { + export function src(): NodeJS.ReadWriteStream; +} diff --git a/build/lib/typings/lazy.js.d.ts b/build/lib/typings/lazy.js.d.ts deleted file mode 100644 index f69924b4b1ccf..0000000000000 --- a/build/lib/typings/lazy.js.d.ts +++ /dev/null @@ -1,276 +0,0 @@ -// Type definitions for Lazy.js 0.3.2 -// Project: https://github.com/dtao/lazy.js/ -// Definitions by: Bart van der Schoor -// Definitions: https://github.com/borisyankov/DefinitelyTyped - -declare function Lazy(value: string): Lazy.StringLikeSequence; -declare function Lazy(value: T[]): Lazy.ArrayLikeSequence; -declare function Lazy(value: any[]): Lazy.ArrayLikeSequence; -declare function Lazy(value: Object): Lazy.ObjectLikeSequence; -declare function Lazy(value: Object): Lazy.ObjectLikeSequence; - -declare module Lazy { - function strict(): StrictLazy; - function generate(generatorFn: GeneratorCallback, length?: number): GeneratedSequence; - function range(to: number): GeneratedSequence; - function range(from: number, to: number, step?: number): GeneratedSequence; - function repeat(value: T, count?: number): GeneratedSequence; - function on(eventType: string): Sequence; - function readFile(path: string): StringLikeSequence; - function makeHttpRequest(path: string): StringLikeSequence; - - interface StrictLazy { - (value: string): StringLikeSequence; - (value: T[]): ArrayLikeSequence; - (value: any[]): ArrayLikeSequence; - (value: Object): ObjectLikeSequence; - (value: Object): ObjectLikeSequence; - strict(): StrictLazy; - generate(generatorFn: GeneratorCallback, length?: number): GeneratedSequence; - range(to: number): GeneratedSequence; - range(from: number, to: number, step?: number): GeneratedSequence; - repeat(value: T, count?: number): GeneratedSequence; - on(eventType: string): Sequence; - readFile(path: string): StringLikeSequence; - makeHttpRequest(path: string): StringLikeSequence; - } - - interface ArrayLike { - length: number; - [index: number]: T; - } - - interface Callback { - (): void; - } - - interface ErrorCallback { - (error: any): void; - } - - interface ValueCallback { - (value: T): void; - } - - interface GetKeyCallback { - (value: T): string; - } - - interface TestCallback { - (value: T): boolean; - } - - interface MapCallback { - (value: T): U; - } - - interface MapStringCallback { - (value: string): string; - } - - interface NumberCallback { - (value: T): number; - } - - interface MemoCallback { - (memo: U, value: T): U; - } - - interface GeneratorCallback { - (index: number): T; - } - - interface CompareCallback { - (x: any, y: any): number; - } - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - interface Iterator { - new(sequence: Sequence): Iterator; - current(): T; - moveNext(): boolean; - } - - interface GeneratedSequence extends Sequence { - new(generatorFn: GeneratorCallback, length: number): GeneratedSequence; - length(): number; - } - - interface AsyncSequence extends SequenceBase { - each(callback: ValueCallback): AsyncHandle; - } - - interface AsyncHandle { - cancel(): void; - onComplete(callback: Callback): void; - onError(callback: ErrorCallback): void; - } - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - module Sequence { - function define(methodName: string[], overrides: Object): Function; - } - - interface Sequence extends SequenceBase { - each(eachFn: ValueCallback): Sequence; - } - - interface ArraySequence extends SequenceBase { - flatten(): Sequence; - } - - interface SequenceBase extends SequenceBaser { - first(): any; - first(count: number): Sequence; - indexOf(value: any, startIndex?: number): Sequence; - - last(): any; - last(count: number): Sequence; - lastIndexOf(value: any): Sequence; - - reverse(): Sequence; - } - - interface SequenceBaser { - // TODO improve define() (needs ugly overload) - async(interval: number): AsyncSequence; - chunk(size: number): Sequence; - compact(): Sequence; - concat(var_args: T[]): Sequence; - concat(sequence: Sequence): Sequence; - consecutive(length: number): Sequence; - contains(value: T): boolean; - countBy(keyFn: GetKeyCallback): ObjectLikeSequence; - countBy(propertyName: string): ObjectLikeSequence; - dropWhile(predicateFn: TestCallback): Sequence; - every(predicateFn: TestCallback): boolean; - filter(predicateFn: TestCallback): Sequence; - find(predicateFn: TestCallback): Sequence; - findWhere(properties: Object): Sequence; - - groupBy(keyFn: GetKeyCallback): ObjectLikeSequence; - initial(count?: number): Sequence; - intersection(var_args: T[]): Sequence; - invoke(methodName: string): Sequence; - isEmpty(): boolean; - join(delimiter?: string): string; - map(mapFn: MapCallback): ArraySequence; - map(mapFn: MapCallback): Sequence; - - // TODO: vscode addition to workaround strict null errors - flatten(): Sequence; - - max(valueFn?: NumberCallback): T; - min(valueFn?: NumberCallback): T; - none(valueFn?: TestCallback): boolean; - pluck(propertyName: string): Sequence; - reduce(aggregatorFn: MemoCallback, memo?: U): U; - reduceRight(aggregatorFn: MemoCallback, memo: U): U; - reject(predicateFn: TestCallback): Sequence; - rest(count?: number): Sequence; - shuffle(): Sequence; - some(predicateFn?: TestCallback): boolean; - sort(sortFn?: CompareCallback, descending?: boolean): Sequence; - sortBy(sortFn: string, descending?: boolean): Sequence; - sortBy(sortFn: NumberCallback, descending?: boolean): Sequence; - sortedIndex(value: T): Sequence; - size(): number; - sum(valueFn?: NumberCallback): Sequence; - takeWhile(predicateFn: TestCallback): Sequence; - union(var_args: T[]): Sequence; - uniq(): Sequence; - where(properties: Object): Sequence; - without(...var_args: T[]): Sequence; - without(var_args: T[]): Sequence; - zip(var_args: T[]): ArraySequence; - - toArray(): T[]; - toObject(): Object; - } - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - module ArrayLikeSequence { - function define(methodName: string[], overrides: Object): Function; - } - - interface ArrayLikeSequence extends Sequence { - // define()X; - concat(var_args: T[]): ArrayLikeSequence; - concat(sequence: Sequence): Sequence; - first(count?: number): ArrayLikeSequence; - get(index: number): T; - length(): number; - map(mapFn: MapCallback): ArraySequence; - map(mapFn: MapCallback): ArrayLikeSequence; - pop(): ArrayLikeSequence; - rest(count?: number): ArrayLikeSequence; - reverse(): ArrayLikeSequence; - shift(): ArrayLikeSequence; - slice(begin: number, end?: number): ArrayLikeSequence; - } - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - module ObjectLikeSequence { - function define(methodName: string[], overrides: Object): Function; - } - - interface ObjectLikeSequence extends Sequence { - assign(other: Object): ObjectLikeSequence; - // throws error - //async(): X; - defaults(defaults: Object): ObjectLikeSequence; - functions(): Sequence; - get(property: string): ObjectLikeSequence; - invert(): ObjectLikeSequence; - keys(): Sequence; - omit(properties: string[]): ObjectLikeSequence; - pairs(): Sequence; - pick(properties: string[]): ObjectLikeSequence; - toArray(): T[]; - toObject(): Object; - values(): Sequence; - } - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - module StringLikeSequence { - function define(methodName: string[], overrides: Object): Function; - } - - interface StringLikeSequence extends SequenceBaser { - charAt(index: number): string; - charCodeAt(index: number): number; - contains(value: string): boolean; - endsWith(suffix: string): boolean; - - first(): string; - first(count: number): StringLikeSequence; - - indexOf(substring: string, startIndex?: number): number; - - last(): string; - last(count: number): StringLikeSequence; - - lastIndexOf(substring: string, startIndex?: number): number; - mapString(mapFn: MapStringCallback): StringLikeSequence; - match(pattern: RegExp): StringLikeSequence; - reverse(): StringLikeSequence; - - split(delimiter: string): StringLikeSequence; - split(delimiter: RegExp): StringLikeSequence; - - startsWith(prefix: string): boolean; - substring(start: number, stop?: number): StringLikeSequence; - toLowerCase(): StringLikeSequence; - toUpperCase(): StringLikeSequence; - } -} - -declare module 'lazy.js' { - export = Lazy; -} - diff --git a/build/lib/typings/rcedit.d.ts b/build/lib/typings/rcedit.d.ts new file mode 100644 index 0000000000000..e18d3f93584d9 --- /dev/null +++ b/build/lib/typings/rcedit.d.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'rcedit' { + export default function rcedit(exePath, options, cb): Promise; +} diff --git a/build/lib/typings/vinyl.d.ts b/build/lib/typings/vinyl.d.ts deleted file mode 100644 index 5062c5154f600..0000000000000 --- a/build/lib/typings/vinyl.d.ts +++ /dev/null @@ -1,135 +0,0 @@ -// Type definitions for vinyl 0.4.3 -// Project: https://github.com/wearefractal/vinyl -// Definitions by: vvakame , jedmao -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -declare module "vinyl" { - - import fs = require("fs"); - - /** - * A virtual file format. - */ - class File { - constructor(options?: { - /** - * Default: process.cwd() - */ - cwd?: string; - /** - * Used for relative pathing. Typically where a glob starts. - */ - base?: string; - /** - * Full path to the file. - */ - path?: string; - /** - * Path history. Has no effect if options.path is passed. - */ - history?: string[]; - /** - * The result of an fs.stat call. See fs.Stats for more information. - */ - stat?: fs.Stats; - /** - * File contents. - * Type: Buffer, Stream, or null - */ - contents?: Buffer | NodeJS.ReadWriteStream; - }); - - /** - * Default: process.cwd() - */ - public cwd: string; - /** - * Used for relative pathing. Typically where a glob starts. - */ - public base: string; - /** - * Gets and sets the basename of `file.path`. - * - * Throws when `file.path` is not set. - * - * Example: - * - * ```js - * var file = new File({ - * cwd: '/', - * base: '/test/', - * path: '/test/file.js' - * }); - * - * console.log(file.basename); // file.js - * - * file.basename = 'file.txt'; - * - * console.log(file.basename); // file.txt - * console.log(file.path); // /test/file.txt - * ``` - */ - basename: string; - /** - * Full path to the file. - */ - public path: string; - public stat: fs.Stats; - /** - * Type: Buffer|Stream|null (Default: null) - */ - public contents: Buffer | NodeJS.ReadableStream; - /** - * Returns path.relative for the file base and file path. - * Example: - * var file = new File({ - * cwd: "/", - * base: "/test/", - * path: "/test/file.js" - * }); - * console.log(file.relative); // file.js - */ - public relative: string; - - public isBuffer(): boolean; - - public isStream(): boolean; - - public isNull(): boolean; - - public isDirectory(): boolean; - - /** - * Returns a new File object with all attributes cloned. Custom attributes are deep-cloned. - */ - public clone(opts?: { contents?: boolean }): File; - - /** - * If file.contents is a Buffer, it will write it to the stream. - * If file.contents is a Stream, it will pipe it to the stream. - * If file.contents is null, it will do nothing. - */ - public pipe( - stream: T, - opts?: { - /** - * If false, the destination stream will not be ended (same as node core). - */ - end?: boolean; - }): T; - - /** - * Returns a pretty String interpretation of the File. Useful for console.log. - */ - public inspect(): string; - } - - /** - * This is required as per: - * https://github.com/microsoft/TypeScript/issues/5073 - */ - namespace File { } - - export = File; - -} diff --git a/build/lib/typings/vscode-gulp-watch.d.ts b/build/lib/typings/vscode-gulp-watch.d.ts new file mode 100644 index 0000000000000..24316c07f16ca --- /dev/null +++ b/build/lib/typings/vscode-gulp-watch.d.ts @@ -0,0 +1,3 @@ +declare module 'vscode-gulp-watch' { + export default function watch(...args: any[]): any; +} diff --git a/build/lib/util.js b/build/lib/util.js deleted file mode 100644 index 8b6f039628173..0000000000000 --- a/build/lib/util.js +++ /dev/null @@ -1,314 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.incremental = incremental; -exports.debounce = debounce; -exports.fixWin32DirectoryPermissions = fixWin32DirectoryPermissions; -exports.setExecutableBit = setExecutableBit; -exports.toFileUri = toFileUri; -exports.skipDirectories = skipDirectories; -exports.cleanNodeModules = cleanNodeModules; -exports.loadSourcemaps = loadSourcemaps; -exports.stripSourceMappingURL = stripSourceMappingURL; -exports.$if = $if; -exports.appendOwnPathSourceURL = appendOwnPathSourceURL; -exports.rewriteSourceMappingURL = rewriteSourceMappingURL; -exports.rimraf = rimraf; -exports.rreddir = rreddir; -exports.ensureDir = ensureDir; -exports.rebase = rebase; -exports.filter = filter; -exports.streamToPromise = streamToPromise; -exports.getElectronVersion = getElectronVersion; -const event_stream_1 = __importDefault(require("event-stream")); -const debounce_1 = __importDefault(require("debounce")); -const gulp_filter_1 = __importDefault(require("gulp-filter")); -const gulp_rename_1 = __importDefault(require("gulp-rename")); -const path_1 = __importDefault(require("path")); -const fs_1 = __importDefault(require("fs")); -const rimraf_1 = __importDefault(require("rimraf")); -const url_1 = require("url"); -const ternary_stream_1 = __importDefault(require("ternary-stream")); -const root = path_1.default.dirname(path_1.default.dirname(__dirname)); -const NoCancellationToken = { isCancellationRequested: () => false }; -function incremental(streamProvider, initial, supportsCancellation) { - const input = event_stream_1.default.through(); - const output = event_stream_1.default.through(); - let state = 'idle'; - let buffer = Object.create(null); - const token = !supportsCancellation ? undefined : { isCancellationRequested: () => Object.keys(buffer).length > 0 }; - const run = (input, isCancellable) => { - state = 'running'; - const stream = !supportsCancellation ? streamProvider() : streamProvider(isCancellable ? token : NoCancellationToken); - input - .pipe(stream) - .pipe(event_stream_1.default.through(undefined, () => { - state = 'idle'; - eventuallyRun(); - })) - .pipe(output); - }; - if (initial) { - run(initial, false); - } - const eventuallyRun = (0, debounce_1.default)(() => { - const paths = Object.keys(buffer); - if (paths.length === 0) { - return; - } - const data = paths.map(path => buffer[path]); - buffer = Object.create(null); - run(event_stream_1.default.readArray(data), true); - }, 500); - input.on('data', (f) => { - buffer[f.path] = f; - if (state === 'idle') { - eventuallyRun(); - } - }); - return event_stream_1.default.duplex(input, output); -} -function debounce(task, duration = 500) { - const input = event_stream_1.default.through(); - const output = event_stream_1.default.through(); - let state = 'idle'; - const run = () => { - state = 'running'; - task() - .pipe(event_stream_1.default.through(undefined, () => { - const shouldRunAgain = state === 'stale'; - state = 'idle'; - if (shouldRunAgain) { - eventuallyRun(); - } - })) - .pipe(output); - }; - run(); - const eventuallyRun = (0, debounce_1.default)(() => run(), duration); - input.on('data', () => { - if (state === 'idle') { - eventuallyRun(); - } - else { - state = 'stale'; - } - }); - return event_stream_1.default.duplex(input, output); -} -function fixWin32DirectoryPermissions() { - if (!/win32/.test(process.platform)) { - return event_stream_1.default.through(); - } - return event_stream_1.default.mapSync(f => { - if (f.stat && f.stat.isDirectory && f.stat.isDirectory()) { - f.stat.mode = 16877; - } - return f; - }); -} -function setExecutableBit(pattern) { - const setBit = event_stream_1.default.mapSync(f => { - if (!f.stat) { - f.stat = { isFile() { return true; } }; - } - f.stat.mode = /* 100755 */ 33261; - return f; - }); - if (!pattern) { - return setBit; - } - const input = event_stream_1.default.through(); - const filter = (0, gulp_filter_1.default)(pattern, { restore: true }); - const output = input - .pipe(filter) - .pipe(setBit) - .pipe(filter.restore); - return event_stream_1.default.duplex(input, output); -} -function toFileUri(filePath) { - const match = filePath.match(/^([a-z])\:(.*)$/i); - if (match) { - filePath = '/' + match[1].toUpperCase() + ':' + match[2]; - } - return 'file://' + filePath.replace(/\\/g, '/'); -} -function skipDirectories() { - return event_stream_1.default.mapSync(f => { - if (!f.isDirectory()) { - return f; - } - }); -} -function cleanNodeModules(rulePath) { - const rules = fs_1.default.readFileSync(rulePath, 'utf8') - .split(/\r?\n/g) - .map(line => line.trim()) - .filter(line => line && !/^#/.test(line)); - const excludes = rules.filter(line => !/^!/.test(line)).map(line => `!**/node_modules/${line}`); - const includes = rules.filter(line => /^!/.test(line)).map(line => `**/node_modules/${line.substr(1)}`); - const input = event_stream_1.default.through(); - const output = event_stream_1.default.merge(input.pipe((0, gulp_filter_1.default)(['**', ...excludes])), input.pipe((0, gulp_filter_1.default)(includes))); - return event_stream_1.default.duplex(input, output); -} -function loadSourcemaps() { - const input = event_stream_1.default.through(); - const output = input - .pipe(event_stream_1.default.map((f, cb) => { - if (f.sourceMap) { - cb(undefined, f); - return; - } - if (!f.contents) { - cb(undefined, f); - return; - } - const contents = f.contents.toString('utf8'); - const reg = /\/\/# sourceMappingURL=(.*)$/g; - let lastMatch = null; - let match = null; - while (match = reg.exec(contents)) { - lastMatch = match; - } - if (!lastMatch) { - f.sourceMap = { - version: '3', - names: [], - mappings: '', - sources: [f.relative.replace(/\\/g, '/')], - sourcesContent: [contents] - }; - cb(undefined, f); - return; - } - f.contents = Buffer.from(contents.replace(/\/\/# sourceMappingURL=(.*)$/g, ''), 'utf8'); - fs_1.default.readFile(path_1.default.join(path_1.default.dirname(f.path), lastMatch[1]), 'utf8', (err, contents) => { - if (err) { - return cb(err); - } - f.sourceMap = JSON.parse(contents); - cb(undefined, f); - }); - })); - return event_stream_1.default.duplex(input, output); -} -function stripSourceMappingURL() { - const input = event_stream_1.default.through(); - const output = input - .pipe(event_stream_1.default.mapSync(f => { - const contents = f.contents.toString('utf8'); - f.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, ''), 'utf8'); - return f; - })); - return event_stream_1.default.duplex(input, output); -} -/** Splits items in the stream based on the predicate, sending them to onTrue if true, or onFalse otherwise */ -function $if(test, onTrue, onFalse = event_stream_1.default.through()) { - if (typeof test === 'boolean') { - return test ? onTrue : onFalse; - } - return (0, ternary_stream_1.default)(test, onTrue, onFalse); -} -/** Operator that appends the js files' original path a sourceURL, so debug locations map */ -function appendOwnPathSourceURL() { - const input = event_stream_1.default.through(); - const output = input - .pipe(event_stream_1.default.mapSync(f => { - if (!(f.contents instanceof Buffer)) { - throw new Error(`contents of ${f.path} are not a buffer`); - } - f.contents = Buffer.concat([f.contents, Buffer.from(`\n//# sourceURL=${(0, url_1.pathToFileURL)(f.path)}`)]); - return f; - })); - return event_stream_1.default.duplex(input, output); -} -function rewriteSourceMappingURL(sourceMappingURLBase) { - const input = event_stream_1.default.through(); - const output = input - .pipe(event_stream_1.default.mapSync(f => { - const contents = f.contents.toString('utf8'); - const str = `//# sourceMappingURL=${sourceMappingURLBase}/${path_1.default.dirname(f.relative).replace(/\\/g, '/')}/$1`; - f.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, str)); - return f; - })); - return event_stream_1.default.duplex(input, output); -} -function rimraf(dir) { - const result = () => new Promise((c, e) => { - let retries = 0; - const retry = () => { - (0, rimraf_1.default)(dir, { maxBusyTries: 1 }, (err) => { - if (!err) { - return c(); - } - if (err.code === 'ENOTEMPTY' && ++retries < 5) { - return setTimeout(() => retry(), 10); - } - return e(err); - }); - }; - retry(); - }); - result.taskName = `clean-${path_1.default.basename(dir).toLowerCase()}`; - return result; -} -function _rreaddir(dirPath, prepend, result) { - const entries = fs_1.default.readdirSync(dirPath, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - _rreaddir(path_1.default.join(dirPath, entry.name), `${prepend}/${entry.name}`, result); - } - else { - result.push(`${prepend}/${entry.name}`); - } - } -} -function rreddir(dirPath) { - const result = []; - _rreaddir(dirPath, '', result); - return result; -} -function ensureDir(dirPath) { - if (fs_1.default.existsSync(dirPath)) { - return; - } - ensureDir(path_1.default.dirname(dirPath)); - fs_1.default.mkdirSync(dirPath); -} -function rebase(count) { - return (0, gulp_rename_1.default)(f => { - const parts = f.dirname ? f.dirname.split(/[\/\\]/) : []; - f.dirname = parts.slice(count).join(path_1.default.sep); - }); -} -function filter(fn) { - const result = event_stream_1.default.through(function (data) { - if (fn(data)) { - this.emit('data', data); - } - else { - result.restore.push(data); - } - }); - result.restore = event_stream_1.default.through(); - return result; -} -function streamToPromise(stream) { - return new Promise((c, e) => { - stream.on('error', err => e(err)); - stream.on('end', () => c()); - }); -} -function getElectronVersion() { - const npmrc = fs_1.default.readFileSync(path_1.default.join(root, '.npmrc'), 'utf8'); - const electronVersion = /^target="(.*)"$/m.exec(npmrc)[1]; - const msBuildId = /^ms_build_id="(.*)"$/m.exec(npmrc)[1]; - return { electronVersion, msBuildId }; -} -//# sourceMappingURL=util.js.map \ No newline at end of file diff --git a/build/lib/util.ts b/build/lib/util.ts index ad81730b3de32..f1354b858c9fe 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -11,12 +11,12 @@ import path from 'path'; import fs from 'fs'; import _rimraf from 'rimraf'; import VinylFile from 'vinyl'; -import { ThroughStream } from 'through'; +import through from 'through'; import sm from 'source-map'; import { pathToFileURL } from 'url'; import ternaryStream from 'ternary-stream'; -const root = path.dirname(path.dirname(__dirname)); +const root = path.dirname(path.dirname(import.meta.dirname)); export interface ICancellationToken { isCancellationRequested(): boolean; @@ -129,9 +129,10 @@ export function fixWin32DirectoryPermissions(): NodeJS.ReadWriteStream { export function setExecutableBit(pattern?: string | string[]): NodeJS.ReadWriteStream { const setBit = es.mapSync(f => { if (!f.stat) { - f.stat = { isFile() { return true; } } as any; + const stat: Pick = { isFile() { return true; }, mode: 0 }; + f.stat = stat as fs.Stats; } - f.stat.mode = /* 100755 */ 33261; + f.stat!.mode = /* 100755 */ 33261; return f; }); @@ -185,9 +186,7 @@ export function cleanNodeModules(rulePath: string): NodeJS.ReadWriteStream { return es.duplex(input, output); } -declare class FileSourceMap extends VinylFile { - public sourceMap: sm.RawSourceMap; -} +type FileSourceMap = VinylFile & { sourceMap: sm.RawSourceMap }; export function loadSourcemaps(): NodeJS.ReadWriteStream { const input = es.through(); @@ -204,8 +203,7 @@ export function loadSourcemaps(): NodeJS.ReadWriteStream { return; } - const contents = (f.contents).toString('utf8'); - + const contents = (f.contents as Buffer).toString('utf8'); const reg = /\/\/# sourceMappingURL=(.*)$/g; let lastMatch: RegExpExecArray | null = null; let match: RegExpExecArray | null = null; @@ -245,7 +243,7 @@ export function stripSourceMappingURL(): NodeJS.ReadWriteStream { const output = input .pipe(es.mapSync(f => { - const contents = (f.contents).toString('utf8'); + const contents = (f.contents as Buffer).toString('utf8'); f.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, ''), 'utf8'); return f; })); @@ -284,7 +282,7 @@ export function rewriteSourceMappingURL(sourceMappingURLBase: string): NodeJS.Re const output = input .pipe(es.mapSync(f => { - const contents = (f.contents).toString('utf8'); + const contents = (f.contents as Buffer).toString('utf8'); const str = `//# sourceMappingURL=${sourceMappingURLBase}/${path.dirname(f.relative).replace(/\\/g, '/')}/$1`; f.contents = Buffer.from(contents.replace(/\n\/\/# sourceMappingURL=(.*)$/gm, str)); return f; @@ -351,17 +349,17 @@ export function rebase(count: number): NodeJS.ReadWriteStream { } export interface FilterStream extends NodeJS.ReadWriteStream { - restore: ThroughStream; + restore: through.ThroughStream; } export function filter(fn: (data: any) => boolean): FilterStream { - const result = es.through(function (data) { + const result = es.through(function (data) { if (fn(data)) { this.emit('data', data); } else { result.restore.push(data); } - }); + }) as unknown as FilterStream; result.restore = es.through(); return result; @@ -380,3 +378,54 @@ export function getElectronVersion(): Record { const msBuildId = /^ms_build_id="(.*)"$/m.exec(npmrc)![1]; return { electronVersion, msBuildId }; } + +export class VinylStat implements fs.Stats { + + readonly dev: number; + readonly ino: number; + readonly mode: number; + readonly nlink: number; + readonly uid: number; + readonly gid: number; + readonly rdev: number; + readonly size: number; + readonly blksize: number; + readonly blocks: number; + readonly atimeMs: number; + readonly mtimeMs: number; + readonly ctimeMs: number; + readonly birthtimeMs: number; + readonly atime: Date; + readonly mtime: Date; + readonly ctime: Date; + readonly birthtime: Date; + + constructor(stat: Partial) { + this.dev = stat.dev ?? 0; + this.ino = stat.ino ?? 0; + this.mode = stat.mode ?? 0; + this.nlink = stat.nlink ?? 0; + this.uid = stat.uid ?? 0; + this.gid = stat.gid ?? 0; + this.rdev = stat.rdev ?? 0; + this.size = stat.size ?? 0; + this.blksize = stat.blksize ?? 0; + this.blocks = stat.blocks ?? 0; + this.atimeMs = stat.atimeMs ?? 0; + this.mtimeMs = stat.mtimeMs ?? 0; + this.ctimeMs = stat.ctimeMs ?? 0; + this.birthtimeMs = stat.birthtimeMs ?? 0; + this.atime = stat.atime ?? new Date(0); + this.mtime = stat.mtime ?? new Date(0); + this.ctime = stat.ctime ?? new Date(0); + this.birthtime = stat.birthtime ?? new Date(0); + } + + isFile(): boolean { return true; } + isDirectory(): boolean { return false; } + isBlockDevice(): boolean { return false; } + isCharacterDevice(): boolean { return false; } + isSymbolicLink(): boolean { return false; } + isFIFO(): boolean { return false; } + isSocket(): boolean { return false; } +} diff --git a/build/lib/watch/index.js b/build/lib/watch/index.js deleted file mode 100644 index 69eca78fd704c..0000000000000 --- a/build/lib/watch/index.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -const watch = process.platform === 'win32' ? require('./watch-win32') : require('vscode-gulp-watch'); -module.exports = function () { - return watch.apply(null, arguments); -}; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/build/lib/watch/index.ts b/build/lib/watch/index.ts index ce4bdfd75edf1..763cacc6d893d 100644 --- a/build/lib/watch/index.ts +++ b/build/lib/watch/index.ts @@ -2,9 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { createRequire } from 'node:module'; -const watch = process.platform === 'win32' ? require('./watch-win32') : require('vscode-gulp-watch'); +const require = createRequire(import.meta.url); +const watch = process.platform === 'win32' ? require('./watch-win32.ts').default : require('vscode-gulp-watch'); -module.exports = function () { - return watch.apply(null, arguments); -}; +export default function (...args: any[]): ReturnType { + return watch.apply(null, args); +} diff --git a/build/lib/watch/watch-win32.js b/build/lib/watch/watch-win32.js deleted file mode 100644 index 7b77981d620ea..0000000000000 --- a/build/lib/watch/watch-win32.js +++ /dev/null @@ -1,104 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const path_1 = __importDefault(require("path")); -const child_process_1 = __importDefault(require("child_process")); -const fs_1 = __importDefault(require("fs")); -const vinyl_1 = __importDefault(require("vinyl")); -const event_stream_1 = __importDefault(require("event-stream")); -const gulp_filter_1 = __importDefault(require("gulp-filter")); -const watcherPath = path_1.default.join(__dirname, 'watcher.exe'); -function toChangeType(type) { - switch (type) { - case '0': return 'change'; - case '1': return 'add'; - default: return 'unlink'; - } -} -function watch(root) { - const result = event_stream_1.default.through(); - let child = child_process_1.default.spawn(watcherPath, [root]); - child.stdout.on('data', function (data) { - const lines = data.toString('utf8').split('\n'); - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim(); - if (line.length === 0) { - continue; - } - const changeType = line[0]; - const changePath = line.substr(2); - // filter as early as possible - if (/^\.git/.test(changePath) || /(^|\\)out($|\\)/.test(changePath)) { - continue; - } - const changePathFull = path_1.default.join(root, changePath); - const file = new vinyl_1.default({ - path: changePathFull, - base: root - }); - file.event = toChangeType(changeType); - result.emit('data', file); - } - }); - child.stderr.on('data', function (data) { - result.emit('error', data); - }); - child.on('exit', function (code) { - result.emit('error', 'Watcher died with code ' + code); - child = null; - }); - process.once('SIGTERM', function () { process.exit(0); }); - process.once('SIGTERM', function () { process.exit(0); }); - process.once('exit', function () { if (child) { - child.kill(); - } }); - return result; -} -const cache = Object.create(null); -module.exports = function (pattern, options) { - options = options || {}; - const cwd = path_1.default.normalize(options.cwd || process.cwd()); - let watcher = cache[cwd]; - if (!watcher) { - watcher = cache[cwd] = watch(cwd); - } - const rebase = !options.base ? event_stream_1.default.through() : event_stream_1.default.mapSync(function (f) { - f.base = options.base; - return f; - }); - return watcher - .pipe((0, gulp_filter_1.default)(['**', '!.git{,/**}'], { dot: options.dot })) // ignore all things git - .pipe((0, gulp_filter_1.default)(pattern, { dot: options.dot })) - .pipe(event_stream_1.default.map(function (file, cb) { - fs_1.default.stat(file.path, function (err, stat) { - if (err && err.code === 'ENOENT') { - return cb(undefined, file); - } - if (err) { - return cb(); - } - if (!stat.isFile()) { - return cb(); - } - fs_1.default.readFile(file.path, function (err, contents) { - if (err && err.code === 'ENOENT') { - return cb(undefined, file); - } - if (err) { - return cb(); - } - file.contents = contents; - file.stat = stat; - cb(undefined, file); - }); - }); - })) - .pipe(rebase); -}; -//# sourceMappingURL=watch-win32.js.map \ No newline at end of file diff --git a/build/lib/watch/watch-win32.ts b/build/lib/watch/watch-win32.ts index bbfde6afba98b..12b8ffc0ac3ea 100644 --- a/build/lib/watch/watch-win32.ts +++ b/build/lib/watch/watch-win32.ts @@ -11,7 +11,7 @@ import es from 'event-stream'; import filter from 'gulp-filter'; import { Stream } from 'stream'; -const watcherPath = path.join(__dirname, 'watcher.exe'); +const watcherPath = path.join(import.meta.dirname, 'watcher.exe'); function toChangeType(type: '0' | '1' | '2'): 'change' | 'add' | 'unlink' { switch (type) { @@ -33,7 +33,7 @@ function watch(root: string): Stream { continue; } - const changeType = <'0' | '1' | '2'>line[0]; + const changeType = line[0] as '0' | '1' | '2'; const changePath = line.substr(2); // filter as early as possible @@ -47,7 +47,7 @@ function watch(root: string): Stream { path: changePathFull, base: root }); - (file).event = toChangeType(changeType); + file.event = toChangeType(changeType); result.emit('data', file); } }); @@ -70,7 +70,7 @@ function watch(root: string): Stream { const cache: { [cwd: string]: Stream } = Object.create(null); -module.exports = function (pattern: string | string[] | filter.FileFunction, options?: { cwd?: string; base?: string; dot?: boolean }) { +export default function (pattern: string | string[] | filter.FileFunction, options?: { cwd?: string; base?: string; dot?: boolean }) { options = options || {}; const cwd = path.normalize(options.cwd || process.cwd()); @@ -105,4 +105,4 @@ module.exports = function (pattern: string | string[] | filter.FileFunction, opt }); })) .pipe(rebase); -}; +} diff --git a/build/linux/debian/calculate-deps.js b/build/linux/debian/calculate-deps.js deleted file mode 100644 index 34276ce7705c5..0000000000000 --- a/build/linux/debian/calculate-deps.js +++ /dev/null @@ -1,89 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.generatePackageDeps = generatePackageDeps; -const child_process_1 = require("child_process"); -const fs_1 = require("fs"); -const os_1 = require("os"); -const path_1 = __importDefault(require("path")); -const cgmanifest_json_1 = __importDefault(require("../../../cgmanifest.json")); -const dep_lists_1 = require("./dep-lists"); -function generatePackageDeps(files, arch, chromiumSysroot, vscodeSysroot) { - const dependencies = files.map(file => calculatePackageDeps(file, arch, chromiumSysroot, vscodeSysroot)); - const additionalDepsSet = new Set(dep_lists_1.additionalDeps); - dependencies.push(additionalDepsSet); - return dependencies; -} -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/calculate_package_deps.py. -function calculatePackageDeps(binaryPath, arch, chromiumSysroot, vscodeSysroot) { - try { - if (!((0, fs_1.statSync)(binaryPath).mode & fs_1.constants.S_IXUSR)) { - throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); - } - } - catch (e) { - // The package might not exist. Don't re-throw the error here. - console.error('Tried to stat ' + binaryPath + ' but failed.'); - } - // Get the Chromium dpkg-shlibdeps file. - const chromiumManifest = cgmanifest_json_1.default.registrations.filter(registration => { - return registration.component.type === 'git' && registration.component.git.name === 'chromium'; - }); - const dpkgShlibdepsUrl = `https://raw.githubusercontent.com/chromium/chromium/${chromiumManifest[0].version}/third_party/dpkg-shlibdeps/dpkg-shlibdeps.pl`; - const dpkgShlibdepsScriptLocation = `${(0, os_1.tmpdir)()}/dpkg-shlibdeps.pl`; - const result = (0, child_process_1.spawnSync)('curl', [dpkgShlibdepsUrl, '-o', dpkgShlibdepsScriptLocation]); - if (result.status !== 0) { - throw new Error('Cannot retrieve dpkg-shlibdeps. Stderr:\n' + result.stderr); - } - const cmd = [dpkgShlibdepsScriptLocation, '--ignore-weak-undefined']; - switch (arch) { - case 'amd64': - cmd.push(`-l${chromiumSysroot}/usr/lib/x86_64-linux-gnu`, `-l${chromiumSysroot}/lib/x86_64-linux-gnu`, `-l${vscodeSysroot}/usr/lib/x86_64-linux-gnu`, `-l${vscodeSysroot}/lib/x86_64-linux-gnu`); - break; - case 'armhf': - cmd.push(`-l${chromiumSysroot}/usr/lib/arm-linux-gnueabihf`, `-l${chromiumSysroot}/lib/arm-linux-gnueabihf`, `-l${vscodeSysroot}/usr/lib/arm-linux-gnueabihf`, `-l${vscodeSysroot}/lib/arm-linux-gnueabihf`); - break; - case 'arm64': - cmd.push(`-l${chromiumSysroot}/usr/lib/aarch64-linux-gnu`, `-l${chromiumSysroot}/lib/aarch64-linux-gnu`, `-l${vscodeSysroot}/usr/lib/aarch64-linux-gnu`, `-l${vscodeSysroot}/lib/aarch64-linux-gnu`); - break; - } - cmd.push(`-l${chromiumSysroot}/usr/lib`); - cmd.push(`-L${vscodeSysroot}/debian/libxkbfile1/DEBIAN/shlibs`); - cmd.push('-O', '-e', path_1.default.resolve(binaryPath)); - const dpkgShlibdepsResult = (0, child_process_1.spawnSync)('perl', cmd, { cwd: chromiumSysroot }); - if (dpkgShlibdepsResult.status !== 0) { - throw new Error(`dpkg-shlibdeps failed with exit code ${dpkgShlibdepsResult.status}. stderr:\n${dpkgShlibdepsResult.stderr} `); - } - const shlibsDependsPrefix = 'shlibs:Depends='; - const requiresList = dpkgShlibdepsResult.stdout.toString('utf-8').trimEnd().split('\n'); - let depsStr = ''; - for (const line of requiresList) { - if (line.startsWith(shlibsDependsPrefix)) { - depsStr = line.substring(shlibsDependsPrefix.length); - } - } - // Refs https://chromium-review.googlesource.com/c/chromium/src/+/3572926 - // Chromium depends on libgcc_s, is from the package libgcc1. However, in - // Bullseye, the package was renamed to libgcc-s1. To avoid adding a dep - // on the newer package, this hack skips the dep. This is safe because - // libgcc-s1 is a dependency of libc6. This hack can be removed once - // support for Debian Buster and Ubuntu Bionic are dropped. - // - // Remove kerberos native module related dependencies as the versions - // computed from sysroot will not satisfy the minimum supported distros - // Refs https://github.com/microsoft/vscode/issues/188881. - // TODO(deepak1556): remove this workaround in favor of computing the - // versions from build container for native modules. - const filteredDeps = depsStr.split(', ').filter(dependency => { - return !dependency.startsWith('libgcc-s1'); - }).sort(); - const requires = new Set(filteredDeps); - return requires; -} -//# sourceMappingURL=calculate-deps.js.map \ No newline at end of file diff --git a/build/linux/debian/calculate-deps.ts b/build/linux/debian/calculate-deps.ts index addc38696a880..98a96302e19ec 100644 --- a/build/linux/debian/calculate-deps.ts +++ b/build/linux/debian/calculate-deps.ts @@ -7,9 +7,9 @@ import { spawnSync } from 'child_process'; import { constants, statSync } from 'fs'; import { tmpdir } from 'os'; import path from 'path'; -import manifests from '../../../cgmanifest.json'; -import { additionalDeps } from './dep-lists'; -import { DebianArchString } from './types'; +import manifests from '../../../cgmanifest.json' with { type: 'json' }; +import { additionalDeps } from './dep-lists.ts'; +import type { DebianArchString } from './types.ts'; export function generatePackageDeps(files: string[], arch: DebianArchString, chromiumSysroot: string, vscodeSysroot: string): Set[] { const dependencies: Set[] = files.map(file => calculatePackageDeps(file, arch, chromiumSysroot, vscodeSysroot)); diff --git a/build/linux/debian/dep-lists.js b/build/linux/debian/dep-lists.js deleted file mode 100644 index 4ef448d454eb9..0000000000000 --- a/build/linux/debian/dep-lists.js +++ /dev/null @@ -1,143 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.referenceGeneratedDepsByArch = exports.recommendedDeps = exports.additionalDeps = void 0; -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/additional_deps -// Additional dependencies not in the dpkg-shlibdeps output. -exports.additionalDeps = [ - 'ca-certificates', // Make sure users have SSL certificates. - 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libnss3 (>= 3.26)', - 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', // For Breakpad crash reports. - 'xdg-utils (>= 1.0.2)', // OS integration -]; -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/debian/manual_recommends -// Dependencies that we can only recommend -// for now since some of the older distros don't support them. -exports.recommendedDeps = [ - 'libvulkan1' // Move to additionalDeps once support for Trusty and Jessie are dropped. -]; -exports.referenceGeneratedDepsByArch = { - 'amd64': [ - 'ca-certificates', - 'libasound2 (>= 1.0.17)', - 'libatk-bridge2.0-0 (>= 2.5.3)', - 'libatk1.0-0 (>= 2.11.90)', - 'libatspi2.0-0 (>= 2.9.90)', - 'libc6 (>= 2.14)', - 'libc6 (>= 2.16)', - 'libc6 (>= 2.17)', - 'libc6 (>= 2.2.5)', - 'libc6 (>= 2.25)', - 'libc6 (>= 2.28)', - 'libcairo2 (>= 1.6.0)', - 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', - 'libdbus-1-3 (>= 1.9.14)', - 'libexpat1 (>= 2.1~beta3)', - 'libgbm1 (>= 17.1.0~rc2)', - 'libglib2.0-0 (>= 2.37.3)', - 'libgtk-3-0 (>= 3.9.10)', - 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libnspr4 (>= 2:4.9-2~)', - 'libnss3 (>= 2:3.30)', - 'libnss3 (>= 3.26)', - 'libpango-1.0-0 (>= 1.14.0)', - 'libudev1 (>= 183)', - 'libx11-6', - 'libx11-6 (>= 2:1.4.99.1)', - 'libxcb1 (>= 1.9.2)', - 'libxcomposite1 (>= 1:0.4.4-1)', - 'libxdamage1 (>= 1:1.1)', - 'libxext6', - 'libxfixes3', - 'libxkbcommon0 (>= 0.5.0)', - 'libxkbfile1 (>= 1:1.1.0)', - 'libxrandr2', - 'xdg-utils (>= 1.0.2)' - ], - 'armhf': [ - 'ca-certificates', - 'libasound2 (>= 1.0.17)', - 'libatk-bridge2.0-0 (>= 2.5.3)', - 'libatk1.0-0 (>= 2.11.90)', - 'libatspi2.0-0 (>= 2.9.90)', - 'libc6 (>= 2.16)', - 'libc6 (>= 2.17)', - 'libc6 (>= 2.25)', - 'libc6 (>= 2.28)', - 'libc6 (>= 2.4)', - 'libc6 (>= 2.9)', - 'libcairo2 (>= 1.6.0)', - 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', - 'libdbus-1-3 (>= 1.9.14)', - 'libexpat1 (>= 2.1~beta3)', - 'libgbm1 (>= 17.1.0~rc2)', - 'libglib2.0-0 (>= 2.37.3)', - 'libgtk-3-0 (>= 3.9.10)', - 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libnspr4 (>= 2:4.9-2~)', - 'libnss3 (>= 2:3.30)', - 'libnss3 (>= 3.26)', - 'libpango-1.0-0 (>= 1.14.0)', - 'libstdc++6 (>= 4.1.1)', - 'libstdc++6 (>= 5)', - 'libstdc++6 (>= 5.2)', - 'libstdc++6 (>= 6)', - 'libstdc++6 (>= 9)', - 'libudev1 (>= 183)', - 'libx11-6', - 'libx11-6 (>= 2:1.4.99.1)', - 'libxcb1 (>= 1.9.2)', - 'libxcomposite1 (>= 1:0.4.4-1)', - 'libxdamage1 (>= 1:1.1)', - 'libxext6', - 'libxfixes3', - 'libxkbcommon0 (>= 0.5.0)', - 'libxkbfile1 (>= 1:1.1.0)', - 'libxrandr2', - 'xdg-utils (>= 1.0.2)' - ], - 'arm64': [ - 'ca-certificates', - 'libasound2 (>= 1.0.17)', - 'libatk-bridge2.0-0 (>= 2.5.3)', - 'libatk1.0-0 (>= 2.11.90)', - 'libatspi2.0-0 (>= 2.9.90)', - 'libc6 (>= 2.17)', - 'libc6 (>= 2.25)', - 'libc6 (>= 2.28)', - 'libcairo2 (>= 1.6.0)', - 'libcurl3-gnutls | libcurl3-nss | libcurl4 | libcurl3', - 'libdbus-1-3 (>= 1.9.14)', - 'libexpat1 (>= 2.1~beta3)', - 'libgbm1 (>= 17.1.0~rc2)', - 'libglib2.0-0 (>= 2.37.3)', - 'libgtk-3-0 (>= 3.9.10)', - 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', - 'libnspr4 (>= 2:4.9-2~)', - 'libnss3 (>= 2:3.30)', - 'libnss3 (>= 3.26)', - 'libpango-1.0-0 (>= 1.14.0)', - 'libstdc++6 (>= 4.1.1)', - 'libstdc++6 (>= 5)', - 'libstdc++6 (>= 5.2)', - 'libstdc++6 (>= 6)', - 'libstdc++6 (>= 9)', - 'libudev1 (>= 183)', - 'libx11-6', - 'libx11-6 (>= 2:1.4.99.1)', - 'libxcb1 (>= 1.9.2)', - 'libxcomposite1 (>= 1:0.4.4-1)', - 'libxdamage1 (>= 1:1.1)', - 'libxext6', - 'libxfixes3', - 'libxkbcommon0 (>= 0.5.0)', - 'libxkbfile1 (>= 1:1.1.0)', - 'libxrandr2', - 'xdg-utils (>= 1.0.2)' - ] -}; -//# sourceMappingURL=dep-lists.js.map \ No newline at end of file diff --git a/build/linux/debian/dep-lists.ts b/build/linux/debian/dep-lists.ts index 5b7ccd51e0986..d00eb59e3a241 100644 --- a/build/linux/debian/dep-lists.ts +++ b/build/linux/debian/dep-lists.ts @@ -38,7 +38,7 @@ export const referenceGeneratedDepsByArch = { 'libdbus-1-3 (>= 1.9.14)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libglib2.0-0 (>= 2.37.3)', + 'libglib2.0-0 (>= 2.39.4)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', 'libnspr4 (>= 2:4.9-2~)', @@ -64,6 +64,7 @@ export const referenceGeneratedDepsByArch = { 'libatk-bridge2.0-0 (>= 2.5.3)', 'libatk1.0-0 (>= 2.11.90)', 'libatspi2.0-0 (>= 2.9.90)', + 'libc6 (>= 2.15)', 'libc6 (>= 2.16)', 'libc6 (>= 2.17)', 'libc6 (>= 2.25)', @@ -75,7 +76,7 @@ export const referenceGeneratedDepsByArch = { 'libdbus-1-3 (>= 1.9.14)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libglib2.0-0 (>= 2.37.3)', + 'libglib2.0-0 (>= 2.39.4)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', 'libnspr4 (>= 2:4.9-2~)', @@ -114,7 +115,7 @@ export const referenceGeneratedDepsByArch = { 'libdbus-1-3 (>= 1.9.14)', 'libexpat1 (>= 2.1~beta3)', 'libgbm1 (>= 17.1.0~rc2)', - 'libglib2.0-0 (>= 2.37.3)', + 'libglib2.0-0 (>= 2.39.4)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', 'libnspr4 (>= 2:4.9-2~)', diff --git a/build/linux/debian/install-sysroot.js b/build/linux/debian/install-sysroot.js deleted file mode 100644 index a94ca427a967b..0000000000000 --- a/build/linux/debian/install-sysroot.js +++ /dev/null @@ -1,227 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getVSCodeSysroot = getVSCodeSysroot; -exports.getChromiumSysroot = getChromiumSysroot; -const child_process_1 = require("child_process"); -const os_1 = require("os"); -const fs_1 = __importDefault(require("fs")); -const https_1 = __importDefault(require("https")); -const path_1 = __importDefault(require("path")); -const crypto_1 = require("crypto"); -// Based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/install-sysroot.py. -const URL_PREFIX = 'https://msftelectronbuild.z5.web.core.windows.net'; -const URL_PATH = 'sysroots/toolchain'; -const REPO_ROOT = path_1.default.dirname(path_1.default.dirname(path_1.default.dirname(__dirname))); -const ghApiHeaders = { - Accept: 'application/vnd.github.v3+json', - 'User-Agent': 'VSCode Build', -}; -if (process.env.GITHUB_TOKEN) { - ghApiHeaders.Authorization = 'Basic ' + Buffer.from(process.env.GITHUB_TOKEN).toString('base64'); -} -const ghDownloadHeaders = { - ...ghApiHeaders, - Accept: 'application/octet-stream', -}; -function getElectronVersion() { - const npmrc = fs_1.default.readFileSync(path_1.default.join(REPO_ROOT, '.npmrc'), 'utf8'); - const electronVersion = /^target="(.*)"$/m.exec(npmrc)[1]; - const msBuildId = /^ms_build_id="(.*)"$/m.exec(npmrc)[1]; - return { electronVersion, msBuildId }; -} -function getSha(filename) { - const hash = (0, crypto_1.createHash)('sha256'); - // Read file 1 MB at a time - const fd = fs_1.default.openSync(filename, 'r'); - const buffer = Buffer.alloc(1024 * 1024); - let position = 0; - let bytesRead = 0; - while ((bytesRead = fs_1.default.readSync(fd, buffer, 0, buffer.length, position)) === buffer.length) { - hash.update(buffer); - position += bytesRead; - } - hash.update(buffer.slice(0, bytesRead)); - return hash.digest('hex'); -} -function getVSCodeSysrootChecksum(expectedName) { - const checksums = fs_1.default.readFileSync(path_1.default.join(REPO_ROOT, 'build', 'checksums', 'vscode-sysroot.txt'), 'utf8'); - for (const line of checksums.split('\n')) { - const [checksum, name] = line.split(/\s+/); - if (name === expectedName) { - return checksum; - } - } - return undefined; -} -/* - * Do not use the fetch implementation from build/lib/fetch as it relies on vinyl streams - * and vinyl-fs breaks the symlinks in the compiler toolchain sysroot. We use the native - * tar implementation for that reason. - */ -async function fetchUrl(options, retries = 10, retryDelay = 1000) { - try { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 30 * 1000); - const version = '20250407-330404'; - try { - const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, { - headers: ghApiHeaders, - signal: controller.signal /* Typings issue with lib.dom.d.ts */ - }); - if (response.ok && (response.status >= 200 && response.status < 300)) { - console.log(`Fetch completed: Status ${response.status}.`); - const contents = Buffer.from(await response.arrayBuffer()); - const asset = JSON.parse(contents.toString()).assets.find((a) => a.name === options.assetName); - if (!asset) { - throw new Error(`Could not find asset in release of Microsoft/vscode-linux-build-agent @ ${version}`); - } - console.log(`Found asset ${options.assetName} @ ${asset.url}.`); - const assetResponse = await fetch(asset.url, { - headers: ghDownloadHeaders - }); - if (assetResponse.ok && (assetResponse.status >= 200 && assetResponse.status < 300)) { - const assetContents = Buffer.from(await assetResponse.arrayBuffer()); - console.log(`Fetched response body buffer: ${assetContents.byteLength} bytes`); - if (options.checksumSha256) { - const actualSHA256Checksum = (0, crypto_1.createHash)('sha256').update(assetContents).digest('hex'); - if (actualSHA256Checksum !== options.checksumSha256) { - throw new Error(`Checksum mismatch for ${asset.url} (expected ${options.checksumSha256}, actual ${actualSHA256Checksum}))`); - } - } - console.log(`Verified SHA256 checksums match for ${asset.url}`); - const tarCommand = `tar -xz -C ${options.dest}`; - (0, child_process_1.execSync)(tarCommand, { input: assetContents }); - console.log(`Fetch complete!`); - return; - } - throw new Error(`Request ${asset.url} failed with status code: ${assetResponse.status}`); - } - throw new Error(`Request https://api.github.com failed with status code: ${response.status}`); - } - finally { - clearTimeout(timeout); - } - } - catch (e) { - if (retries > 0) { - console.log(`Fetching failed: ${e}`); - await new Promise(resolve => setTimeout(resolve, retryDelay)); - return fetchUrl(options, retries - 1, retryDelay); - } - throw e; - } -} -async function getVSCodeSysroot(arch, isMusl = false) { - let expectedName; - let triple; - const prefix = process.env['VSCODE_SYSROOT_PREFIX'] ?? '-glibc-2.28-gcc-10.5.0'; - switch (arch) { - case 'amd64': - expectedName = `x86_64-linux-gnu${prefix}.tar.gz`; - triple = 'x86_64-linux-gnu'; - break; - case 'arm64': - if (isMusl) { - expectedName = 'aarch64-linux-musl-gcc-10.3.0.tar.gz'; - triple = 'aarch64-linux-musl'; - } - else { - expectedName = `aarch64-linux-gnu${prefix}.tar.gz`; - triple = 'aarch64-linux-gnu'; - } - break; - case 'armhf': - expectedName = `arm-rpi-linux-gnueabihf${prefix}.tar.gz`; - triple = 'arm-rpi-linux-gnueabihf'; - break; - } - console.log(`Fetching ${expectedName} for ${triple}`); - const checksumSha256 = getVSCodeSysrootChecksum(expectedName); - if (!checksumSha256) { - throw new Error(`Could not find checksum for ${expectedName}`); - } - const sysroot = process.env['VSCODE_SYSROOT_DIR'] ?? path_1.default.join((0, os_1.tmpdir)(), `vscode-${arch}-sysroot`); - const stamp = path_1.default.join(sysroot, '.stamp'); - let result = `${sysroot}/${triple}/${triple}/sysroot`; - if (isMusl) { - result = `${sysroot}/output/${triple}`; - } - if (fs_1.default.existsSync(stamp) && fs_1.default.readFileSync(stamp).toString() === expectedName) { - return result; - } - console.log(`Installing ${arch} root image: ${sysroot}`); - fs_1.default.rmSync(sysroot, { recursive: true, force: true }); - fs_1.default.mkdirSync(sysroot, { recursive: true }); - await fetchUrl({ - checksumSha256, - assetName: expectedName, - dest: sysroot - }); - fs_1.default.writeFileSync(stamp, expectedName); - return result; -} -async function getChromiumSysroot(arch) { - const sysrootJSONUrl = `https://raw.githubusercontent.com/electron/electron/v${getElectronVersion().electronVersion}/script/sysroots.json`; - const sysrootDictLocation = `${(0, os_1.tmpdir)()}/sysroots.json`; - const result = (0, child_process_1.spawnSync)('curl', [sysrootJSONUrl, '-o', sysrootDictLocation]); - if (result.status !== 0) { - throw new Error('Cannot retrieve sysroots.json. Stderr:\n' + result.stderr); - } - const sysrootInfo = require(sysrootDictLocation); - const sysrootArch = `bullseye_${arch}`; - const sysrootDict = sysrootInfo[sysrootArch]; - const tarballFilename = sysrootDict['Tarball']; - const tarballSha = sysrootDict['Sha256Sum']; - const sysroot = path_1.default.join((0, os_1.tmpdir)(), sysrootDict['SysrootDir']); - const url = [URL_PREFIX, URL_PATH, tarballSha].join('/'); - const stamp = path_1.default.join(sysroot, '.stamp'); - if (fs_1.default.existsSync(stamp) && fs_1.default.readFileSync(stamp).toString() === url) { - return sysroot; - } - console.log(`Installing Debian ${arch} root image: ${sysroot}`); - fs_1.default.rmSync(sysroot, { recursive: true, force: true }); - fs_1.default.mkdirSync(sysroot); - const tarball = path_1.default.join(sysroot, tarballFilename); - console.log(`Downloading ${url}`); - let downloadSuccess = false; - for (let i = 0; i < 3 && !downloadSuccess; i++) { - fs_1.default.writeFileSync(tarball, ''); - await new Promise((c) => { - https_1.default.get(url, (res) => { - res.on('data', (chunk) => { - fs_1.default.appendFileSync(tarball, chunk); - }); - res.on('end', () => { - downloadSuccess = true; - c(); - }); - }).on('error', (err) => { - console.error('Encountered an error during the download attempt: ' + err.message); - c(); - }); - }); - } - if (!downloadSuccess) { - fs_1.default.rmSync(tarball); - throw new Error('Failed to download ' + url); - } - const sha = getSha(tarball); - if (sha !== tarballSha) { - throw new Error(`Tarball sha1sum is wrong. Expected ${tarballSha}, actual ${sha}`); - } - const proc = (0, child_process_1.spawnSync)('tar', ['xf', tarball, '-C', sysroot]); - if (proc.status) { - throw new Error('Tarball extraction failed with code ' + proc.status); - } - fs_1.default.rmSync(tarball); - fs_1.default.writeFileSync(stamp, url); - return sysroot; -} -//# sourceMappingURL=install-sysroot.js.map \ No newline at end of file diff --git a/build/linux/debian/install-sysroot.ts b/build/linux/debian/install-sysroot.ts index 670fb68adcf97..2cab657c1b7e1 100644 --- a/build/linux/debian/install-sysroot.ts +++ b/build/linux/debian/install-sysroot.ts @@ -9,12 +9,12 @@ import fs from 'fs'; import https from 'https'; import path from 'path'; import { createHash } from 'crypto'; -import { DebianArchString } from './types'; +import type { DebianArchString } from './types.ts'; // Based on https://source.chromium.org/chromium/chromium/src/+/main:build/linux/sysroot_scripts/install-sysroot.py. const URL_PREFIX = 'https://msftelectronbuild.z5.web.core.windows.net'; const URL_PATH = 'sysroots/toolchain'; -const REPO_ROOT = path.dirname(path.dirname(path.dirname(__dirname))); +const REPO_ROOT = path.dirname(path.dirname(path.dirname(import.meta.dirname))); const ghApiHeaders: Record = { Accept: 'application/vnd.github.v3+json', @@ -82,7 +82,7 @@ async function fetchUrl(options: IFetchOptions, retries = 10, retryDelay = 1000) try { const response = await fetch(`https://api.github.com/repos/Microsoft/vscode-linux-build-agent/releases/tags/v${version}`, { headers: ghApiHeaders, - signal: controller.signal as any /* Typings issue with lib.dom.d.ts */ + signal: controller.signal }); if (response.ok && (response.status >= 200 && response.status < 300)) { console.log(`Fetch completed: Status ${response.status}.`); @@ -188,7 +188,7 @@ export async function getChromiumSysroot(arch: DebianArchString): Promise { - return !bundledDeps.some(bundledDep => dependency.startsWith(bundledDep)); - }).sort(); - const referenceGeneratedDeps = packageType === 'deb' ? - dep_lists_1.referenceGeneratedDepsByArch[arch] : - dep_lists_2.referenceGeneratedDepsByArch[arch]; - if (JSON.stringify(sortedDependencies) !== JSON.stringify(referenceGeneratedDeps)) { - const failMessage = 'The dependencies list has changed.' - + '\nOld:\n' + referenceGeneratedDeps.join('\n') - + '\nNew:\n' + sortedDependencies.join('\n'); - if (FAIL_BUILD_FOR_NEW_DEPENDENCIES) { - throw new Error(failMessage); - } - else { - console.warn(failMessage); - } - } - return sortedDependencies; -} -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/merge_package_deps.py. -function mergePackageDeps(inputDeps) { - const requires = new Set(); - for (const depSet of inputDeps) { - for (const dep of depSet) { - const trimmedDependency = dep.trim(); - if (trimmedDependency.length && !trimmedDependency.startsWith('#')) { - requires.add(trimmedDependency); - } - } - } - return requires; -} -//# sourceMappingURL=dependencies-generator.js.map \ No newline at end of file diff --git a/build/linux/dependencies-generator.ts b/build/linux/dependencies-generator.ts index 1aab03f903ac1..d80346365f828 100644 --- a/build/linux/dependencies-generator.ts +++ b/build/linux/dependencies-generator.ts @@ -2,19 +2,16 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -'use strict'; - import { spawnSync } from 'child_process'; import path from 'path'; -import { getChromiumSysroot, getVSCodeSysroot } from './debian/install-sysroot'; -import { generatePackageDeps as generatePackageDepsDebian } from './debian/calculate-deps'; -import { generatePackageDeps as generatePackageDepsRpm } from './rpm/calculate-deps'; -import { referenceGeneratedDepsByArch as debianGeneratedDeps } from './debian/dep-lists'; -import { referenceGeneratedDepsByArch as rpmGeneratedDeps } from './rpm/dep-lists'; -import { DebianArchString, isDebianArchString } from './debian/types'; -import { isRpmArchString, RpmArchString } from './rpm/types'; -import product = require('../../product.json'); +import { getChromiumSysroot, getVSCodeSysroot } from './debian/install-sysroot.ts'; +import { generatePackageDeps as generatePackageDepsDebian } from './debian/calculate-deps.ts'; +import { generatePackageDeps as generatePackageDepsRpm } from './rpm/calculate-deps.ts'; +import { referenceGeneratedDepsByArch as debianGeneratedDeps } from './debian/dep-lists.ts'; +import { referenceGeneratedDepsByArch as rpmGeneratedDeps } from './rpm/dep-lists.ts'; +import { type DebianArchString, isDebianArchString } from './debian/types.ts'; +import { isRpmArchString, type RpmArchString } from './rpm/types.ts'; +import product from '../../product.json' with { type: 'json' }; // A flag that can easily be toggled. // Make sure to compile the build directory after toggling the value. @@ -25,7 +22,7 @@ import product = require('../../product.json'); // are valid, are in dep-lists.ts const FAIL_BUILD_FOR_NEW_DEPENDENCIES: boolean = true; -// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/138.0.7204.100:chrome/installer/linux/BUILD.gn;l=64-80 +// Based on https://source.chromium.org/chromium/chromium/src/+/refs/tags/142.0.7444.235:chrome/installer/linux/BUILD.gn;l=64-80 // and the Linux Archive build // Shared library dependencies that we already bundle. const bundledDeps = [ diff --git a/build/linux/libcxx-fetcher.js b/build/linux/libcxx-fetcher.js deleted file mode 100644 index d6c998e5aea94..0000000000000 --- a/build/linux/libcxx-fetcher.js +++ /dev/null @@ -1,73 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.downloadLibcxxHeaders = downloadLibcxxHeaders; -exports.downloadLibcxxObjects = downloadLibcxxObjects; -// Can be removed once https://github.com/electron/electron-rebuild/pull/703 is available. -const fs_1 = __importDefault(require("fs")); -const path_1 = __importDefault(require("path")); -const debug_1 = __importDefault(require("debug")); -const extract_zip_1 = __importDefault(require("extract-zip")); -const get_1 = require("@electron/get"); -const root = path_1.default.dirname(path_1.default.dirname(__dirname)); -const d = (0, debug_1.default)('libcxx-fetcher'); -async function downloadLibcxxHeaders(outDir, electronVersion, lib_name) { - if (await fs_1.default.existsSync(path_1.default.resolve(outDir, 'include'))) { - return; - } - if (!await fs_1.default.existsSync(outDir)) { - await fs_1.default.mkdirSync(outDir, { recursive: true }); - } - d(`downloading ${lib_name}_headers`); - const headers = await (0, get_1.downloadArtifact)({ - version: electronVersion, - isGeneric: true, - artifactName: `${lib_name}_headers.zip`, - }); - d(`unpacking ${lib_name}_headers from ${headers}`); - await (0, extract_zip_1.default)(headers, { dir: outDir }); -} -async function downloadLibcxxObjects(outDir, electronVersion, targetArch = 'x64') { - if (await fs_1.default.existsSync(path_1.default.resolve(outDir, 'libc++.a'))) { - return; - } - if (!await fs_1.default.existsSync(outDir)) { - await fs_1.default.mkdirSync(outDir, { recursive: true }); - } - d(`downloading libcxx-objects-linux-${targetArch}`); - const objects = await (0, get_1.downloadArtifact)({ - version: electronVersion, - platform: 'linux', - artifactName: 'libcxx-objects', - arch: targetArch, - }); - d(`unpacking libcxx-objects from ${objects}`); - await (0, extract_zip_1.default)(objects, { dir: outDir }); -} -async function main() { - const libcxxObjectsDirPath = process.env['VSCODE_LIBCXX_OBJECTS_DIR']; - const libcxxHeadersDownloadDir = process.env['VSCODE_LIBCXX_HEADERS_DIR']; - const libcxxabiHeadersDownloadDir = process.env['VSCODE_LIBCXXABI_HEADERS_DIR']; - const arch = process.env['VSCODE_ARCH']; - const packageJSON = JSON.parse(fs_1.default.readFileSync(path_1.default.join(root, 'package.json'), 'utf8')); - const electronVersion = packageJSON.devDependencies.electron; - if (!libcxxObjectsDirPath || !libcxxHeadersDownloadDir || !libcxxabiHeadersDownloadDir) { - throw new Error('Required build env not set'); - } - await downloadLibcxxObjects(libcxxObjectsDirPath, electronVersion, arch); - await downloadLibcxxHeaders(libcxxHeadersDownloadDir, electronVersion, 'libcxx'); - await downloadLibcxxHeaders(libcxxabiHeadersDownloadDir, electronVersion, 'libcxxabi'); -} -if (require.main === module) { - main().catch(err => { - console.error(err); - process.exit(1); - }); -} -//# sourceMappingURL=libcxx-fetcher.js.map \ No newline at end of file diff --git a/build/linux/libcxx-fetcher.ts b/build/linux/libcxx-fetcher.ts index 6bdbd8a4f302b..981fbd3392e0c 100644 --- a/build/linux/libcxx-fetcher.ts +++ b/build/linux/libcxx-fetcher.ts @@ -11,7 +11,7 @@ import debug from 'debug'; import extract from 'extract-zip'; import { downloadArtifact } from '@electron/get'; -const root = path.dirname(path.dirname(__dirname)); +const root = path.dirname(path.dirname(import.meta.dirname)); const d = debug('libcxx-fetcher'); @@ -71,7 +71,7 @@ async function main(): Promise { await downloadLibcxxHeaders(libcxxabiHeadersDownloadDir, electronVersion, 'libcxxabi'); } -if (require.main === module) { +if (import.meta.main) { main().catch(err => { console.error(err); process.exit(1); diff --git a/build/linux/rpm/calculate-deps.js b/build/linux/rpm/calculate-deps.js deleted file mode 100644 index b19e26f18544e..0000000000000 --- a/build/linux/rpm/calculate-deps.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.generatePackageDeps = generatePackageDeps; -const child_process_1 = require("child_process"); -const fs_1 = require("fs"); -const dep_lists_1 = require("./dep-lists"); -function generatePackageDeps(files) { - const dependencies = files.map(file => calculatePackageDeps(file)); - const additionalDepsSet = new Set(dep_lists_1.additionalDeps); - dependencies.push(additionalDepsSet); - return dependencies; -} -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/calculate_package_deps.py. -function calculatePackageDeps(binaryPath) { - try { - if (!((0, fs_1.statSync)(binaryPath).mode & fs_1.constants.S_IXUSR)) { - throw new Error(`Binary ${binaryPath} needs to have an executable bit set.`); - } - } - catch (e) { - // The package might not exist. Don't re-throw the error here. - console.error('Tried to stat ' + binaryPath + ' but failed.'); - } - const findRequiresResult = (0, child_process_1.spawnSync)('/usr/lib/rpm/find-requires', { input: binaryPath + '\n' }); - if (findRequiresResult.status !== 0) { - throw new Error(`find-requires failed with exit code ${findRequiresResult.status}.\nstderr: ${findRequiresResult.stderr}`); - } - const requires = new Set(findRequiresResult.stdout.toString('utf-8').trimEnd().split('\n')); - return requires; -} -//# sourceMappingURL=calculate-deps.js.map \ No newline at end of file diff --git a/build/linux/rpm/calculate-deps.ts b/build/linux/rpm/calculate-deps.ts index 4be2200c01830..0a1f010759473 100644 --- a/build/linux/rpm/calculate-deps.ts +++ b/build/linux/rpm/calculate-deps.ts @@ -5,7 +5,7 @@ import { spawnSync } from 'child_process'; import { constants, statSync } from 'fs'; -import { additionalDeps } from './dep-lists'; +import { additionalDeps } from './dep-lists.ts'; export function generatePackageDeps(files: string[]): Set[] { const dependencies: Set[] = files.map(file => calculatePackageDeps(file)); diff --git a/build/linux/rpm/dep-lists.js b/build/linux/rpm/dep-lists.js deleted file mode 100644 index 2f742daf2f84d..0000000000000 --- a/build/linux/rpm/dep-lists.js +++ /dev/null @@ -1,321 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.referenceGeneratedDepsByArch = exports.additionalDeps = void 0; -// Based on https://source.chromium.org/chromium/chromium/src/+/main:chrome/installer/linux/rpm/additional_deps -// Additional dependencies not in the rpm find-requires output. -exports.additionalDeps = [ - 'ca-certificates', // Make sure users have SSL certificates. - 'libgtk-3.so.0()(64bit)', - 'libnss3.so(NSS_3.22)(64bit)', - 'libssl3.so(NSS_3.28)(64bit)', - 'rpmlib(FileDigests) <= 4.6.0-1', - 'libvulkan.so.1()(64bit)', - 'libcurl.so.4()(64bit)', - 'xdg-utils' // OS integration -]; -exports.referenceGeneratedDepsByArch = { - 'x86_64': [ - 'ca-certificates', - 'ld-linux-x86-64.so.2()(64bit)', - 'ld-linux-x86-64.so.2(GLIBC_2.2.5)(64bit)', - 'ld-linux-x86-64.so.2(GLIBC_2.3)(64bit)', - 'libX11.so.6()(64bit)', - 'libXcomposite.so.1()(64bit)', - 'libXdamage.so.1()(64bit)', - 'libXext.so.6()(64bit)', - 'libXfixes.so.3()(64bit)', - 'libXrandr.so.2()(64bit)', - 'libasound.so.2()(64bit)', - 'libasound.so.2(ALSA_0.9)(64bit)', - 'libasound.so.2(ALSA_0.9.0rc4)(64bit)', - 'libatk-1.0.so.0()(64bit)', - 'libatk-bridge-2.0.so.0()(64bit)', - 'libatspi.so.0()(64bit)', - 'libc.so.6()(64bit)', - 'libc.so.6(GLIBC_2.10)(64bit)', - 'libc.so.6(GLIBC_2.11)(64bit)', - 'libc.so.6(GLIBC_2.12)(64bit)', - 'libc.so.6(GLIBC_2.14)(64bit)', - 'libc.so.6(GLIBC_2.15)(64bit)', - 'libc.so.6(GLIBC_2.16)(64bit)', - 'libc.so.6(GLIBC_2.17)(64bit)', - 'libc.so.6(GLIBC_2.18)(64bit)', - 'libc.so.6(GLIBC_2.2.5)(64bit)', - 'libc.so.6(GLIBC_2.25)(64bit)', - 'libc.so.6(GLIBC_2.27)(64bit)', - 'libc.so.6(GLIBC_2.28)(64bit)', - 'libc.so.6(GLIBC_2.3)(64bit)', - 'libc.so.6(GLIBC_2.3.2)(64bit)', - 'libc.so.6(GLIBC_2.3.3)(64bit)', - 'libc.so.6(GLIBC_2.3.4)(64bit)', - 'libc.so.6(GLIBC_2.4)(64bit)', - 'libc.so.6(GLIBC_2.6)(64bit)', - 'libc.so.6(GLIBC_2.7)(64bit)', - 'libc.so.6(GLIBC_2.8)(64bit)', - 'libc.so.6(GLIBC_2.9)(64bit)', - 'libcairo.so.2()(64bit)', - 'libcurl.so.4()(64bit)', - 'libdbus-1.so.3()(64bit)', - 'libdbus-1.so.3(LIBDBUS_1_3)(64bit)', - 'libdl.so.2()(64bit)', - 'libdl.so.2(GLIBC_2.2.5)(64bit)', - 'libexpat.so.1()(64bit)', - 'libgbm.so.1()(64bit)', - 'libgcc_s.so.1()(64bit)', - 'libgcc_s.so.1(GCC_3.0)(64bit)', - 'libgcc_s.so.1(GCC_3.3)(64bit)', - 'libgcc_s.so.1(GCC_4.0.0)(64bit)', - 'libgcc_s.so.1(GCC_4.2.0)(64bit)', - 'libgio-2.0.so.0()(64bit)', - 'libglib-2.0.so.0()(64bit)', - 'libgobject-2.0.so.0()(64bit)', - 'libgtk-3.so.0()(64bit)', - 'libm.so.6()(64bit)', - 'libm.so.6(GLIBC_2.2.5)(64bit)', - 'libnspr4.so()(64bit)', - 'libnss3.so()(64bit)', - 'libnss3.so(NSS_3.11)(64bit)', - 'libnss3.so(NSS_3.12)(64bit)', - 'libnss3.so(NSS_3.12.1)(64bit)', - 'libnss3.so(NSS_3.2)(64bit)', - 'libnss3.so(NSS_3.22)(64bit)', - 'libnss3.so(NSS_3.3)(64bit)', - 'libnss3.so(NSS_3.30)(64bit)', - 'libnss3.so(NSS_3.4)(64bit)', - 'libnss3.so(NSS_3.5)(64bit)', - 'libnss3.so(NSS_3.6)(64bit)', - 'libnss3.so(NSS_3.9.2)(64bit)', - 'libnssutil3.so()(64bit)', - 'libnssutil3.so(NSSUTIL_3.12.3)(64bit)', - 'libpango-1.0.so.0()(64bit)', - 'libpthread.so.0()(64bit)', - 'libpthread.so.0(GLIBC_2.12)(64bit)', - 'libpthread.so.0(GLIBC_2.2.5)(64bit)', - 'libpthread.so.0(GLIBC_2.3.2)(64bit)', - 'libpthread.so.0(GLIBC_2.3.3)(64bit)', - 'libpthread.so.0(GLIBC_2.3.4)(64bit)', - 'librt.so.1()(64bit)', - 'librt.so.1(GLIBC_2.2.5)(64bit)', - 'libsmime3.so()(64bit)', - 'libsmime3.so(NSS_3.10)(64bit)', - 'libsmime3.so(NSS_3.2)(64bit)', - 'libssl3.so(NSS_3.28)(64bit)', - 'libudev.so.1()(64bit)', - 'libudev.so.1(LIBUDEV_183)(64bit)', - 'libutil.so.1()(64bit)', - 'libutil.so.1(GLIBC_2.2.5)(64bit)', - 'libxcb.so.1()(64bit)', - 'libxkbcommon.so.0()(64bit)', - 'libxkbcommon.so.0(V_0.5.0)(64bit)', - 'libxkbfile.so.1()(64bit)', - 'rpmlib(FileDigests) <= 4.6.0-1', - 'rtld(GNU_HASH)', - 'xdg-utils' - ], - 'armv7hl': [ - 'ca-certificates', - 'ld-linux-armhf.so.3', - 'ld-linux-armhf.so.3(GLIBC_2.4)', - 'libX11.so.6', - 'libXcomposite.so.1', - 'libXdamage.so.1', - 'libXext.so.6', - 'libXfixes.so.3', - 'libXrandr.so.2', - 'libasound.so.2', - 'libasound.so.2(ALSA_0.9)', - 'libasound.so.2(ALSA_0.9.0rc4)', - 'libatk-1.0.so.0', - 'libatk-bridge-2.0.so.0', - 'libatspi.so.0', - 'libc.so.6', - 'libc.so.6(GLIBC_2.10)', - 'libc.so.6(GLIBC_2.11)', - 'libc.so.6(GLIBC_2.12)', - 'libc.so.6(GLIBC_2.14)', - 'libc.so.6(GLIBC_2.15)', - 'libc.so.6(GLIBC_2.16)', - 'libc.so.6(GLIBC_2.17)', - 'libc.so.6(GLIBC_2.18)', - 'libc.so.6(GLIBC_2.25)', - 'libc.so.6(GLIBC_2.27)', - 'libc.so.6(GLIBC_2.28)', - 'libc.so.6(GLIBC_2.4)', - 'libc.so.6(GLIBC_2.6)', - 'libc.so.6(GLIBC_2.7)', - 'libc.so.6(GLIBC_2.8)', - 'libc.so.6(GLIBC_2.9)', - 'libcairo.so.2', - 'libcurl.so.4()(64bit)', - 'libdbus-1.so.3', - 'libdbus-1.so.3(LIBDBUS_1_3)', - 'libdl.so.2', - 'libdl.so.2(GLIBC_2.4)', - 'libexpat.so.1', - 'libgbm.so.1', - 'libgcc_s.so.1', - 'libgcc_s.so.1(GCC_3.0)', - 'libgcc_s.so.1(GCC_3.5)', - 'libgcc_s.so.1(GCC_4.3.0)', - 'libgio-2.0.so.0', - 'libglib-2.0.so.0', - 'libgobject-2.0.so.0', - 'libgtk-3.so.0', - 'libgtk-3.so.0()(64bit)', - 'libm.so.6', - 'libm.so.6(GLIBC_2.4)', - 'libnspr4.so', - 'libnss3.so', - 'libnss3.so(NSS_3.11)', - 'libnss3.so(NSS_3.12)', - 'libnss3.so(NSS_3.12.1)', - 'libnss3.so(NSS_3.2)', - 'libnss3.so(NSS_3.22)', - 'libnss3.so(NSS_3.22)(64bit)', - 'libnss3.so(NSS_3.3)', - 'libnss3.so(NSS_3.30)', - 'libnss3.so(NSS_3.4)', - 'libnss3.so(NSS_3.5)', - 'libnss3.so(NSS_3.6)', - 'libnss3.so(NSS_3.9.2)', - 'libnssutil3.so', - 'libnssutil3.so(NSSUTIL_3.12.3)', - 'libpango-1.0.so.0', - 'libpthread.so.0', - 'libpthread.so.0(GLIBC_2.12)', - 'libpthread.so.0(GLIBC_2.4)', - 'librt.so.1', - 'librt.so.1(GLIBC_2.4)', - 'libsmime3.so', - 'libsmime3.so(NSS_3.10)', - 'libsmime3.so(NSS_3.2)', - 'libssl3.so(NSS_3.28)(64bit)', - 'libstdc++.so.6', - 'libstdc++.so.6(CXXABI_1.3)', - 'libstdc++.so.6(CXXABI_1.3.5)', - 'libstdc++.so.6(CXXABI_1.3.8)', - 'libstdc++.so.6(CXXABI_1.3.9)', - 'libstdc++.so.6(CXXABI_ARM_1.3.3)', - 'libstdc++.so.6(GLIBCXX_3.4)', - 'libstdc++.so.6(GLIBCXX_3.4.11)', - 'libstdc++.so.6(GLIBCXX_3.4.14)', - 'libstdc++.so.6(GLIBCXX_3.4.15)', - 'libstdc++.so.6(GLIBCXX_3.4.18)', - 'libstdc++.so.6(GLIBCXX_3.4.19)', - 'libstdc++.so.6(GLIBCXX_3.4.20)', - 'libstdc++.so.6(GLIBCXX_3.4.21)', - 'libstdc++.so.6(GLIBCXX_3.4.22)', - 'libstdc++.so.6(GLIBCXX_3.4.26)', - 'libstdc++.so.6(GLIBCXX_3.4.5)', - 'libstdc++.so.6(GLIBCXX_3.4.9)', - 'libudev.so.1', - 'libudev.so.1(LIBUDEV_183)', - 'libutil.so.1', - 'libutil.so.1(GLIBC_2.4)', - 'libxcb.so.1', - 'libxkbcommon.so.0', - 'libxkbcommon.so.0(V_0.5.0)', - 'libxkbfile.so.1', - 'rpmlib(FileDigests) <= 4.6.0-1', - 'rtld(GNU_HASH)', - 'xdg-utils' - ], - 'aarch64': [ - 'ca-certificates', - 'ld-linux-aarch64.so.1()(64bit)', - 'ld-linux-aarch64.so.1(GLIBC_2.17)(64bit)', - 'libX11.so.6()(64bit)', - 'libXcomposite.so.1()(64bit)', - 'libXdamage.so.1()(64bit)', - 'libXext.so.6()(64bit)', - 'libXfixes.so.3()(64bit)', - 'libXrandr.so.2()(64bit)', - 'libasound.so.2()(64bit)', - 'libasound.so.2(ALSA_0.9)(64bit)', - 'libasound.so.2(ALSA_0.9.0rc4)(64bit)', - 'libatk-1.0.so.0()(64bit)', - 'libatk-bridge-2.0.so.0()(64bit)', - 'libatspi.so.0()(64bit)', - 'libc.so.6()(64bit)', - 'libc.so.6(GLIBC_2.17)(64bit)', - 'libc.so.6(GLIBC_2.18)(64bit)', - 'libc.so.6(GLIBC_2.25)(64bit)', - 'libc.so.6(GLIBC_2.27)(64bit)', - 'libc.so.6(GLIBC_2.28)(64bit)', - 'libcairo.so.2()(64bit)', - 'libcurl.so.4()(64bit)', - 'libdbus-1.so.3()(64bit)', - 'libdbus-1.so.3(LIBDBUS_1_3)(64bit)', - 'libdl.so.2()(64bit)', - 'libdl.so.2(GLIBC_2.17)(64bit)', - 'libexpat.so.1()(64bit)', - 'libgbm.so.1()(64bit)', - 'libgcc_s.so.1()(64bit)', - 'libgcc_s.so.1(GCC_3.0)(64bit)', - 'libgcc_s.so.1(GCC_3.3)(64bit)', - 'libgcc_s.so.1(GCC_4.0.0)(64bit)', - 'libgcc_s.so.1(GCC_4.2.0)(64bit)', - 'libgcc_s.so.1(GCC_4.5.0)(64bit)', - 'libgio-2.0.so.0()(64bit)', - 'libglib-2.0.so.0()(64bit)', - 'libgobject-2.0.so.0()(64bit)', - 'libgtk-3.so.0()(64bit)', - 'libm.so.6()(64bit)', - 'libm.so.6(GLIBC_2.17)(64bit)', - 'libnspr4.so()(64bit)', - 'libnss3.so()(64bit)', - 'libnss3.so(NSS_3.11)(64bit)', - 'libnss3.so(NSS_3.12)(64bit)', - 'libnss3.so(NSS_3.12.1)(64bit)', - 'libnss3.so(NSS_3.2)(64bit)', - 'libnss3.so(NSS_3.22)(64bit)', - 'libnss3.so(NSS_3.3)(64bit)', - 'libnss3.so(NSS_3.30)(64bit)', - 'libnss3.so(NSS_3.4)(64bit)', - 'libnss3.so(NSS_3.5)(64bit)', - 'libnss3.so(NSS_3.6)(64bit)', - 'libnss3.so(NSS_3.9.2)(64bit)', - 'libnssutil3.so()(64bit)', - 'libnssutil3.so(NSSUTIL_3.12.3)(64bit)', - 'libpango-1.0.so.0()(64bit)', - 'libpthread.so.0()(64bit)', - 'libpthread.so.0(GLIBC_2.17)(64bit)', - 'libsmime3.so()(64bit)', - 'libsmime3.so(NSS_3.10)(64bit)', - 'libsmime3.so(NSS_3.2)(64bit)', - 'libssl3.so(NSS_3.28)(64bit)', - 'libstdc++.so.6()(64bit)', - 'libstdc++.so.6(CXXABI_1.3)(64bit)', - 'libstdc++.so.6(CXXABI_1.3.5)(64bit)', - 'libstdc++.so.6(CXXABI_1.3.8)(64bit)', - 'libstdc++.so.6(CXXABI_1.3.9)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.11)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.14)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.15)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.18)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.19)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.20)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.21)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.22)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.26)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.5)(64bit)', - 'libstdc++.so.6(GLIBCXX_3.4.9)(64bit)', - 'libudev.so.1()(64bit)', - 'libudev.so.1(LIBUDEV_183)(64bit)', - 'libutil.so.1()(64bit)', - 'libutil.so.1(GLIBC_2.17)(64bit)', - 'libxcb.so.1()(64bit)', - 'libxkbcommon.so.0()(64bit)', - 'libxkbcommon.so.0(V_0.5.0)(64bit)', - 'libxkbfile.so.1()(64bit)', - 'rpmlib(FileDigests) <= 4.6.0-1', - 'rtld(GNU_HASH)', - 'xdg-utils' - ] -}; -//# sourceMappingURL=dep-lists.js.map \ No newline at end of file diff --git a/build/linux/rpm/dep-lists.ts b/build/linux/rpm/dep-lists.ts index 90b97bed301d1..783923f34d94f 100644 --- a/build/linux/rpm/dep-lists.ts +++ b/build/linux/rpm/dep-lists.ts @@ -256,7 +256,6 @@ export const referenceGeneratedDepsByArch = { 'libgcc_s.so.1()(64bit)', 'libgcc_s.so.1(GCC_3.0)(64bit)', 'libgcc_s.so.1(GCC_3.3)(64bit)', - 'libgcc_s.so.1(GCC_4.0.0)(64bit)', 'libgcc_s.so.1(GCC_4.2.0)(64bit)', 'libgcc_s.so.1(GCC_4.5.0)(64bit)', 'libgio-2.0.so.0()(64bit)', diff --git a/build/linux/rpm/types.js b/build/linux/rpm/types.js deleted file mode 100644 index a20b9c2fe025b..0000000000000 --- a/build/linux/rpm/types.js +++ /dev/null @@ -1,11 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.isRpmArchString = isRpmArchString; -function isRpmArchString(s) { - return ['x86_64', 'armv7hl', 'aarch64'].includes(s); -} -//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index 3b46cae5b77f6..b1f676796afd1 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -70,27 +70,27 @@ declare namespace monaco { dispose(): void; } -#include(vs/platform/markers/common/markers): MarkerTag, MarkerSeverity -#include(vs/base/common/cancellation): CancellationTokenSource, CancellationToken -#include(vs/base/common/uri): URI, UriComponents -#include(vs/base/common/keyCodes): KeyCode -#include(vs/editor/common/services/editorBaseApi): KeyMod -#include(vs/base/common/htmlContent): IMarkdownString, MarkdownStringTrustedOptions -#include(vs/base/browser/keyboardEvent): IKeyboardEvent -#include(vs/base/browser/mouseEvent): IMouseEvent -#include(vs/editor/common/editorCommon): IScrollEvent -#include(vs/editor/common/core/position): IPosition, Position -#include(vs/editor/common/core/range): IRange, Range -#include(vs/editor/common/core/selection): ISelection, Selection, SelectionDirection -#include(vs/editor/common/languages): Token +#include(vs/platform/markers/common/markers.js): MarkerTag, MarkerSeverity +#include(vs/base/common/cancellation.js): CancellationTokenSource, CancellationToken +#include(vs/base/common/uri.js): URI, UriComponents +#include(vs/base/common/keyCodes.js): KeyCode +#include(vs/editor/common/services/editorBaseApi.js): KeyMod +#include(vs/base/common/htmlContent.js): IMarkdownString, MarkdownStringTrustedOptions +#include(vs/base/browser/keyboardEvent.js): IKeyboardEvent +#include(vs/base/browser/mouseEvent.js): IMouseEvent +#include(vs/editor/common/editorCommon.js): IScrollEvent +#include(vs/editor/common/core/position.js): IPosition, Position +#include(vs/editor/common/core/range.js): IRange, Range +#include(vs/editor/common/core/selection.js): ISelection, Selection, SelectionDirection +#include(vs/editor/common/languages.js): Token } declare namespace monaco.editor { -#includeAll(vs/editor/standalone/browser/standaloneEditor;languages.Token=>Token): -#include(vs/editor/standalone/common/standaloneTheme): BuiltinTheme, IStandaloneThemeData, IColors -#include(vs/editor/common/languages/supports/tokenization): ITokenThemeRule -#include(vs/editor/standalone/browser/standaloneWebWorker): MonacoWebWorker, IInternalWebWorkerOptions -#include(vs/editor/standalone/browser/standaloneCodeEditor): IActionDescriptor, IGlobalEditorOptions, IStandaloneEditorConstructionOptions, IStandaloneDiffEditorConstructionOptions, IStandaloneCodeEditor, IStandaloneDiffEditor +#includeAll(vs/editor/standalone/browser/standaloneEditor.js;languages.Token=>Token): +#include(vs/editor/standalone/common/standaloneTheme.js): BuiltinTheme, IStandaloneThemeData, IColors +#include(vs/editor/common/languages/supports/tokenization.js): ITokenThemeRule +#include(vs/editor/standalone/browser/standaloneWebWorker.js): MonacoWebWorker, IInternalWebWorkerOptions +#include(vs/editor/standalone/browser/standaloneCodeEditor.js): IActionDescriptor, IGlobalEditorOptions, IStandaloneEditorConstructionOptions, IStandaloneDiffEditorConstructionOptions, IStandaloneCodeEditor, IStandaloneDiffEditor export interface ICommandHandler { (...args: any[]): void; } @@ -101,26 +101,27 @@ export interface ILocalizedString { export interface ICommandMetadata { readonly description: ILocalizedString | string; } -#include(vs/platform/contextkey/common/contextkey): IContextKey, ContextKeyValue -#include(vs/editor/standalone/browser/standaloneServices): IEditorOverrideServices -#include(vs/platform/markers/common/markers): IMarker, IMarkerData, IRelatedInformation -#include(vs/editor/standalone/browser/colorizer): IColorizerOptions, IColorizerElementOptions -#include(vs/base/common/scrollable): ScrollbarVisibility -#include(vs/base/common/themables): ThemeColor, ThemeIcon -#include(vs/editor/common/core/editOperation): ISingleEditOperation -#include(vs/editor/common/core/wordHelper): IWordAtPosition -#includeAll(vs/editor/common/model): IScrollEvent -#include(vs/editor/common/diff/legacyLinesDiffComputer): IChange, ICharChange, ILineChange -#include(vs/editor/common/core/2d/dimension): IDimension -#includeAll(vs/editor/common/editorCommon): IScrollEvent -#includeAll(vs/editor/common/textModelEvents): -#includeAll(vs/editor/common/cursorEvents): -#include(vs/platform/accessibility/common/accessibility): AccessibilitySupport -#includeAll(vs/editor/common/config/editorOptions): -#include(vs/editor/browser/config/editorConfiguration): IEditorConstructionOptions -#includeAll(vs/editor/browser/editorBrowser;editorCommon.=>): -#include(vs/editor/common/config/fontInfo): FontInfo, BareFontInfo -#include(vs/editor/common/config/editorZoom): EditorZoom, IEditorZoom +#include(vs/platform/contextkey/common/contextkey.js): IContextKey, ContextKeyValue +#include(vs/editor/standalone/browser/standaloneServices.js): IEditorOverrideServices +#include(vs/platform/markers/common/markers.js): IMarker, IMarkerData, IRelatedInformation +#include(vs/editor/standalone/browser/colorizer.js): IColorizerOptions, IColorizerElementOptions +#include(vs/base/common/scrollable.js): ScrollbarVisibility +#include(vs/base/common/themables.js): ThemeColor, ThemeIcon +#include(vs/editor/common/core/editOperation.js): ISingleEditOperation +#include(vs/editor/common/core/wordHelper.js): IWordAtPosition +#includeAll(vs/editor/common/model.js): IScrollEvent +#include(vs/editor/common/diff/legacyLinesDiffComputer.js): IChange, ICharChange, ILineChange +#include(vs/editor/common/core/2d/dimension.js): IDimension +#includeAll(vs/editor/common/editorCommon.js): IScrollEvent +#includeAll(vs/editor/common/textModelEvents.js): +#include(vs/editor/common/model/mirrorTextModel.js): IModelContentChange +#includeAll(vs/editor/common/cursorEvents.js): +#include(vs/platform/accessibility/common/accessibility.js): AccessibilitySupport +#includeAll(vs/editor/common/config/editorOptions.js): +#include(vs/editor/browser/config/editorConfiguration.js): IEditorConstructionOptions +#includeAll(vs/editor/browser/editorBrowser.js;editorCommon.=>): +#include(vs/editor/common/config/fontInfo.js): FontInfo, BareFontInfo +#include(vs/editor/common/config/editorZoom.js): EditorZoom, IEditorZoom //compatibility: export type IReadOnlyModel = ITextModel; @@ -129,20 +130,21 @@ export type IModel = ITextModel; declare namespace monaco.languages { -#include(vs/base/common/glob): IRelativePattern -#include(vs/editor/common/languageSelector): LanguageSelector, LanguageFilter -#includeAll(vs/editor/standalone/browser/standaloneLanguages;languages.=>;editorCommon.=>editor.;model.=>editor.;IMarkerData=>editor.IMarkerData): -#includeAll(vs/editor/common/languages/languageConfiguration): -#includeAll(vs/editor/common/languages;IMarkerData=>editor.IMarkerData;ISingleEditOperation=>editor.ISingleEditOperation;model.=>editor.;ThemeIcon=>editor.ThemeIcon): Token -#include(vs/editor/common/languages/language): ILanguageExtensionPoint -#includeAll(vs/editor/standalone/common/monarch/monarchTypes): +#include(vs/editor/common/textModelEditSource.js): EditDeltaInfo +#include(vs/base/common/glob.js): IRelativePattern +#include(vs/editor/common/languageSelector.js): LanguageSelector, LanguageFilter +#includeAll(vs/editor/standalone/browser/standaloneLanguages.js;languages.=>;editorCommon.=>editor.;model.=>editor.;IMarkerData=>editor.IMarkerData): +#includeAll(vs/editor/common/languages/languageConfiguration.js): +#includeAll(vs/editor/common/languages.js;IMarkerData=>editor.IMarkerData;ISingleEditOperation=>editor.ISingleEditOperation;model.=>editor.;ThemeIcon=>editor.ThemeIcon): Token +#include(vs/editor/common/languages/language.js): ILanguageExtensionPoint +#includeAll(vs/editor/standalone/common/monarch/monarchTypes.js): } declare namespace monaco.worker { -#include(vs/editor/common/model/mirrorTextModel): IMirrorTextModel -#includeAll(vs/editor/common/services/editorWebWorker;): +#include(vs/editor/common/model/mirrorTextModel.js): IMirrorTextModel +#includeAll(vs/editor/common/services/editorWebWorker.js;): } diff --git a/build/monaco/monaco.usage.recipe b/build/monaco/monaco.usage.recipe index 9e96a68568a6b..b0526a4506e03 100644 --- a/build/monaco/monaco.usage.recipe +++ b/build/monaco/monaco.usage.recipe @@ -1,12 +1,12 @@ // This file is adding references to various symbols which should not be removed via tree shaking -import { IObservable } from './vs/base/common/observable'; +import { IObservable } from './vs/base/common/observable.js'; -import { ServiceIdentifier } from './vs/platform/instantiation/common/instantiation'; -import { start } from './vs/editor/editor.worker.start'; -import { SyncDescriptor0 } from './vs/platform/instantiation/common/descriptors'; -import * as editorAPI from './vs/editor/editor.api'; +import { ServiceIdentifier } from './vs/platform/instantiation/common/instantiation.js'; +import { start } from './vs/editor/editor.worker.start.js'; +import { SyncDescriptor0 } from './vs/platform/instantiation/common/descriptors.js'; +import * as editorAPI from './vs/editor/editor.api.js'; (function () { var a: any; diff --git a/build/npm/dirs.js b/build/npm/dirs.js deleted file mode 100644 index 9f653c7247948..0000000000000 --- a/build/npm/dirs.js +++ /dev/null @@ -1,65 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const fs = require('fs'); - -// Complete list of directories where npm should be executed to install node modules -const dirs = [ - '', - 'build', - 'extensions', - 'extensions/configuration-editing', - 'extensions/css-language-features', - 'extensions/css-language-features/server', - 'extensions/debug-auto-launch', - 'extensions/debug-server-ready', - 'extensions/emmet', - 'extensions/extension-editing', - 'extensions/git', - 'extensions/git-base', - 'extensions/github', - 'extensions/github-authentication', - 'extensions/grunt', - 'extensions/gulp', - 'extensions/html-language-features', - 'extensions/html-language-features/server', - 'extensions/ipynb', - 'extensions/jake', - 'extensions/json-language-features', - 'extensions/json-language-features/server', - 'extensions/markdown-language-features', - 'extensions/markdown-math', - 'extensions/media-preview', - 'extensions/merge-conflict', - 'extensions/microsoft-authentication', - 'extensions/notebook-renderers', - 'extensions/npm', - 'extensions/php-language-features', - 'extensions/references-view', - 'extensions/search-result', - 'extensions/simple-browser', - 'extensions/tunnel-forwarding', - 'extensions/typescript-language-features', - 'extensions/vscode-api-tests', - 'extensions/vscode-colorize-tests', - 'extensions/vscode-colorize-perf-tests', - 'extensions/vscode-test-resolver', - 'remote', - 'remote/web', - 'test/automation', - 'test/integration/browser', - 'test/monaco', - 'test/smoke', - '.vscode/extensions/vscode-selfhost-import-aid', - '.vscode/extensions/vscode-selfhost-test-provider', -]; - -if (fs.existsSync(`${__dirname}/../../.build/distro/npm`)) { - dirs.push('.build/distro/npm'); - dirs.push('.build/distro/npm/remote'); - dirs.push('.build/distro/npm/remote/web'); -} - -exports.dirs = dirs; diff --git a/build/npm/dirs.ts b/build/npm/dirs.ts new file mode 100644 index 0000000000000..48d76e2731a6e --- /dev/null +++ b/build/npm/dirs.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { existsSync } from 'fs'; + +/** + * Complete list of directories where npm should be executed to install node modules + */ +export const dirs = [ + '', + 'build', + 'build/vite', + 'extensions', + 'extensions/configuration-editing', + 'extensions/css-language-features', + 'extensions/css-language-features/server', + 'extensions/debug-auto-launch', + 'extensions/debug-server-ready', + 'extensions/emmet', + 'extensions/extension-editing', + 'extensions/git', + 'extensions/git-base', + 'extensions/github', + 'extensions/github-authentication', + 'extensions/grunt', + 'extensions/gulp', + 'extensions/html-language-features', + 'extensions/html-language-features/server', + 'extensions/ipynb', + 'extensions/jake', + 'extensions/json-language-features', + 'extensions/json-language-features/server', + 'extensions/markdown-language-features', + 'extensions/markdown-math', + 'extensions/media-preview', + 'extensions/merge-conflict', + 'extensions/mermaid-chat-features', + 'extensions/microsoft-authentication', + 'extensions/notebook-renderers', + 'extensions/npm', + 'extensions/php-language-features', + 'extensions/references-view', + 'extensions/search-result', + 'extensions/simple-browser', + 'extensions/tunnel-forwarding', + 'extensions/terminal-suggest', + 'extensions/typescript-language-features', + 'extensions/vscode-api-tests', + 'extensions/vscode-colorize-tests', + 'extensions/vscode-colorize-perf-tests', + 'extensions/vscode-test-resolver', + 'remote', + 'remote/web', + 'test/automation', + 'test/integration/browser', + 'test/monaco', + 'test/smoke', + 'test/mcp', + '.vscode/extensions/vscode-selfhost-import-aid', + '.vscode/extensions/vscode-selfhost-test-provider', +]; + +if (existsSync(`${import.meta.dirname}/../../.build/distro/npm`)) { + dirs.push('.build/distro/npm'); + dirs.push('.build/distro/npm/remote'); + dirs.push('.build/distro/npm/remote/web'); +} diff --git a/build/npm/gyp/custom-headers/v8-source-location.patch b/build/npm/gyp/custom-headers/v8-source-location.patch new file mode 100644 index 0000000000000..545eb9a118bb2 --- /dev/null +++ b/build/npm/gyp/custom-headers/v8-source-location.patch @@ -0,0 +1,94 @@ +--- v8-source-location.h 2025-10-28 05:57:35 ++++ v8-source-location.h 2025-11-07 03:10:02 +@@ -6,12 +6,21 @@ + #define INCLUDE_SOURCE_LOCATION_H_ + + #include +-#include + #include + + #include "v8config.h" // NOLINT(build/include_directory) + ++#if defined(__has_builtin) ++#define V8_SUPPORTS_SOURCE_LOCATION \ ++ (__has_builtin(__builtin_FUNCTION) && __has_builtin(__builtin_FILE) && \ ++ __has_builtin(__builtin_LINE)) // NOLINT ++#elif defined(V8_CC_GNU) && __GNUC__ >= 7 + #define V8_SUPPORTS_SOURCE_LOCATION 1 ++#elif defined(V8_CC_INTEL) && __ICC >= 1800 ++#define V8_SUPPORTS_SOURCE_LOCATION 1 ++#else ++#define V8_SUPPORTS_SOURCE_LOCATION 0 ++#endif + + namespace v8 { + +@@ -25,10 +34,15 @@ + * Construct source location information corresponding to the location of the + * call site. + */ ++#if V8_SUPPORTS_SOURCE_LOCATION + static constexpr SourceLocation Current( +- const std::source_location& loc = std::source_location::current()) { +- return SourceLocation(loc); ++ const char* function = __builtin_FUNCTION(), ++ const char* file = __builtin_FILE(), size_t line = __builtin_LINE()) { ++ return SourceLocation(function, file, line); + } ++#else ++ static constexpr SourceLocation Current() { return SourceLocation(); } ++#endif // V8_SUPPORTS_SOURCE_LOCATION + #ifdef DEBUG + static constexpr SourceLocation CurrentIfDebug( + const std::source_location& loc = std::source_location::current()) { +@@ -49,21 +63,21 @@ + * + * \returns the function name as cstring. + */ +- constexpr const char* Function() const { return loc_.function_name(); } ++ constexpr const char* Function() const { return function_; } + + /** + * Returns the name of the current source file represented by this object. + * + * \returns the file name as cstring. + */ +- constexpr const char* FileName() const { return loc_.file_name(); } ++ constexpr const char* FileName() const { return file_; } + + /** + * Returns the line number represented by this object. + * + * \returns the line number. + */ +- constexpr size_t Line() const { return loc_.line(); } ++ constexpr size_t Line() const { return line_; } + + /** + * Returns a human-readable string representing this object. +@@ -71,18 +85,19 @@ + * \returns a human-readable string representing source location information. + */ + std::string ToString() const { +- if (loc_.line() == 0) { ++ if (!file_) { + return {}; + } +- return std::string(loc_.function_name()) + "@" + loc_.file_name() + ":" + +- std::to_string(loc_.line()); ++ return std::string(function_) + "@" + file_ + ":" + std::to_string(line_); + } + + private: +- constexpr explicit SourceLocation(const std::source_location& loc) +- : loc_(loc) {} ++ constexpr SourceLocation(const char* function, const char* file, size_t line) ++ : function_(function), file_(file), line_(line) {} + +- std::source_location loc_; ++ const char* function_ = nullptr; ++ const char* file_ = nullptr; ++ size_t line_ = 0u; + }; + + } // namespace v8 diff --git a/build/npm/gyp/package-lock.json b/build/npm/gyp/package-lock.json index 08b6ae29b01c4..19a8610c33919 100644 --- a/build/npm/gyp/package-lock.json +++ b/build/npm/gyp/package-lock.json @@ -138,9 +138,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -352,9 +352,9 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, "license": "ISC", "dependencies": { diff --git a/build/npm/jsconfig.json b/build/npm/jsconfig.json deleted file mode 100644 index fa767b17d0f07..0000000000000 --- a/build/npm/jsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "es2024", - "lib": [ - "ES2024" - ], - "module": "node16", - "checkJs": true, - "noEmit": true - } -} diff --git a/build/npm/mixin-telemetry-docs.ts b/build/npm/mixin-telemetry-docs.ts new file mode 100644 index 0000000000000..be33793431a92 --- /dev/null +++ b/build/npm/mixin-telemetry-docs.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { execSync } from 'child_process'; +import { join, resolve } from 'path'; +import { existsSync, rmSync } from 'fs'; + +const rootPath = resolve(import.meta.dirname, '..', '..'); +const telemetryDocsPath = join(rootPath, 'vscode-telemetry-docs'); +const repoUrl = 'https://github.com/microsoft/vscode-telemetry-docs'; + +console.log('Cloning vscode-telemetry-docs repository...'); + +// Remove existing directory if it exists +if (existsSync(telemetryDocsPath)) { + console.log('Removing existing vscode-telemetry-docs directory...'); + rmSync(telemetryDocsPath, { recursive: true, force: true }); +} + +try { + // Clone the repository (shallow clone of main branch only) + console.log(`Cloning ${repoUrl} to ${telemetryDocsPath}...`); + execSync(`git clone --depth 1 --branch main --single-branch ${repoUrl} vscode-telemetry-docs`, { + cwd: rootPath, + stdio: 'inherit' + }); + + console.log('Successfully cloned vscode-telemetry-docs repository.'); +} catch (error) { + console.error('Failed to clone vscode-telemetry-docs repository:', (error as Error).message); + process.exit(1); +} diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js deleted file mode 100644 index 1033e4ecf68a6..0000000000000 --- a/build/npm/postinstall.js +++ /dev/null @@ -1,180 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const fs = require('fs'); -const path = require('path'); -const os = require('os'); -const cp = require('child_process'); -const { dirs } = require('./dirs'); -const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; -const root = path.dirname(path.dirname(__dirname)); - -function log(dir, message) { - if (process.stdout.isTTY) { - console.log(`\x1b[34m[${dir}]\x1b[0m`, message); - } else { - console.log(`[${dir}]`, message); - } -} - -function run(command, args, opts) { - log(opts.cwd || '.', '$ ' + command + ' ' + args.join(' ')); - - const result = cp.spawnSync(command, args, opts); - - if (result.error) { - console.error(`ERR Failed to spawn process: ${result.error}`); - process.exit(1); - } else if (result.status !== 0) { - console.error(`ERR Process exited with code: ${result.status}`); - process.exit(result.status); - } -} - -/** - * @param {string} dir - * @param {*} [opts] - */ -function npmInstall(dir, opts) { - opts = { - env: { ...process.env }, - ...(opts ?? {}), - cwd: dir, - stdio: 'inherit', - shell: true - }; - - const command = process.env['npm_command'] || 'install'; - - if (process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'] && /^(.build\/distro\/npm\/)?remote$/.test(dir)) { - const userinfo = os.userInfo(); - log(dir, `Installing dependencies inside container ${process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME']}...`); - - opts.cwd = root; - if (process.env['npm_config_arch'] === 'arm64') { - run('sudo', ['docker', 'run', '--rm', '--privileged', 'multiarch/qemu-user-static', '--reset', '-p', 'yes'], opts); - } - run('sudo', ['docker', 'run', '-e', 'GITHUB_TOKEN', '-v', `${process.env['VSCODE_HOST_MOUNT']}:/root/vscode`, '-v', `${process.env['VSCODE_HOST_MOUNT']}/.build/.netrc:/root/.netrc`, '-w', path.resolve('/root/vscode', dir), process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'sh', '-c', `\"chown -R root:root ${path.resolve('/root/vscode', dir)} && npm i -g node-gyp-build && npm ci\"`], opts); - run('sudo', ['chown', '-R', `${userinfo.uid}:${userinfo.gid}`, `${path.resolve(root, dir)}`], opts); - } else { - log(dir, 'Installing dependencies...'); - run(npm, command.split(' '), opts); - } - removeParcelWatcherPrebuild(dir); -} - -function setNpmrcConfig(dir, env) { - const npmrcPath = path.join(root, dir, '.npmrc'); - const lines = fs.readFileSync(npmrcPath, 'utf8').split('\n'); - - for (const line of lines) { - const trimmedLine = line.trim(); - if (trimmedLine && !trimmedLine.startsWith('#')) { - const [key, value] = trimmedLine.split('='); - env[`npm_config_${key}`] = value.replace(/^"(.*)"$/, '$1'); - } - } - - // Use our bundled node-gyp version - env['npm_config_node_gyp'] = - process.platform === 'win32' - ? path.join(__dirname, 'gyp', 'node_modules', '.bin', 'node-gyp.cmd') - : path.join(__dirname, 'gyp', 'node_modules', '.bin', 'node-gyp'); - - // Force node-gyp to use process.config on macOS - // which defines clang variable as expected. Otherwise we - // run into compilation errors due to incorrect compiler - // configuration. - // NOTE: This means the process.config should contain - // the correct clang variable. So keep the version check - // in preinstall sync with this logic. - // Change was first introduced in https://github.com/nodejs/node/commit/6e0a2bb54c5bbeff0e9e33e1a0c683ed980a8a0f - if ((dir === 'remote' || dir === 'build') && process.platform === 'darwin') { - env['npm_config_force_process_config'] = 'true'; - } else { - delete env['npm_config_force_process_config']; - } - - if (dir === 'build') { - env['npm_config_target'] = process.versions.node; - env['npm_config_arch'] = process.arch; - } -} - -function removeParcelWatcherPrebuild(dir) { - const parcelModuleFolder = path.join(root, dir, 'node_modules', '@parcel'); - if (!fs.existsSync(parcelModuleFolder)) { - return; - } - - const parcelModules = fs.readdirSync(parcelModuleFolder); - for (const moduleName of parcelModules) { - if (moduleName.startsWith('watcher-')) { - const modulePath = path.join(parcelModuleFolder, moduleName); - fs.rmSync(modulePath, { recursive: true, force: true }); - log(dir, `Removed @parcel/watcher prebuilt module ${modulePath}`); - } - } -} - -for (let dir of dirs) { - - if (dir === '') { - removeParcelWatcherPrebuild(dir); - continue; // already executed in root - } - - let opts; - - if (dir === 'build') { - opts = { - env: { - ...process.env - }, - } - if (process.env['CC']) { opts.env['CC'] = 'gcc'; } - if (process.env['CXX']) { opts.env['CXX'] = 'g++'; } - if (process.env['CXXFLAGS']) { opts.env['CXXFLAGS'] = ''; } - if (process.env['LDFLAGS']) { opts.env['LDFLAGS'] = ''; } - - setNpmrcConfig('build', opts.env); - npmInstall('build', opts); - continue; - } - - if (/^(.build\/distro\/npm\/)?remote$/.test(dir)) { - // node modules used by vscode server - opts = { - env: { - ...process.env - }, - } - if (process.env['VSCODE_REMOTE_CC']) { - opts.env['CC'] = process.env['VSCODE_REMOTE_CC']; - } else { - delete opts.env['CC']; - } - if (process.env['VSCODE_REMOTE_CXX']) { - opts.env['CXX'] = process.env['VSCODE_REMOTE_CXX']; - } else { - delete opts.env['CXX']; - } - if (process.env['CXXFLAGS']) { delete opts.env['CXXFLAGS']; } - if (process.env['CFLAGS']) { delete opts.env['CFLAGS']; } - if (process.env['LDFLAGS']) { delete opts.env['LDFLAGS']; } - if (process.env['VSCODE_REMOTE_CXXFLAGS']) { opts.env['CXXFLAGS'] = process.env['VSCODE_REMOTE_CXXFLAGS']; } - if (process.env['VSCODE_REMOTE_LDFLAGS']) { opts.env['LDFLAGS'] = process.env['VSCODE_REMOTE_LDFLAGS']; } - if (process.env['VSCODE_REMOTE_NODE_GYP']) { opts.env['npm_config_node_gyp'] = process.env['VSCODE_REMOTE_NODE_GYP']; } - - setNpmrcConfig('remote', opts.env); - npmInstall(dir, opts); - continue; - } - - npmInstall(dir, opts); -} - -cp.execSync('git config pull.rebase merges'); -cp.execSync('git config blame.ignoreRevsFile .git-blame-ignore-revs'); diff --git a/build/npm/postinstall.ts b/build/npm/postinstall.ts new file mode 100644 index 0000000000000..3e260853a5350 --- /dev/null +++ b/build/npm/postinstall.ts @@ -0,0 +1,186 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import path from 'path'; +import * as os from 'os'; +import * as child_process from 'child_process'; +import { dirs } from './dirs.ts'; + +const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; +const root = path.dirname(path.dirname(import.meta.dirname)); + +function log(dir: string, message: string) { + if (process.stdout.isTTY) { + console.log(`\x1b[34m[${dir}]\x1b[0m`, message); + } else { + console.log(`[${dir}]`, message); + } +} + +function run(command: string, args: string[], opts: child_process.SpawnSyncOptions) { + log(opts.cwd as string || '.', '$ ' + command + ' ' + args.join(' ')); + + const result = child_process.spawnSync(command, args, opts); + + if (result.error) { + console.error(`ERR Failed to spawn process: ${result.error}`); + process.exit(1); + } else if (result.status !== 0) { + console.error(`ERR Process exited with code: ${result.status}`); + process.exit(result.status); + } +} + +function npmInstall(dir: string, opts?: child_process.SpawnSyncOptions) { + opts = { + env: { ...process.env }, + ...(opts ?? {}), + cwd: dir, + stdio: 'inherit', + shell: true + }; + + const command = process.env['npm_command'] || 'install'; + + if (process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'] && /^(.build\/distro\/npm\/)?remote$/.test(dir)) { + const userinfo = os.userInfo(); + log(dir, `Installing dependencies inside container ${process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME']}...`); + + opts.cwd = root; + if (process.env['npm_config_arch'] === 'arm64') { + run('sudo', ['docker', 'run', '--rm', '--privileged', 'multiarch/qemu-user-static', '--reset', '-p', 'yes'], opts); + } + run('sudo', [ + 'docker', 'run', + '-e', 'GITHUB_TOKEN', + '-v', `${process.env['VSCODE_HOST_MOUNT']}:/root/vscode`, + '-v', `${process.env['VSCODE_HOST_MOUNT']}/.build/.netrc:/root/.netrc`, + '-v', `${process.env['VSCODE_NPMRC_PATH']}:/root/.npmrc`, + '-w', path.resolve('/root/vscode', dir), + process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], + 'sh', '-c', `\"chown -R root:root ${path.resolve('/root/vscode', dir)} && export PATH="/root/vscode/.build/nodejs-musl/usr/local/bin:$PATH" && npm i -g node-gyp-build && npm ci\"` + ], opts); + run('sudo', ['chown', '-R', `${userinfo.uid}:${userinfo.gid}`, `${path.resolve(root, dir)}`], opts); + } else { + log(dir, 'Installing dependencies...'); + run(npm, command.split(' '), opts); + } + removeParcelWatcherPrebuild(dir); +} + +function setNpmrcConfig(dir: string, env: NodeJS.ProcessEnv) { + const npmrcPath = path.join(root, dir, '.npmrc'); + const lines = fs.readFileSync(npmrcPath, 'utf8').split('\n'); + + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine && !trimmedLine.startsWith('#')) { + const [key, value] = trimmedLine.split('='); + env[`npm_config_${key}`] = value.replace(/^"(.*)"$/, '$1'); + } + } + + // Use our bundled node-gyp version + env['npm_config_node_gyp'] = + process.platform === 'win32' + ? path.join(import.meta.dirname, 'gyp', 'node_modules', '.bin', 'node-gyp.cmd') + : path.join(import.meta.dirname, 'gyp', 'node_modules', '.bin', 'node-gyp'); + + // Force node-gyp to use process.config on macOS + // which defines clang variable as expected. Otherwise we + // run into compilation errors due to incorrect compiler + // configuration. + // NOTE: This means the process.config should contain + // the correct clang variable. So keep the version check + // in preinstall sync with this logic. + // Change was first introduced in https://github.com/nodejs/node/commit/6e0a2bb54c5bbeff0e9e33e1a0c683ed980a8a0f + if ((dir === 'remote' || dir === 'build') && process.platform === 'darwin') { + env['npm_config_force_process_config'] = 'true'; + } else { + delete env['npm_config_force_process_config']; + } + + if (dir === 'build') { + env['npm_config_target'] = process.versions.node; + env['npm_config_arch'] = process.arch; + } +} + +function removeParcelWatcherPrebuild(dir: string) { + const parcelModuleFolder = path.join(root, dir, 'node_modules', '@vscode'); + if (!fs.existsSync(parcelModuleFolder)) { + return; + } + + const parcelModules = fs.readdirSync(parcelModuleFolder); + for (const moduleName of parcelModules) { + if (moduleName.startsWith('watcher-')) { + const modulePath = path.join(parcelModuleFolder, moduleName); + fs.rmSync(modulePath, { recursive: true, force: true }); + log(dir, `Removed @vscode/watcher prebuilt module ${modulePath}`); + } + } +} + +for (const dir of dirs) { + + if (dir === '') { + removeParcelWatcherPrebuild(dir); + continue; // already executed in root + } + + let opts: child_process.SpawnSyncOptions | undefined; + + if (dir === 'build') { + opts = { + env: { + ...process.env + }, + }; + if (process.env['CC']) { opts.env!['CC'] = 'gcc'; } + if (process.env['CXX']) { opts.env!['CXX'] = 'g++'; } + if (process.env['CXXFLAGS']) { opts.env!['CXXFLAGS'] = ''; } + if (process.env['LDFLAGS']) { opts.env!['LDFLAGS'] = ''; } + + setNpmrcConfig('build', opts.env!); + npmInstall('build', opts); + continue; + } + + if (/^(.build\/distro\/npm\/)?remote$/.test(dir)) { + // node modules used by vscode server + opts = { + env: { + ...process.env + }, + }; + if (process.env['VSCODE_REMOTE_CC']) { + opts.env!['CC'] = process.env['VSCODE_REMOTE_CC']; + } else { + delete opts.env!['CC']; + } + if (process.env['VSCODE_REMOTE_CXX']) { + opts.env!['CXX'] = process.env['VSCODE_REMOTE_CXX']; + } else { + delete opts.env!['CXX']; + } + if (process.env['CXXFLAGS']) { delete opts.env!['CXXFLAGS']; } + if (process.env['CFLAGS']) { delete opts.env!['CFLAGS']; } + if (process.env['LDFLAGS']) { delete opts.env!['LDFLAGS']; } + if (process.env['VSCODE_REMOTE_CXXFLAGS']) { opts.env!['CXXFLAGS'] = process.env['VSCODE_REMOTE_CXXFLAGS']; } + if (process.env['VSCODE_REMOTE_LDFLAGS']) { opts.env!['LDFLAGS'] = process.env['VSCODE_REMOTE_LDFLAGS']; } + if (process.env['VSCODE_REMOTE_NODE_GYP']) { opts.env!['npm_config_node_gyp'] = process.env['VSCODE_REMOTE_NODE_GYP']; } + + setNpmrcConfig('remote', opts.env!); + npmInstall(dir, opts); + continue; + } + + npmInstall(dir, opts); +} + +child_process.execSync('git config pull.rebase merges'); +child_process.execSync('git config blame.ignoreRevsFile .git-blame-ignore-revs'); diff --git a/build/npm/preinstall.js b/build/npm/preinstall.js deleted file mode 100644 index 164d752bbac60..0000000000000 --- a/build/npm/preinstall.js +++ /dev/null @@ -1,126 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const nodeVersion = /^(\d+)\.(\d+)\.(\d+)/.exec(process.versions.node); -const majorNodeVersion = parseInt(nodeVersion[1]); -const minorNodeVersion = parseInt(nodeVersion[2]); -const patchNodeVersion = parseInt(nodeVersion[3]); - -if (!process.env['VSCODE_SKIP_NODE_VERSION_CHECK']) { - if (majorNodeVersion < 22 || (majorNodeVersion === 22 && minorNodeVersion < 15) || (majorNodeVersion === 22 && minorNodeVersion === 15 && patchNodeVersion < 1)) { - console.error('\x1b[1;31m*** Please use Node.js v22.15.1 or later for development.\x1b[0;0m'); - throw new Error(); - } -} - -if (process.env['npm_execpath'].includes('yarn')) { - console.error('\x1b[1;31m*** Seems like you are using `yarn` which is not supported in this repo any more, please use `npm i` instead. ***\x1b[0;0m'); - throw new Error(); -} - -const path = require('path'); -const fs = require('fs'); -const cp = require('child_process'); -const os = require('os'); - -if (process.platform === 'win32') { - if (!hasSupportedVisualStudioVersion()) { - console.error('\x1b[1;31m*** Invalid C/C++ Compiler Toolchain. Please check https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites.\x1b[0;0m'); - throw new Error(); - } - installHeaders(); -} - -if (process.arch !== os.arch()) { - console.error(`\x1b[1;31m*** ARCHITECTURE MISMATCH: The node.js process is ${process.arch}, but your OS architecture is ${os.arch()}. ***\x1b[0;0m`); - console.error(`\x1b[1;31m*** This can greatly increase the build time of vs code. ***\x1b[0;0m`); -} - -function hasSupportedVisualStudioVersion() { - const fs = require('fs'); - const path = require('path'); - // Translated over from - // https://source.chromium.org/chromium/chromium/src/+/master:build/vs_toolchain.py;l=140-175 - const supportedVersions = ['2022', '2019', '2017']; - - const availableVersions = []; - for (const version of supportedVersions) { - let vsPath = process.env[`vs${version}_install`]; - if (vsPath && fs.existsSync(vsPath)) { - availableVersions.push(version); - break; - } - const programFiles86Path = process.env['ProgramFiles(x86)']; - const programFiles64Path = process.env['ProgramFiles']; - - const vsTypes = ['Enterprise', 'Professional', 'Community', 'Preview', 'BuildTools', 'IntPreview']; - if (programFiles64Path) { - vsPath = `${programFiles64Path}/Microsoft Visual Studio/${version}`; - if (vsTypes.some(vsType => fs.existsSync(path.join(vsPath, vsType)))) { - availableVersions.push(version); - break; - } - } - - if (programFiles86Path) { - vsPath = `${programFiles86Path}/Microsoft Visual Studio/${version}`; - if (vsTypes.some(vsType => fs.existsSync(path.join(vsPath, vsType)))) { - availableVersions.push(version); - break; - } - } - } - return availableVersions.length; -} - -function installHeaders() { - cp.execSync(`npm.cmd ${process.env['npm_command'] || 'ci'}`, { - env: process.env, - cwd: path.join(__dirname, 'gyp'), - stdio: 'inherit' - }); - - // The node gyp package got installed using the above npm command using the gyp/package.json - // file checked into our repository. So from that point it is save to construct the path - // to that executable - const node_gyp = path.join(__dirname, 'gyp', 'node_modules', '.bin', 'node-gyp.cmd'); - const result = cp.execFileSync(node_gyp, ['list'], { encoding: 'utf8', shell: true }); - const versions = new Set(result.split(/\n/g).filter(line => !line.startsWith('gyp info')).map(value => value)); - - const local = getHeaderInfo(path.join(__dirname, '..', '..', '.npmrc')); - const remote = getHeaderInfo(path.join(__dirname, '..', '..', 'remote', '.npmrc')); - - if (local !== undefined && !versions.has(local.target)) { - // Both disturl and target come from a file checked into our repository - cp.execFileSync(node_gyp, ['install', '--dist-url', local.disturl, local.target], { shell: true }); - } - - if (remote !== undefined && !versions.has(remote.target)) { - // Both disturl and target come from a file checked into our repository - cp.execFileSync(node_gyp, ['install', '--dist-url', remote.disturl, remote.target], { shell: true }); - } -} - -/** - * @param {string} rcFile - * @returns {{ disturl: string; target: string } | undefined} - */ -function getHeaderInfo(rcFile) { - const lines = fs.readFileSync(rcFile, 'utf8').split(/\r\n?/g); - let disturl, target; - for (const line of lines) { - let match = line.match(/\s*disturl=*\"(.*)\"\s*$/); - if (match !== null && match.length >= 1) { - disturl = match[1]; - } - match = line.match(/\s*target=*\"(.*)\"\s*$/); - if (match !== null && match.length >= 1) { - target = match[1]; - } - } - return disturl !== undefined && target !== undefined - ? { disturl, target } - : undefined; -} diff --git a/build/npm/preinstall.ts b/build/npm/preinstall.ts new file mode 100644 index 0000000000000..3476fcabb5009 --- /dev/null +++ b/build/npm/preinstall.ts @@ -0,0 +1,165 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import path from 'path'; +import * as fs from 'fs'; +import * as child_process from 'child_process'; +import * as os from 'os'; + +if (!process.env['VSCODE_SKIP_NODE_VERSION_CHECK']) { + // Get the running Node.js version + const nodeVersion = /^(\d+)\.(\d+)\.(\d+)/.exec(process.versions.node); + const majorNodeVersion = parseInt(nodeVersion![1]); + const minorNodeVersion = parseInt(nodeVersion![2]); + const patchNodeVersion = parseInt(nodeVersion![3]); + + // Get the required Node.js version from .nvmrc + const nvmrcPath = path.join(import.meta.dirname, '..', '..', '.nvmrc'); + const requiredVersion = fs.readFileSync(nvmrcPath, 'utf8').trim(); + const requiredVersionMatch = /^(\d+)\.(\d+)\.(\d+)/.exec(requiredVersion); + + if (!requiredVersionMatch) { + console.error('\x1b[1;31m*** Unable to parse required Node.js version from .nvmrc\x1b[0;0m'); + throw new Error(); + } + + const requiredMajor = parseInt(requiredVersionMatch[1]); + const requiredMinor = parseInt(requiredVersionMatch[2]); + const requiredPatch = parseInt(requiredVersionMatch[3]); + + if (majorNodeVersion < requiredMajor || + (majorNodeVersion === requiredMajor && minorNodeVersion < requiredMinor) || + (majorNodeVersion === requiredMajor && minorNodeVersion === requiredMinor && patchNodeVersion < requiredPatch)) { + console.error(`\x1b[1;31m*** Please use Node.js v${requiredVersion} or later for development. Currently using v${process.versions.node}.\x1b[0;0m`); + throw new Error(); + } +} + +if (process.env.npm_execpath?.includes('yarn')) { + console.error('\x1b[1;31m*** Seems like you are using `yarn` which is not supported in this repo any more, please use `npm i` instead. ***\x1b[0;0m'); + throw new Error(); +} + +if (process.platform === 'win32') { + if (!hasSupportedVisualStudioVersion()) { + console.error('\x1b[1;31m*** Invalid C/C++ Compiler Toolchain. Please check https://github.com/microsoft/vscode/wiki/How-to-Contribute#prerequisites.\x1b[0;0m'); + console.error('\x1b[1;31m*** If you have Visual Studio installed in a custom location, you can specify it via the environment variable:\x1b[0;0m'); + console.error('\x1b[1;31m*** set vs2022_install= (or vs2019_install for older versions)\x1b[0;0m'); + throw new Error(); + } +} + +installHeaders(); + +if (process.arch !== os.arch()) { + console.error(`\x1b[1;31m*** ARCHITECTURE MISMATCH: The node.js process is ${process.arch}, but your OS architecture is ${os.arch()}. ***\x1b[0;0m`); + console.error(`\x1b[1;31m*** This can greatly increase the build time of vs code. ***\x1b[0;0m`); +} + +function hasSupportedVisualStudioVersion() { + // Translated over from + // https://source.chromium.org/chromium/chromium/src/+/master:build/vs_toolchain.py;l=140-175 + const supportedVersions = ['2022', '2019']; + + const availableVersions = []; + for (const version of supportedVersions) { + // Check environment variable first (explicit override) + let vsPath = process.env[`vs${version}_install`]; + if (vsPath && fs.existsSync(vsPath)) { + availableVersions.push(version); + break; + } + + // Check default installation paths + const programFiles86Path = process.env['ProgramFiles(x86)']; + const programFiles64Path = process.env['ProgramFiles']; + + const vsTypes = ['Enterprise', 'Professional', 'Community', 'Preview', 'BuildTools', 'IntPreview']; + if (programFiles64Path) { + vsPath = `${programFiles64Path}/Microsoft Visual Studio/${version}`; + if (vsTypes.some(vsType => fs.existsSync(path.join(vsPath!, vsType)))) { + availableVersions.push(version); + break; + } + } + + if (programFiles86Path) { + vsPath = `${programFiles86Path}/Microsoft Visual Studio/${version}`; + if (vsTypes.some(vsType => fs.existsSync(path.join(vsPath!, vsType)))) { + availableVersions.push(version); + break; + } + } + } + + return availableVersions.length; +} + +function installHeaders() { + const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + child_process.execSync(`${npm} ${process.env.npm_command || 'ci'}`, { + env: process.env, + cwd: path.join(import.meta.dirname, 'gyp'), + stdio: 'inherit' + }); + + // The node gyp package got installed using the above npm command using the gyp/package.json + // file checked into our repository. So from that point it is safe to construct the path + // to that executable + const node_gyp = process.platform === 'win32' + ? path.join(import.meta.dirname, 'gyp', 'node_modules', '.bin', 'node-gyp.cmd') + : path.join(import.meta.dirname, 'gyp', 'node_modules', '.bin', 'node-gyp'); + + const local = getHeaderInfo(path.join(import.meta.dirname, '..', '..', '.npmrc')); + const remote = getHeaderInfo(path.join(import.meta.dirname, '..', '..', 'remote', '.npmrc')); + + if (local !== undefined) { + // Both disturl and target come from a file checked into our repository + child_process.execFileSync(node_gyp, ['install', '--dist-url', local.disturl, local.target], { shell: true }); + } + + if (remote !== undefined) { + // Both disturl and target come from a file checked into our repository + child_process.execFileSync(node_gyp, ['install', '--dist-url', remote.disturl, remote.target], { shell: true }); + } + + // On Linux, apply a patch to the downloaded headers + // Remove dependency on std::source_location to avoid bumping the required GCC version to 11+ + // Refs https://chromium-review.googlesource.com/c/v8/v8/+/6879784 + if (process.platform === 'linux') { + const homedir = os.homedir(); + const cachePath = process.env.XDG_CACHE_HOME || path.join(homedir, '.cache'); + const nodeGypCache = path.join(cachePath, 'node-gyp'); + const localHeaderPath = path.join(nodeGypCache, local!.target, 'include', 'node'); + if (fs.existsSync(localHeaderPath)) { + console.log('Applying v8-source-location.patch to', localHeaderPath); + try { + child_process.execFileSync('patch', ['-p0', '-i', path.join(import.meta.dirname, 'gyp', 'custom-headers', 'v8-source-location.patch')], { + cwd: localHeaderPath + }); + } catch (error) { + throw new Error(`Error applying v8-source-location.patch: ${(error as Error).message}`); + } + } + } +} + +function getHeaderInfo(rcFile: string): { disturl: string; target: string } | undefined { + const lines = fs.readFileSync(rcFile, 'utf8').split(/\r\n|\n/g); + let disturl: string | undefined; + let target: string | undefined; + for (const line of lines) { + let match = line.match(/\s*disturl=*\"(.*)\"\s*$/); + if (match !== null && match.length >= 1) { + disturl = match[1]; + } + match = line.match(/\s*target=*\"(.*)\"\s*$/); + if (match !== null && match.length >= 1) { + target = match[1]; + } + } + return disturl !== undefined && target !== undefined + ? { disturl, target } + : undefined; +} diff --git a/build/npm/update-all-grammars.mjs b/build/npm/update-all-grammars.mjs deleted file mode 100644 index 7e303a655f7a9..0000000000000 --- a/build/npm/update-all-grammars.mjs +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { spawn as _spawn } from 'child_process'; -import { readdirSync, readFileSync } from 'fs'; -import { join } from 'path'; -import url from 'url'; - -async function spawn(cmd, args, opts) { - return new Promise((c, e) => { - const child = _spawn(cmd, args, { shell: true, stdio: 'inherit', env: process.env, ...opts }); - child.on('close', code => code === 0 ? c() : e(`Returned ${code}`)); - }); -} - -async function main() { - await spawn('npm', ['ci'], { cwd: 'extensions' }); - - for (const extension of readdirSync('extensions')) { - try { - const packageJSON = JSON.parse(readFileSync(join('extensions', extension, 'package.json')).toString()); - if (!(packageJSON && packageJSON.scripts && packageJSON.scripts['update-grammar'])) { - continue; - } - } catch { - continue; - } - - await spawn(`npm`, ['run', 'update-grammar'], { cwd: `extensions/${extension}` }); - } - - // run integration tests - - if (process.platform === 'win32') { - _spawn('.\\scripts\\test-integration.bat', [], { env: process.env, stdio: 'inherit' }); - } else { - _spawn('/bin/bash', ['./scripts/test-integration.sh'], { env: process.env, stdio: 'inherit' }); - } -} - -if (import.meta.url === url.pathToFileURL(process.argv[1]).href) { - main().catch(err => { - console.error(err); - process.exit(1); - }); -} diff --git a/build/npm/update-all-grammars.ts b/build/npm/update-all-grammars.ts new file mode 100644 index 0000000000000..aae11ae132697 --- /dev/null +++ b/build/npm/update-all-grammars.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { spawn as _spawn } from 'child_process'; +import { readdirSync, readFileSync } from 'fs'; +import { join } from 'path'; + +async function spawn(cmd: string, args: string[], opts?: Parameters[2]) { + return new Promise((c, e) => { + const child = _spawn(cmd, args, { shell: true, stdio: 'inherit', env: process.env, ...opts }); + child.on('close', code => code === 0 ? c() : e(`Returned ${code}`)); + }); +} + +async function main() { + await spawn('npm', ['ci'], { cwd: 'extensions' }); + + for (const extension of readdirSync('extensions')) { + try { + const packageJSON = JSON.parse(readFileSync(join('extensions', extension, 'package.json')).toString()); + if (!(packageJSON && packageJSON.scripts && packageJSON.scripts['update-grammar'])) { + continue; + } + } catch { + continue; + } + + await spawn(`npm`, ['run', 'update-grammar'], { cwd: `extensions/${extension}` }); + } + + // run integration tests + + if (process.platform === 'win32') { + _spawn('.\\scripts\\test-integration.bat', [], { env: process.env, stdio: 'inherit' }); + } else { + _spawn('/bin/bash', ['./scripts/test-integration.sh'], { env: process.env, stdio: 'inherit' }); + } +} + +if (import.meta.main) { + try { + await main(); + } catch (err) { + console.error(err); + process.exit(1); + } +} diff --git a/build/npm/update-distro.mjs b/build/npm/update-distro.mjs deleted file mode 100644 index 655d9f2c243ad..0000000000000 --- a/build/npm/update-distro.mjs +++ /dev/null @@ -1,18 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { execSync } from 'child_process'; -import { join, resolve } from 'path'; -import { readFileSync, writeFileSync } from 'fs'; -import { fileURLToPath } from 'url'; - -const rootPath = resolve(fileURLToPath(import.meta.url), '..', '..', '..', '..'); -const vscodePath = join(rootPath, 'vscode'); -const distroPath = join(rootPath, 'vscode-distro'); -const commit = execSync('git rev-parse HEAD', { cwd: distroPath, encoding: 'utf8' }).trim(); -const packageJsonPath = join(vscodePath, 'package.json'); -const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); - -packageJson.distro = commit; -writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); diff --git a/build/npm/update-distro.ts b/build/npm/update-distro.ts new file mode 100644 index 0000000000000..3c58af6197e88 --- /dev/null +++ b/build/npm/update-distro.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { execSync } from 'child_process'; +import { join, resolve } from 'path'; +import { readFileSync, writeFileSync } from 'fs'; + +const rootPath = resolve(import.meta.dirname, '..', '..', '..'); +const vscodePath = join(rootPath, 'vscode'); +const distroPath = join(rootPath, 'vscode-distro'); +const commit = execSync('git rev-parse HEAD', { cwd: distroPath, encoding: 'utf8' }).trim(); +const packageJsonPath = join(vscodePath, 'package.json'); +const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); + +packageJson.distro = commit; +writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); diff --git a/build/npm/update-localization-extension.js b/build/npm/update-localization-extension.js deleted file mode 100644 index 6274323f74769..0000000000000 --- a/build/npm/update-localization-extension.js +++ /dev/null @@ -1,107 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -let i18n = require("../lib/i18n"); - -let fs = require("fs"); -let path = require("path"); - -let gulp = require('gulp'); -let vfs = require("vinyl-fs"); -let rimraf = require('rimraf'); -let minimist = require('minimist'); - -function update(options) { - let idOrPath = options._; - if (!idOrPath) { - throw new Error('Argument must be the location of the localization extension.'); - } - let location = options.location; - if (location !== undefined && !fs.existsSync(location)) { - throw new Error(`${location} doesn't exist.`); - } - let externalExtensionsLocation = options.externalExtensionsLocation; - if (externalExtensionsLocation !== undefined && !fs.existsSync(externalExtensionsLocation)) { - throw new Error(`${externalExtensionsLocation} doesn't exist.`); - } - let locExtFolder = idOrPath; - if (/^\w{2,3}(-\w+)?$/.test(idOrPath)) { - locExtFolder = path.join('..', 'vscode-loc', 'i18n', `vscode-language-pack-${idOrPath}`); - } - let locExtStat = fs.statSync(locExtFolder); - if (!locExtStat || !locExtStat.isDirectory) { - throw new Error('No directory found at ' + idOrPath); - } - let packageJSON = JSON.parse(fs.readFileSync(path.join(locExtFolder, 'package.json')).toString()); - let contributes = packageJSON['contributes']; - if (!contributes) { - throw new Error('The extension must define a "localizations" contribution in the "package.json"'); - } - let localizations = contributes['localizations']; - if (!localizations) { - throw new Error('The extension must define a "localizations" contribution of type array in the "package.json"'); - } - - localizations.forEach(function (localization) { - if (!localization.languageId || !localization.languageName || !localization.localizedLanguageName) { - throw new Error('Each localization contribution must define "languageId", "languageName" and "localizedLanguageName" properties.'); - } - let languageId = localization.languageId; - let translationDataFolder = path.join(locExtFolder, 'translations'); - - switch (languageId) { - case 'zh-cn': - languageId = 'zh-Hans'; - break; - case 'zh-tw': - languageId = 'zh-Hant'; - break; - case 'pt-br': - languageId = 'pt-BR'; - break; - } - - if (fs.existsSync(translationDataFolder) && fs.existsSync(path.join(translationDataFolder, 'main.i18n.json'))) { - console.log('Clearing \'' + translationDataFolder + '\'...'); - rimraf.sync(translationDataFolder); - } - - console.log(`Importing translations for ${languageId} form '${location}' to '${translationDataFolder}' ...`); - let translationPaths = []; - gulp.src([ - path.join(location, '**', languageId, '*.xlf'), - ...i18n.EXTERNAL_EXTENSIONS.map(extensionId => path.join(externalExtensionsLocation, extensionId, languageId, '*-new.xlf')) - ], { silent: false }) - .pipe(i18n.prepareI18nPackFiles(translationPaths)) - .on('error', (error) => { - console.log(`Error occurred while importing translations:`); - translationPaths = undefined; - if (Array.isArray(error)) { - error.forEach(console.log); - } else if (error) { - console.log(error); - } else { - console.log('Unknown error'); - } - }) - .pipe(vfs.dest(translationDataFolder)) - .on('end', function () { - if (translationPaths !== undefined) { - localization.translations = []; - for (let tp of translationPaths) { - localization.translations.push({ id: tp.id, path: `./translations/${tp.resourceName}` }); - } - fs.writeFileSync(path.join(locExtFolder, 'package.json'), JSON.stringify(packageJSON, null, '\t') + '\n'); - } - }); - }); -} -if (path.basename(process.argv[1]) === 'update-localization-extension.js') { - var options = minimist(process.argv.slice(2), { - string: ['location', 'externalExtensionsLocation'] - }); - update(options); -} diff --git a/build/npm/update-localization-extension.ts b/build/npm/update-localization-extension.ts new file mode 100644 index 0000000000000..cb7981b9388ed --- /dev/null +++ b/build/npm/update-localization-extension.ts @@ -0,0 +1,128 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as i18n from '../lib/i18n.ts'; +import fs from 'fs'; +import path from 'path'; +import gulp from 'gulp'; +import vfs from 'vinyl-fs'; +import rimraf from 'rimraf'; +import minimist from 'minimist'; + +interface Options { + _: string[]; + location?: string; + externalExtensionsLocation?: string; +} + +interface PackageJson { + contributes?: { + localizations?: Localization[]; + }; +} + +interface Localization { + languageId: string; + languageName: string; + localizedLanguageName: string; + translations?: Array<{ id: string; path: string }>; +} + +interface TranslationPath { + id: string; + resourceName: string; +} + +function update(options: Options) { + const idOrPath = options._[0]; + if (!idOrPath) { + throw new Error('Argument must be the location of the localization extension.'); + } + const location = options.location; + if (location !== undefined && !fs.existsSync(location)) { + throw new Error(`${location} doesn't exist.`); + } + const externalExtensionsLocation = options.externalExtensionsLocation; + if (externalExtensionsLocation !== undefined && !fs.existsSync(externalExtensionsLocation)) { + throw new Error(`${externalExtensionsLocation} doesn't exist.`); + } + let locExtFolder: string = idOrPath; + if (/^\w{2,3}(-\w+)?$/.test(idOrPath)) { + locExtFolder = path.join('..', 'vscode-loc', 'i18n', `vscode-language-pack-${idOrPath}`); + } + const locExtStat = fs.statSync(locExtFolder); + if (!locExtStat || !locExtStat.isDirectory) { + throw new Error('No directory found at ' + idOrPath); + } + const packageJSON = JSON.parse(fs.readFileSync(path.join(locExtFolder, 'package.json')).toString()) as PackageJson; + const contributes = packageJSON['contributes']; + if (!contributes) { + throw new Error('The extension must define a "localizations" contribution in the "package.json"'); + } + const localizations = contributes['localizations']; + if (!localizations) { + throw new Error('The extension must define a "localizations" contribution of type array in the "package.json"'); + } + + localizations.forEach(function (localization) { + if (!localization.languageId || !localization.languageName || !localization.localizedLanguageName) { + throw new Error('Each localization contribution must define "languageId", "languageName" and "localizedLanguageName" properties.'); + } + let languageId = localization.languageId; + const translationDataFolder = path.join(locExtFolder, 'translations'); + + switch (languageId) { + case 'zh-cn': + languageId = 'zh-Hans'; + break; + case 'zh-tw': + languageId = 'zh-Hant'; + break; + case 'pt-br': + languageId = 'pt-BR'; + break; + } + + if (fs.existsSync(translationDataFolder) && fs.existsSync(path.join(translationDataFolder, 'main.i18n.json'))) { + console.log('Clearing \'' + translationDataFolder + '\'...'); + rimraf.sync(translationDataFolder); + } + + console.log(`Importing translations for ${languageId} form '${location}' to '${translationDataFolder}' ...`); + let translationPaths: TranslationPath[] | undefined = []; + gulp.src([ + path.join(location!, '**', languageId, '*.xlf'), + ...i18n.EXTERNAL_EXTENSIONS.map((extensionId: string) => path.join(externalExtensionsLocation!, extensionId, languageId, '*-new.xlf')) + ], { silent: false }) + .pipe(i18n.prepareI18nPackFiles(translationPaths)) + .on('error', (error: unknown) => { + console.log(`Error occurred while importing translations:`); + translationPaths = undefined; + if (Array.isArray(error)) { + error.forEach(console.log); + } else if (error) { + console.log(error); + } else { + console.log('Unknown error'); + } + }) + .pipe(vfs.dest(translationDataFolder)) + .on('end', function () { + if (translationPaths !== undefined) { + localization.translations = []; + for (const tp of translationPaths) { + localization.translations.push({ id: tp.id, path: `./translations/${tp.resourceName}` }); + } + fs.writeFileSync(path.join(locExtFolder, 'package.json'), JSON.stringify(packageJSON, null, '\t') + '\n'); + } + }); + }); +} +if (path.basename(process.argv[1]) === 'update-localization-extension.js') { + const options = minimist(process.argv.slice(2), { + string: ['location', 'externalExtensionsLocation'] + }) as Options; + update(options); +} diff --git a/build/package-lock.json b/build/package-lock.json index c4f99c2fefc21..b0ebfdd2e04f9 100644 --- a/build/package-lock.json +++ b/build/package-lock.json @@ -25,26 +25,31 @@ "@types/glob": "^7.1.1", "@types/gulp": "^4.0.17", "@types/gulp-filter": "^3.0.32", + "@types/gulp-flatmap": "^1.0.0", "@types/gulp-gzip": "^0.0.31", "@types/gulp-json-editor": "^2.2.31", + "@types/gulp-plumber": "^0.0.37", "@types/gulp-rename": "^0.0.33", + "@types/gulp-replace": "^0.0.31", "@types/gulp-sort": "^2.0.4", "@types/gulp-sourcemaps": "^0.0.32", "@types/jws": "^3.2.10", + "@types/lazy.js": "^0.5.9", "@types/mime": "0.0.29", "@types/minimatch": "^3.0.3", "@types/minimist": "^1.2.1", - "@types/mocha": "^9.1.1", - "@types/node": "22.x", + "@types/node": "^22.18.10", + "@types/p-all": "^1.0.0", "@types/pump": "^1.0.1", "@types/rimraf": "^2.0.4", "@types/through": "^0.0.29", "@types/through2": "^2.0.36", + "@types/vinyl": "^2.0.12", "@types/workerpool": "^6.4.0", "@types/xml2js": "0.0.33", - "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/iconv-lite-umd": "0.7.1", "@vscode/ripgrep": "^1.15.13", - "@vscode/vsce": "2.20.1", + "@vscode/vsce": "3.6.1", "ansi-colors": "^3.2.3", "byline": "^5.0.0", "debug": "^4.3.2", @@ -53,7 +58,7 @@ "gulp-merge-json": "^2.1.1", "gulp-sort": "^2.0.0", "jsonc-parser": "^2.3.0", - "jws": "^4.0.0", + "jws": "^4.0.1", "mime": "^1.4.1", "source-map": "0.6.1", "ternary-stream": "^3.0.0", @@ -68,6 +73,23 @@ "vscode-gulp-watch": "^5.0.3" } }, + "node_modules/@azu/format-text": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", + "integrity": "sha512-Swi4N7Edy1Eqq82GxgEECXSSLyn6GOb5htRFPzBDdUkECGXtlf12ynO5oJSpWKPwCaUssOu7NfhDcCWpIC6Ywg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@azu/style-format": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azu/style-format/-/style-format-1.0.1.tgz", + "integrity": "sha512-AHcTojlNBdD/3/KxIKlg8sxIWHfOtQszLvOpagLTO+bjC3u7SAszu1lf//u7JJC50aUSH+BVWDD/KvaA6Gfn5g==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "@azu/format-text": "^1.0.1" + } + }, "node_modules/@azure/abort-controller": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.0.2.tgz", @@ -441,6 +463,31 @@ "node": ">=18.0.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@electron/asar": { "version": "3.2.10", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.2.10.tgz", @@ -961,6 +1008,72 @@ "node": ">=18" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@malept/cross-spawn-promise": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", @@ -983,213 +1096,629 @@ "node": ">= 12.13.0" } }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "engines": { - "node": ">=10" + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "engines": { + "node": ">= 8" } }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.0" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 8" } }, - "node_modules/@types/ansi-colors": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@types/ansi-colors/-/ansi-colors-3.2.0.tgz", - "integrity": "sha512-0caWAhXht9N2lOdMzJLXybsSkYCx1QOdxx6pae48tswI9QV3DFX26AoOpy0JxwhCb+zISTqmd6H8t9Zby9BoZg==", - "dev": true - }, - "node_modules/@types/byline": { - "version": "4.2.32", - "resolved": "https://registry.npmjs.org/@types/byline/-/byline-4.2.32.tgz", - "integrity": "sha512-qtlm/J6XOO9p+Ep/ZB5+mCFEDhzWDDHWU4a1eReN7lkPZXW9rkloq2jcAhvKKmlO5tL2GSvKROb+PTsNVhBiyQ==", + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*" + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "node_modules/@secretlint/config-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", "dev": true, + "license": "MIT", "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@types/debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.0.0.tgz", - "integrity": "sha1-QXVgIAMx4buE1y2oU5EQLC/NYbc= sha512-B7FcD9ry40L831t7iuHQyDfYi+qVEV75qkEI2ROOyfjb2PfkAspL+NK6B2A0BceMuNhiYRmtKTNnNP7Ul4N2Pg==", - "dev": true - }, - "node_modules/@types/debug": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.9.tgz", - "integrity": "sha512-8Hz50m2eoS56ldRlepxSBa6PWEVCtzUo/92HgLc2qTMnotJNIm7xP+UZhyWoYsyOdd5dxZ+NZLb24rsKyFs2ow==", + "node_modules/@secretlint/config-loader": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", "dev": true, + "license": "MIT", "dependencies": { - "@types/ms": "*" + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@types/events": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", - "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", - "dev": true - }, - "node_modules/@types/expect": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", - "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", - "dev": true - }, - "node_modules/@types/fancy-log": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@types/fancy-log/-/fancy-log-1.3.0.tgz", - "integrity": "sha512-mQjDxyOM1Cpocd+vm1kZBP7smwKZ4TNokFeds9LV7OZibmPJFEzY3+xZMrKfUdNT71lv8GoCPD6upKwHxubClw==", - "dev": true - }, - "node_modules/@types/fs-extra": { - "version": "9.0.12", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.12.tgz", - "integrity": "sha512-I+bsBr67CurCGnSenZZ7v94gd3tc3+Aj2taxMT4yu4ABLuOgOjeFxX3dokG24ztSRg5tnT00sL8BszO7gSMoIw==", + "node_modules/@secretlint/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*" + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@types/glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", - "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "node_modules/@secretlint/formatter": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", "dev": true, + "license": "MIT", "dependencies": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@types/glob-stream": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/glob-stream/-/glob-stream-6.1.0.tgz", - "integrity": "sha512-RHv6ZQjcTncXo3thYZrsbAVwoy4vSKosSWhuhuQxLOTv74OJuFQxXkmUuZCr3q9uNBEVCvIzmZL/FeRNbHZGUg==", + "node_modules/@secretlint/formatter/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, - "dependencies": { - "@types/glob": "*", - "@types/node": "*" + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "node_modules/@secretlint/node": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@types/gulp": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@types/gulp/-/gulp-4.0.17.tgz", - "integrity": "sha512-+pKQynu2C/HS16kgmDlAicjtFYP8kaa86eE9P0Ae7GB5W29we/E2TIdbOWtEZD5XkpY+jr8fyqfwO6SWZecLpQ==", + "node_modules/@secretlint/profiler": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", "dev": true, - "dependencies": { - "@types/node": "*", - "@types/undertaker": ">=1.2.6", - "@types/vinyl-fs": "*", - "chokidar": "^3.3.1" - } + "license": "MIT" }, - "node_modules/@types/gulp-filter": { - "version": "3.0.32", - "resolved": "https://registry.npmjs.org/@types/gulp-filter/-/gulp-filter-3.0.32.tgz", - "integrity": "sha512-JvY4qTxXehoK2yCUxYVxTMvckVbDM5TWHWeUoYJyX31gwFqw4YDD6JNzhuTxI3yHPUTY4BBRTqgm6puQEZVCNg==", + "node_modules/@secretlint/resolver": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", "dev": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*", - "@types/vinyl": "*" - } + "license": "MIT" }, - "node_modules/@types/gulp-gzip": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/@types/gulp-gzip/-/gulp-gzip-0.0.31.tgz", - "integrity": "sha512-KQjHz1FTqLse8/EiktfhN/vo33vamX4gVAQhMTp55STDBA7UToW5CJqYyP7iRorkHK9/aJ2nL2hLkNZkY4M8+w==", + "node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*" + "node-sarif-builder": "^3.2.0" } }, - "node_modules/@types/gulp-json-editor": { - "version": "2.2.31", - "resolved": "https://registry.npmjs.org/@types/gulp-json-editor/-/gulp-json-editor-2.2.31.tgz", - "integrity": "sha512-piis0ImYAy0dt18R4EtTbAY+RV8jwTq5VisnUV6OfP8kD4743aHGkAdAd08No4NY3rFa5mD6ytIu8L0YU7nL9w==", + "node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/js-beautify": "*", - "@types/node": "*" + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@types/gulp-rename": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/gulp-rename/-/gulp-rename-0.0.33.tgz", - "integrity": "sha512-FIZQvbZJj6V1gHPTzO+g/BCWpDur7fJrroae4gwV3LaoHBQ+MrR9sB+2HssK8fHv4WdY6hVNxkcft9bYatuPIA==", + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", "dev": true, - "dependencies": { - "@types/node": "*" + "license": "MIT", + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@types/gulp-sort": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/gulp-sort/-/gulp-sort-2.0.4.tgz", - "integrity": "sha512-HUHxH+oMox1ct0SnxPqCXBni0MSws1ygcSAoLO4ISRmR/UuvNIz40rgNveZxwxQk+p78kw09z/qKQkgKJmNUOQ==", + "node_modules/@secretlint/source-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", "dev": true, + "license": "MIT", "dependencies": { - "@types/gulp-util": "*", - "@types/node": "*" + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@types/gulp-sourcemaps": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@types/gulp-sourcemaps/-/gulp-sourcemaps-0.0.32.tgz", - "integrity": "sha512-+7BAmptW2bxyJnJcCEuie7vLoop3FwWgCdBMzyv7MYXED/HeNMeQuX7uPCkp4vfU1TTu4CYFH0IckNPvo0VePA==", + "node_modules/@secretlint/types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", "dev": true, - "dependencies": { - "@types/node": "*" + "license": "MIT", + "engines": { + "node": ">=20.0.0" } }, - "node_modules/@types/gulp-util": { - "version": "3.0.41", - "resolved": "https://registry.npmjs.org/@types/gulp-util/-/gulp-util-3.0.41.tgz", - "integrity": "sha512-BK0kJZ8euQNlISsmD6mBr/1RZkB0mljdtBsz2usv+QHPV10alH2AJw5p05S9LU6S+VdTjbFmGU0OxpH++2W9/Q==", + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", "dev": true, - "dependencies": { - "@types/node": "*", - "@types/through2": "*", - "@types/vinyl": "*", - "chalk": "^2.2.0" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/@types/http-cache-semantics": { + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@textlint/ast-node-types": { + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.2.2.tgz", + "integrity": "sha512-9ByYNzWV8tpz6BFaRzeRzIov8dkbSZu9q7IWqEIfmRuLWb2qbI/5gTvKcoWT1HYs4XM7IZ8TKSXcuPvMb6eorA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter": { + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.2.2.tgz", + "integrity": "sha512-oMVaMJ3exFvXhCj3AqmCbLaeYrTNLqaJnLJMIlmnRM3/kZdxvku4OYdaDzgtlI194cVxamOY5AbHBBVnY79kEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azu/format-text": "^1.0.2", + "@azu/style-format": "^1.0.1", + "@textlint/module-interop": "15.2.2", + "@textlint/resolver": "15.2.2", + "@textlint/types": "15.2.2", + "chalk": "^4.1.2", + "debug": "^4.4.1", + "js-yaml": "^3.14.1", + "lodash": "^4.17.21", + "pluralize": "^2.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "table": "^6.9.0", + "text-table": "^0.2.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/pluralize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-2.0.0.tgz", + "integrity": "sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/linter-formatter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/linter-formatter/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@textlint/module-interop": { + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.2.2.tgz", + "integrity": "sha512-2rmNcWrcqhuR84Iio1WRzlc4tEoOMHd6T7urjtKNNefpTt1owrTJ9WuOe60yD3FrTW0J/R0ux5wxUbP/eaeFOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/resolver": { + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.2.2.tgz", + "integrity": "sha512-4hGWjmHt0y+5NAkoYZ8FvEkj8Mez9TqfbTm3BPjoV32cIfEixl2poTOgapn1rfm73905GSO3P1jiWjmgvii13Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@textlint/types": { + "version": "15.2.2", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.2.2.tgz", + "integrity": "sha512-X2BHGAR3yXJsCAjwYEDBIk9qUDWcH4pW61ISfmtejau+tVqKtnbbvEZnMTb6mWgKU1BvTmftd5DmB1XVDUtY3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@textlint/ast-node-types": "15.2.2" + } + }, + "node_modules/@types/ansi-colors": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/ansi-colors/-/ansi-colors-3.2.0.tgz", + "integrity": "sha512-0caWAhXht9N2lOdMzJLXybsSkYCx1QOdxx6pae48tswI9QV3DFX26AoOpy0JxwhCb+zISTqmd6H8t9Zby9BoZg==", + "dev": true + }, + "node_modules/@types/byline": { + "version": "4.2.32", + "resolved": "https://registry.npmjs.org/@types/byline/-/byline-4.2.32.tgz", + "integrity": "sha512-qtlm/J6XOO9p+Ep/ZB5+mCFEDhzWDDHWU4a1eReN7lkPZXW9rkloq2jcAhvKKmlO5tL2GSvKROb+PTsNVhBiyQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/debounce/-/debounce-1.0.0.tgz", + "integrity": "sha1-QXVgIAMx4buE1y2oU5EQLC/NYbc= sha512-B7FcD9ry40L831t7iuHQyDfYi+qVEV75qkEI2ROOyfjb2PfkAspL+NK6B2A0BceMuNhiYRmtKTNnNP7Ul4N2Pg==", + "dev": true + }, + "node_modules/@types/debug": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.9.tgz", + "integrity": "sha512-8Hz50m2eoS56ldRlepxSBa6PWEVCtzUo/92HgLc2qTMnotJNIm7xP+UZhyWoYsyOdd5dxZ+NZLb24rsKyFs2ow==", + "dev": true, + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/events": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", + "dev": true + }, + "node_modules/@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", + "dev": true + }, + "node_modules/@types/fancy-log": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@types/fancy-log/-/fancy-log-1.3.0.tgz", + "integrity": "sha512-mQjDxyOM1Cpocd+vm1kZBP7smwKZ4TNokFeds9LV7OZibmPJFEzY3+xZMrKfUdNT71lv8GoCPD6upKwHxubClw==", + "dev": true + }, + "node_modules/@types/fs-extra": { + "version": "9.0.12", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.12.tgz", + "integrity": "sha512-I+bsBr67CurCGnSenZZ7v94gd3tc3+Aj2taxMT4yu4ABLuOgOjeFxX3dokG24ztSRg5tnT00sL8BszO7gSMoIw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "dev": true, + "dependencies": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha512-RHv6ZQjcTncXo3thYZrsbAVwoy4vSKosSWhuhuQxLOTv74OJuFQxXkmUuZCr3q9uNBEVCvIzmZL/FeRNbHZGUg==", + "dev": true, + "dependencies": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/gulp": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@types/gulp/-/gulp-4.0.17.tgz", + "integrity": "sha512-+pKQynu2C/HS16kgmDlAicjtFYP8kaa86eE9P0Ae7GB5W29we/E2TIdbOWtEZD5XkpY+jr8fyqfwO6SWZecLpQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/undertaker": ">=1.2.6", + "@types/vinyl-fs": "*", + "chokidar": "^3.3.1" + } + }, + "node_modules/@types/gulp-filter": { + "version": "3.0.32", + "resolved": "https://registry.npmjs.org/@types/gulp-filter/-/gulp-filter-3.0.32.tgz", + "integrity": "sha512-JvY4qTxXehoK2yCUxYVxTMvckVbDM5TWHWeUoYJyX31gwFqw4YDD6JNzhuTxI3yHPUTY4BBRTqgm6puQEZVCNg==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*", + "@types/vinyl": "*" + } + }, + "node_modules/@types/gulp-flatmap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/gulp-flatmap/-/gulp-flatmap-1.0.0.tgz", + "integrity": "sha512-GTv0a9BxhbWYkxaPDCqnZFI13pXUUpJ90hBWkhGOQQ76qDDtHWugr0+IEiTEc0KYS0bOs80YszZE7WFNA5ndfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/vinyl": "*" + } + }, + "node_modules/@types/gulp-gzip": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/@types/gulp-gzip/-/gulp-gzip-0.0.31.tgz", + "integrity": "sha512-KQjHz1FTqLse8/EiktfhN/vo33vamX4gVAQhMTp55STDBA7UToW5CJqYyP7iRorkHK9/aJ2nL2hLkNZkY4M8+w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/gulp-json-editor": { + "version": "2.2.31", + "resolved": "https://registry.npmjs.org/@types/gulp-json-editor/-/gulp-json-editor-2.2.31.tgz", + "integrity": "sha512-piis0ImYAy0dt18R4EtTbAY+RV8jwTq5VisnUV6OfP8kD4743aHGkAdAd08No4NY3rFa5mD6ytIu8L0YU7nL9w==", + "dev": true, + "dependencies": { + "@types/js-beautify": "*", + "@types/node": "*" + } + }, + "node_modules/@types/gulp-plumber": { + "version": "0.0.37", + "resolved": "https://registry.npmjs.org/@types/gulp-plumber/-/gulp-plumber-0.0.37.tgz", + "integrity": "sha512-U1vFhhwDepAWmJ1ZVl6p+uwk/+rAs8+QLTRlrMLMQQ7KeqPPCvD5vy6JHMeqXwnxMSlbboa2PXQqoMg+ljZIJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/gulp-rename": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/gulp-rename/-/gulp-rename-0.0.33.tgz", + "integrity": "sha512-FIZQvbZJj6V1gHPTzO+g/BCWpDur7fJrroae4gwV3LaoHBQ+MrR9sB+2HssK8fHv4WdY6hVNxkcft9bYatuPIA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/gulp-replace": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/@types/gulp-replace/-/gulp-replace-0.0.31.tgz", + "integrity": "sha512-dbgQ1u0N9ShXrzahBgQfMSu6qUh8nlTLt7whhQ0S0sEUHhV3scysppJ1UX0fl53PJENgAL99ueykddyrCaDt7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/gulp-sort": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/gulp-sort/-/gulp-sort-2.0.4.tgz", + "integrity": "sha512-HUHxH+oMox1ct0SnxPqCXBni0MSws1ygcSAoLO4ISRmR/UuvNIz40rgNveZxwxQk+p78kw09z/qKQkgKJmNUOQ==", + "dev": true, + "dependencies": { + "@types/gulp-util": "*", + "@types/node": "*" + } + }, + "node_modules/@types/gulp-sourcemaps": { + "version": "0.0.32", + "resolved": "https://registry.npmjs.org/@types/gulp-sourcemaps/-/gulp-sourcemaps-0.0.32.tgz", + "integrity": "sha512-+7BAmptW2bxyJnJcCEuie7vLoop3FwWgCdBMzyv7MYXED/HeNMeQuX7uPCkp4vfU1TTu4CYFH0IckNPvo0VePA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/gulp-util": { + "version": "3.0.41", + "resolved": "https://registry.npmjs.org/@types/gulp-util/-/gulp-util-3.0.41.tgz", + "integrity": "sha512-BK0kJZ8euQNlISsmD6mBr/1RZkB0mljdtBsz2usv+QHPV10alH2AJw5p05S9LU6S+VdTjbFmGU0OxpH++2W9/Q==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/through2": "*", + "@types/vinyl": "*", + "chalk": "^2.2.0" + } + }, + "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", @@ -1220,6 +1749,13 @@ "@types/node": "*" } }, + "node_modules/@types/lazy.js": { + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@types/lazy.js/-/lazy.js-0.5.9.tgz", + "integrity": "sha512-oO7oF31unBSr3M4yshbgie/PP5VLQTWvopHQqD0OYIQ1ItFHlxBJBBYAn6gOiRK4elQKF0s/nJ45wIJziU4MqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-0.0.29.tgz", @@ -1238,12 +1774,6 @@ "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", "dev": true }, - "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true - }, "node_modules/@types/ms": { "version": "0.7.32", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.32.tgz", @@ -1251,15 +1781,29 @@ "dev": true }, "node_modules/@types/node": { - "version": "22.13.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", - "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==", + "version": "22.18.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.13.tgz", + "integrity": "sha512-Bo45YKIjnmFtv6I1TuC8AaHBbqXtIo+Om5fE4QiU1Tj8QR/qt+8O3BAtOimG5IFmwaWiPmB3Mv3jtYzBA4Us2A==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.20.0" + "undici-types": "~6.21.0" } }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/p-all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/p-all/-/p-all-1.0.0.tgz", + "integrity": "sha512-ZaM7VBS9kzAcDPB7YkoQWYxujUQyblUsjBmVaSO0igkoO7/Sus+cIoriD8/8RPpCOFnyPU1SfDDB5hH+i4S9Eg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/pump": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@types/pump/-/pump-1.0.1.tgz", @@ -1288,6 +1832,13 @@ "@types/node": "*" } }, + "node_modules/@types/sarif": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", + "integrity": "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/through": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.29.tgz", @@ -1311,128 +1862,379 @@ "resolved": "https://registry.npmjs.org/@types/undertaker/-/undertaker-1.2.11.tgz", "integrity": "sha512-j1Z0V2ByRHr8ZK7eOeGq0LGkkdthNFW0uAZGY22iRkNQNL9/vAV0yFPr1QN3FM/peY5bxs9P+1f0PYJTQVa5iA==", "dev": true, - "dependencies": { - "@types/node": "*", - "@types/undertaker-registry": "*", - "async-done": "~1.3.2" - } + "dependencies": { + "@types/node": "*", + "@types/undertaker-registry": "*", + "async-done": "~1.3.2" + } + }, + "node_modules/@types/undertaker-registry": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/undertaker-registry/-/undertaker-registry-1.0.1.tgz", + "integrity": "sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==", + "dev": true + }, + "node_modules/@types/vinyl": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", + "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, + "node_modules/@types/vinyl-fs": { + "version": "2.4.9", + "resolved": "https://registry.npmjs.org/@types/vinyl-fs/-/vinyl-fs-2.4.9.tgz", + "integrity": "sha512-Q0EXd6c1fORjiOuK4ZaKdfFcMyFzJlTi56dqktwaWVLIDAzE49wUs3bKnYbZwzyMWoH+NcMWnRuR73S9A0jnRA==", + "dev": true, + "dependencies": { + "@types/events": "*", + "@types/glob-stream": "*", + "@types/node": "*", + "@types/vinyl": "*" + } + }, + "node_modules/@types/workerpool": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@types/workerpool/-/workerpool-6.4.0.tgz", + "integrity": "sha512-SIF2/169pDsLKeM8GQGHkOFifGalDbZgiBSaLUnnlVSRsAOenkAvQ6h4uhV2W+PZZczS+8LQxACwNkSykdT91A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/xml2js": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.0.33.tgz", + "integrity": "sha1-IMXdZGAkUoTWSlVpABW5XkCft94= sha512-6dx6V6EdddqLjhxGdQrNdSu+O+3F7tOlyj660SpkO4/5uDSZM+LXcGQKAFnIJbvTzkQ6d2g3rWxyEXVwYAUoJg==", + "dev": true + }, + "node_modules/@types/yauzl": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", + "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vscode/iconv-lite-umd": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.1.tgz", + "integrity": "sha512-tK6k0DXFHW7q5+GGuGZO+phpAqpxO4WXl+BLc/8/uOk3RsM2ssAL3CQUQDb1TGfwltjsauhN6S4ghYZzs4sPFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vscode/ripgrep": { + "version": "1.15.14", + "resolved": "https://registry.npmjs.org/@vscode/ripgrep/-/ripgrep-1.15.14.tgz", + "integrity": "sha512-/G1UJPYlm+trBWQ6cMO3sv6b8D1+G16WaJH1/DSqw32JOVlzgZbLkDxRyzIpTpv30AcYGMkCf5tUqGlW6HbDWw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "https-proxy-agent": "^7.0.2", + "proxy-from-env": "^1.1.0", + "yauzl": "^2.9.2" + } + }, + "node_modules/@vscode/vsce": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.6.1.tgz", + "integrity": "sha512-UXtMgeCBl/t5zjn1TX1v1sl5L/oIv3Xc3pkKPGzaqeFCIkp5+wfFFDBXTWDt3d5uUulHnZKORHkMIsKNe9+k5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@secretlint/node": "^10.1.2", + "@secretlint/secretlint-formatter-sarif": "^10.1.2", + "@secretlint/secretlint-rule-no-dotenv": "^10.1.2", + "@secretlint/secretlint-rule-preset-recommend": "^10.1.2", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^4.1.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^12.1.0", + "form-data": "^4.0.0", + "glob": "^11.0.0", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^14.1.0", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "secretlint": "^10.1.2", + "semver": "^7.5.2", + "tmp": "^0.2.3", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign/-/vsce-sign-2.0.7.tgz", + "integrity": "sha512-cz0GFW8qCxpypOy3y509u26K1FIPMlDIHBwGmDyvEbgoma2v3y5YIHHuijr8zCYBp9kzCCOJd28s/0PG7cA7ew==", + "dev": true, + "hasInstallScript": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optionalDependencies": { + "@vscode/vsce-sign-alpine-arm64": "2.0.6", + "@vscode/vsce-sign-alpine-x64": "2.0.6", + "@vscode/vsce-sign-darwin-arm64": "2.0.6", + "@vscode/vsce-sign-darwin-x64": "2.0.6", + "@vscode/vsce-sign-linux-arm": "2.0.6", + "@vscode/vsce-sign-linux-arm64": "2.0.6", + "@vscode/vsce-sign-linux-x64": "2.0.6", + "@vscode/vsce-sign-win32-arm64": "2.0.6", + "@vscode/vsce-sign-win32-x64": "2.0.6" + } + }, + "node_modules/@vscode/vsce-sign-alpine-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-arm64/-/vsce-sign-alpine-arm64-2.0.6.tgz", + "integrity": "sha512-wKkJBsvKF+f0GfsUuGT0tSW0kZL87QggEiqNqK6/8hvqsXvpx8OsTEc3mnE1kejkh5r+qUyQ7PtF8jZYN0mo8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-alpine-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-alpine-x64/-/vsce-sign-alpine-x64-2.0.6.tgz", + "integrity": "sha512-YoAGlmdK39vKi9jA18i4ufBbd95OqGJxRvF3n6ZbCyziwy3O+JgOpIUPxv5tjeO6gQfx29qBivQ8ZZTUF2Ba0w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "alpine" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-arm64/-/vsce-sign-darwin-arm64-2.0.6.tgz", + "integrity": "sha512-5HMHaJRIQuozm/XQIiJiA0W9uhdblwwl2ZNDSSAeXGO9YhB9MH5C4KIHOmvyjUnKy4UCuiP43VKpIxW1VWP4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-darwin-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-darwin-x64/-/vsce-sign-darwin-x64-2.0.6.tgz", + "integrity": "sha512-25GsUbTAiNfHSuRItoQafXOIpxlYj+IXb4/qarrXu7kmbH94jlm5sdWSCKrrREs8+GsXF1b+l3OB7VJy5jsykw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm/-/vsce-sign-linux-arm-2.0.6.tgz", + "integrity": "sha512-UndEc2Xlq4HsuMPnwu7420uqceXjs4yb5W8E2/UkaHBB9OWCwMd3/bRe/1eLe3D8kPpxzcaeTyXiK3RdzS/1CA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-arm64/-/vsce-sign-linux-arm64-2.0.6.tgz", + "integrity": "sha512-cfb1qK7lygtMa4NUl2582nP7aliLYuDEVpAbXJMkDq1qE+olIw/es+C8j1LJwvcRq1I2yWGtSn3EkDp9Dq5FdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@vscode/vsce-sign-linux-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-linux-x64/-/vsce-sign-linux-x64-2.0.6.tgz", + "integrity": "sha512-/olerl1A4sOqdP+hjvJ1sbQjKN07Y3DVnxO4gnbn/ahtQvFrdhUi0G1VsZXDNjfqmXw57DmPi5ASnj/8PGZhAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/undertaker-registry": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/undertaker-registry/-/undertaker-registry-1.0.1.tgz", - "integrity": "sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==", - "dev": true + "node_modules/@vscode/vsce-sign-win32-arm64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-arm64/-/vsce-sign-win32-arm64-2.0.6.tgz", + "integrity": "sha512-ivM/MiGIY0PJNZBoGtlRBM/xDpwbdlCWomUWuLmIxbi1Cxe/1nooYrEQoaHD8ojVRgzdQEUzMsRbyF5cJJgYOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@types/vinyl": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", - "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", + "node_modules/@vscode/vsce-sign-win32-x64": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@vscode/vsce-sign-win32-x64/-/vsce-sign-win32-x64-2.0.6.tgz", + "integrity": "sha512-mgth9Kvze+u8CruYMmhHw6Zgy3GRX2S+Ed5oSokDEK5vPEwGGKnmuXua9tmFhomeAnhgJnL4DCna3TiNuGrBTQ==", + "cpu": [ + "x64" + ], "dev": true, - "dependencies": { - "@types/expect": "^1.20.4", - "@types/node": "*" - } + "license": "SEE LICENSE IN LICENSE.txt", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@types/vinyl-fs": { - "version": "2.4.9", - "resolved": "https://registry.npmjs.org/@types/vinyl-fs/-/vinyl-fs-2.4.9.tgz", - "integrity": "sha512-Q0EXd6c1fORjiOuK4ZaKdfFcMyFzJlTi56dqktwaWVLIDAzE49wUs3bKnYbZwzyMWoH+NcMWnRuR73S9A0jnRA==", + "node_modules/@vscode/vsce/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/events": "*", - "@types/glob-stream": "*", - "@types/node": "*", - "@types/vinyl": "*" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@types/workerpool": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@types/workerpool/-/workerpool-6.4.0.tgz", - "integrity": "sha512-SIF2/169pDsLKeM8GQGHkOFifGalDbZgiBSaLUnnlVSRsAOenkAvQ6h4uhV2W+PZZczS+8LQxACwNkSykdT91A==", + "node_modules/@vscode/vsce/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { - "@types/node": "*" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@types/xml2js": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.0.33.tgz", - "integrity": "sha1-IMXdZGAkUoTWSlVpABW5XkCft94= sha512-6dx6V6EdddqLjhxGdQrNdSu+O+3F7tOlyj660SpkO4/5uDSZM+LXcGQKAFnIJbvTzkQ6d2g3rWxyEXVwYAUoJg==", - "dev": true - }, - "node_modules/@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", + "node_modules/@vscode/vsce/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, + "license": "MIT", "dependencies": { - "@types/node": "*" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "node_modules/@vscode/iconv-lite-umd": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz", - "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==", - "dev": true + "node_modules/@vscode/vsce/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" }, - "node_modules/@vscode/ripgrep": { - "version": "1.15.14", - "resolved": "https://registry.npmjs.org/@vscode/ripgrep/-/ripgrep-1.15.14.tgz", - "integrity": "sha512-/G1UJPYlm+trBWQ6cMO3sv6b8D1+G16WaJH1/DSqw32JOVlzgZbLkDxRyzIpTpv30AcYGMkCf5tUqGlW6HbDWw==", + "node_modules/@vscode/vsce/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", "dev": true, - "hasInstallScript": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "https-proxy-agent": "^7.0.2", - "proxy-from-env": "^1.1.0", - "yauzl": "^2.9.2" + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@vscode/vsce": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.20.1.tgz", - "integrity": "sha512-ilbvoqvR/1/zseRPBAzYR6aKqSJ+jvda4/BqIwOqTxajpvLtEpK3kMLs77+dJdrlygS+VrP7Yhad8j0ukyD96g==", + "node_modules/@vscode/vsce/node_modules/glob/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "azure-devops-node-api": "^11.0.1", - "chalk": "^2.4.2", - "cheerio": "^1.0.0-rc.9", - "commander": "^6.1.0", - "glob": "^7.0.6", - "hosted-git-info": "^4.0.2", - "jsonc-parser": "^3.2.0", - "leven": "^3.1.0", - "markdown-it": "^12.3.2", - "mime": "^1.3.4", - "minimatch": "^3.0.3", - "parse-semver": "^1.1.1", - "read": "^1.0.7", - "semver": "^7.5.2", - "tmp": "^0.2.1", - "typed-rest-client": "^1.8.4", - "url-join": "^4.0.1", - "xml2js": "^0.5.0", - "yauzl": "^2.3.1", - "yazl": "^2.2.2" - }, - "bin": { - "vsce": "vsce" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": ">= 14" + "node": "20 || >=22" }, - "optionalDependencies": { - "keytar": "^7.7.0" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@vscode/vsce/node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "node_modules/@vscode/vsce/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=8" } }, "node_modules/@vscode/vsce/node_modules/jsonc-parser": { @@ -1453,6 +2255,19 @@ "node": ">=10" } }, + "node_modules/@vscode/vsce/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@vscode/vsce/node_modules/yazl": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", @@ -1485,6 +2300,23 @@ "node": ">= 14" } }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-colors": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", @@ -1495,6 +2327,22 @@ "node": ">=6" } }, + "node_modules/ansi-escapes": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", + "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-gray": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", @@ -1507,6 +2355,19 @@ "node": ">=0.10.0" } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1542,10 +2403,21 @@ } }, "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/argparse/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/arr-diff": { "version": "4.0.0", @@ -1574,6 +2446,16 @@ "node": ">=0.10.0" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/async-done": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", @@ -1589,11 +2471,19 @@ "node": ">= 0.10" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/azure-devops-node-api": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.2.0.tgz", - "integrity": "sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA==", + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", + "integrity": "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==", "dev": true, + "license": "MIT", "dependencies": { "tunnel": "0.0.6", "typed-rest-client": "^1.8.4" @@ -1634,6 +2524,22 @@ "node": ">=8" } }, + "node_modules/binaryextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", + "integrity": "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -1659,6 +2565,13 @@ "dev": true, "optional": true }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "dev": true, + "license": "BSD-2-Clause" + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1758,17 +2671,29 @@ "node": ">=8" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, + "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -1916,6 +2841,16 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/cockatiel": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", + "integrity": "sha512-gfrHV6ZPkquExvMh9IOkKsBzNDk6sDuZ6DdBGUBkvFnTCqCxzpuq48RySgP0AnaqQkw2zynOFj9yly6T1Q2G5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -1940,6 +2875,29 @@ "color-support": "bin.js" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1996,12 +2954,13 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -2058,23 +3017,6 @@ "node": ">=10" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -2094,7 +3036,17 @@ "object-keys": "^1.0.12" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" } }, "node_modules/detect-libc": { @@ -2179,6 +3131,21 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexify": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", @@ -2191,6 +3158,13 @@ "stream-shift": "^1.0.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -2200,6 +3174,30 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/editions": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.22.0.tgz", + "integrity": "sha512-UgGlf8IW75je7HZjNDpJdCv4cGJWIi6yumFdZ0R7A8/CIhQiWUjyGLCxdHpd8bmyD1gnkfUNK0oeOXqUS2cpfQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.15.0" + }, + "engines": { + "ecmascript": ">= es5", + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -2230,14 +3228,25 @@ "node": ">=6" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.4" + "license": "MIT", + "engines": { + "node": ">=18" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -2247,6 +3256,36 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, "engines": { "node": ">= 0.4" } @@ -2308,6 +3347,20 @@ "node": ">=0.8.0" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/events": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", @@ -2375,12 +3428,53 @@ "node": ">= 0.10" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-parser": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz", @@ -2404,6 +3498,16 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -2452,12 +3556,46 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fork-stream": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", "integrity": "sha512-Pqq5NnT78ehvUnAk/We/Jr22vSvanRlFTpAmQ88xBY/M1TlHe+P0ILuEyXS595ysdGfaj22634LBkGMA2GTcpA==", "dev": true }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -2503,21 +3641,28 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -2526,6 +3671,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -2631,13 +3790,35 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", "dev": true, + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.1.3" + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2731,23 +3912,12 @@ "node": ">=4" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -2755,11 +3925,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -2772,6 +3946,7 @@ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -2878,6 +4053,29 @@ ], "optional": true }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/index-to-position": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.1.0.tgz", + "integrity": "sha512-XPdx9Dq4t9Qk1mTMbWONJqU7boCoumEH7fRET37HX5+khDUl3J2W6PdALxhILYlIYx2amlwYcRPp28p0tSiojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2950,6 +4148,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3022,6 +4230,61 @@ "node": ">=0.10.0" } }, + "node_modules/istextorbinary": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", + "integrity": "sha512-5mbUj3SiZXCuRf9fT3ibzbSSEWiy63gFfksmGfdOzujPjW3k+z8WvIBxcJHBoQNlaZaiyB25deviif2+osLmLw==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "binaryextensions": "^6.11.0", + "editions": "^6.21.0", + "textextensions": "^6.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsbi": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-3.1.4.tgz", @@ -3034,6 +4297,13 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -3085,23 +4355,25 @@ } }, "node_modules/jsonwebtoken/node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "dev": true, + "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jsonwebtoken/node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "dev": true, + "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, @@ -3121,24 +4393,25 @@ } }, "node_modules/jwa": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", - "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "dev": true, + "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", - "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "dev": true, "license": "MIT", "dependencies": { - "jwa": "^2.0.0", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, @@ -3173,12 +4446,13 @@ } }, "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dev": true, + "license": "MIT", "dependencies": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "node_modules/lodash": { @@ -3193,6 +4467,13 @@ "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -3215,29 +4496,29 @@ } }, "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "bin": { - "markdown-it": "bin/markdown-it.js" + "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/markdown-it/node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } + "license": "Python-2.0" }, "node_modules/matcher": { "version": "3.0.0", @@ -3259,17 +4540,28 @@ "dev": true, "optional": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" }, "node_modules/merge-stream": { "version": "2.0.0", @@ -3277,6 +4569,30 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -3289,6 +4605,29 @@ "node": ">=4" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -3317,6 +4656,16 @@ "dev": true, "optional": true }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -3325,10 +4674,11 @@ "optional": true }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/mute-stream": { "version": "0.0.8", @@ -3397,6 +4747,106 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-sarif-builder": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/node-sarif-builder/-/node-sarif-builder-3.2.0.tgz", + "integrity": "sha512-kVIOdynrF2CRodHZeP/97Rh1syTUHBNiw17hUCIVhlhEsWlfJm19MuO56s4MdKbr22xWx6mzMnNAgXzVlIYM9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sarif": "^2.1.7", + "fs-extra": "^11.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-sarif-builder/node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/node-sarif-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/node-sarif-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -3440,10 +4890,11 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3511,6 +4962,57 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-json": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", + "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-node-version": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", @@ -3581,17 +5083,65 @@ "node": ">=8" } }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.1.tgz", + "integrity": "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA= sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "dev": true }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "devOptional": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -3650,6 +5200,16 @@ "node": ">=0.10.0" } }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -3715,13 +5275,24 @@ "once": "^1.3.1" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" @@ -3730,6 +5301,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -3754,20 +5346,99 @@ "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, - "bin": { - "rc": "cli.js" + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-config-loader": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.3.tgz", + "integrity": "sha512-kD7FqML7l800i6pS6pvLyIE2ncbk9Du8Q0gp/4hMPhJU6ZxApkoLcGD8ZeqgiAlfwZ6BlETq6qqe+12DUL207w==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "js-yaml": "^4.1.0", + "json5": "^2.2.2", + "require-from-string": "^2.0.2" + } + }, + "node_modules/rc-config-loader/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/rc-config-loader/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/read": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "dev": true, + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", + "node_modules/read-pkg/node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", "dev": true, - "dependencies": { - "mute-stream": "~0.0.4" - }, + "license": "MIT", "engines": { - "node": ">=0.8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/readable-stream": { @@ -3811,6 +5482,16 @@ "node": ">= 0.10" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -3829,6 +5510,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -3847,6 +5539,30 @@ "node": ">=8.0" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -3859,6 +5575,28 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true }, + "node_modules/secretlint": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/semaphore": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", @@ -3900,23 +5638,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3939,15 +5660,73 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -3956,6 +5735,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -4003,51 +5795,271 @@ "simple-concat": "^1.0.0" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.22", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.22.tgz", + "integrity": "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "dev": true, + "optional": true + }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true, + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, + "node_modules/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "devOptional": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/sprintf-js": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", - "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "optional": true + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/stoppable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", - "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, "engines": { - "node": ">=4", - "npm": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "devOptional": true, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, "node_modules/strip-bom": { @@ -4104,6 +6116,16 @@ "dev": true, "license": "MIT" }, + "node_modules/structured-source": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/structured-source/-/structured-source-4.0.0.tgz", + "integrity": "sha512-qGzRFNJDjFieQkl/sVOI2dUjHKRyL9dAJi2gCPGJLbJHBIkyOHxjuocpIEfbLioX+qSJpvbYdT49/YCdMznKxA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boundary": "^2.0.0" + } + }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -4128,10 +6150,90 @@ "node": ">=4" } }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, + "node_modules/supports-hyperlinks/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/tar-fs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", - "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "dev": true, "license": "MIT", "optional": true, @@ -4159,6 +6261,23 @@ "node": ">=6" } }, + "node_modules/terminal-link": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-4.0.0.tgz", + "integrity": "sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "supports-hyperlinks": "^3.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ternary-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-3.0.0.tgz", @@ -4181,6 +6300,29 @@ "readable-stream": "2 || 3" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/textextensions": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-6.11.0.tgz", + "integrity": "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -4206,10 +6348,11 @@ } }, "node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.4.tgz", + "integrity": "sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.14" } @@ -4320,6 +6463,7 @@ "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } @@ -4355,6 +6499,7 @@ "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", "dev": true, + "license": "MIT", "dependencies": { "qs": "^6.9.1", "tunnel": "0.0.6", @@ -4362,24 +6507,39 @@ } }, "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" }, "node_modules/underscore": { "version": "1.13.7", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/undici-types": { - "version": "6.20.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "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" }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", @@ -4416,6 +6576,30 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/version-range": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", + "integrity": "sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/vinyl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", @@ -4576,6 +6760,140 @@ "integrity": "sha512-r64Ea3glXY2RVzMeNxB+4J+0YHAVzUdV4cM5nHi4BBC2LvnO1pWFAIYKYuGcPElbg1/7eEiaPtZ/jzCjIUuGBg==", "dev": true }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/build/package.json b/build/package.json index 987beb0baf015..78c2e6143a1e6 100644 --- a/build/package.json +++ b/build/package.json @@ -19,26 +19,31 @@ "@types/glob": "^7.1.1", "@types/gulp": "^4.0.17", "@types/gulp-filter": "^3.0.32", + "@types/gulp-flatmap": "^1.0.0", "@types/gulp-gzip": "^0.0.31", "@types/gulp-json-editor": "^2.2.31", + "@types/gulp-plumber": "^0.0.37", "@types/gulp-rename": "^0.0.33", + "@types/gulp-replace": "^0.0.31", "@types/gulp-sort": "^2.0.4", "@types/gulp-sourcemaps": "^0.0.32", "@types/jws": "^3.2.10", + "@types/lazy.js": "^0.5.9", "@types/mime": "0.0.29", "@types/minimatch": "^3.0.3", "@types/minimist": "^1.2.1", - "@types/mocha": "^9.1.1", - "@types/node": "22.x", + "@types/node": "^22.18.10", + "@types/p-all": "^1.0.0", "@types/pump": "^1.0.1", "@types/rimraf": "^2.0.4", "@types/through": "^0.0.29", "@types/through2": "^2.0.36", + "@types/vinyl": "^2.0.12", "@types/workerpool": "^6.4.0", "@types/xml2js": "0.0.33", - "@vscode/iconv-lite-umd": "0.7.0", + "@vscode/iconv-lite-umd": "0.7.1", "@vscode/ripgrep": "^1.15.13", - "@vscode/vsce": "2.20.1", + "@vscode/vsce": "3.6.1", "ansi-colors": "^3.2.3", "byline": "^5.0.0", "debug": "^4.3.2", @@ -47,7 +52,7 @@ "gulp-merge-json": "^2.1.1", "gulp-sort": "^2.0.0", "jsonc-parser": "^2.3.0", - "jws": "^4.0.0", + "jws": "^4.0.1", "mime": "^1.4.1", "source-map": "0.6.1", "ternary-stream": "^3.0.0", @@ -57,14 +62,21 @@ "workerpool": "^6.4.0", "yauzl": "^2.10.0" }, - "type": "commonjs", + "type": "module", "scripts": { - "compile": "../node_modules/.bin/tsc -p tsconfig.build.json", - "watch": "../node_modules/.bin/tsc -p tsconfig.build.json --watch", - "npmCheckJs": "../node_modules/.bin/tsc --noEmit" + "copy-policy-dto": "node lib/policies/copyPolicyDto.ts", + "pretypecheck": "npm run copy-policy-dto", + "typecheck": "cd .. && npx tsgo --project build/tsconfig.json", + "watch": "npm run typecheck -- --watch", + "test": "mocha --ui tdd 'lib/**/*.test.ts'" }, "optionalDependencies": { "tree-sitter-typescript": "^0.23.2", "vscode-gulp-watch": "^5.0.3" + }, + "overrides": { + "path-scurry": { + "lru-cache": "11.2.1" + } } } diff --git a/build/setup-npm-registry.js b/build/setup-npm-registry.js deleted file mode 100644 index 24c850dbb8480..0000000000000 --- a/build/setup-npm-registry.js +++ /dev/null @@ -1,41 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -const fs = require('fs').promises; -const path = require('path'); - -async function* getPackageLockFiles(dir) { - const files = await fs.readdir(dir); - - for (const file of files) { - const fullPath = path.join(dir, file); - const stat = await fs.stat(fullPath); - - if (stat.isDirectory()) { - yield* getPackageLockFiles(fullPath); - } else if (file === 'package-lock.json') { - yield fullPath; - } - } -} - -async function setup(url, file) { - let contents = await fs.readFile(file, 'utf8'); - contents = contents.replace(/https:\/\/registry\.[^.]+\.com\//g, url); - await fs.writeFile(file, contents); -} - -async function main(url, dir) { - const root = dir ?? process.cwd(); - - for await (const file of getPackageLockFiles(root)) { - console.log(`Enabling custom NPM registry: ${path.relative(root, file)}`); - await setup(url, file); - } -} - -main(process.argv[2], process.argv[3]); diff --git a/build/setup-npm-registry.ts b/build/setup-npm-registry.ts new file mode 100644 index 0000000000000..670c3e339db0f --- /dev/null +++ b/build/setup-npm-registry.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { promises as fs } from 'fs'; +import path from 'path'; + +/** + * Recursively find all package-lock.json files in a directory + */ +async function* getPackageLockFiles(dir: string): AsyncGenerator { + const files = await fs.readdir(dir); + + for (const file of files) { + const fullPath = path.join(dir, file); + const stat = await fs.stat(fullPath); + + if (stat.isDirectory()) { + yield* getPackageLockFiles(fullPath); + } else if (file === 'package-lock.json') { + yield fullPath; + } + } +} + +/** + * Replace the registry URL in a package-lock.json file + */ +async function setup(url: string, file: string): Promise { + let contents = await fs.readFile(file, 'utf8'); + contents = contents.replace(/https:\/\/registry\.[^.]+\.org\//g, url); + await fs.writeFile(file, contents); +} + +/** + * Main function to set up custom NPM registry + */ +async function main(url: string, dir?: string): Promise { + const root = dir ?? process.cwd(); + + for await (const file of getPackageLockFiles(root)) { + console.log(`Enabling custom NPM registry: ${path.relative(root, file)}`); + await setup(url, file); + } +} + +main(process.argv[2], process.argv[3]); diff --git a/build/stylelint.js b/build/stylelint.js deleted file mode 100644 index 5b1668ea2e488..0000000000000 --- a/build/stylelint.js +++ /dev/null @@ -1,57 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -const es = require('event-stream'); -const vfs = require('vinyl-fs'); -const { stylelintFilter } = require('./filters'); -const { getVariableNameValidator } = require('./lib/stylelint/validateVariableNames'); - -module.exports = gulpstylelint; - -/** use regex on lines */ -function gulpstylelint(reporter) { - const variableValidator = getVariableNameValidator(); - let errorCount = 0; - return es.through(function (file) { - const lines = file.__lines || file.contents.toString('utf8').split(/\r\n|\r|\n/); - file.__lines = lines; - - lines.forEach((line, i) => { - variableValidator(line, unknownVariable => { - reporter(file.relative + '(' + (i + 1) + ',1): Unknown variable: ' + unknownVariable, true); - errorCount++; - }); - }); - - this.emit('data', file); - }, function () { - if (errorCount > 0) { - reporter('All valid variable names are in `build/lib/stylelint/vscode-known-variables.json`\nTo update that file, run `./scripts/test-documentation.sh|bat.`', false); - } - this.emit('end'); - } - ); -} - -function stylelint() { - return vfs - .src(stylelintFilter, { base: '.', follow: true, allowEmpty: true }) - .pipe(gulpstylelint((message, isError) => { - if (isError) { - console.error(message); - } else { - console.info(message); - } - })) - .pipe(es.through(function () { /* noop, important for the stream to end */ })); -} - -if (require.main === module) { - stylelint().on('error', (err) => { - console.error(); - console.error(err); - process.exit(1); - }); -} diff --git a/build/stylelint.ts b/build/stylelint.ts new file mode 100644 index 0000000000000..037fe110615c5 --- /dev/null +++ b/build/stylelint.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import es from 'event-stream'; +import vfs from 'vinyl-fs'; +import { stylelintFilter } from './filters.ts'; +import { getVariableNameValidator } from './lib/stylelint/validateVariableNames.ts'; + +interface FileWithLines { + __lines?: string[]; + relative: string; + contents: Buffer; +} + +type Reporter = (message: string, isError: boolean) => void; + +/** + * Stylelint gulpfile task + */ +export default function gulpstylelint(reporter: Reporter): NodeJS.ReadWriteStream { + const variableValidator = getVariableNameValidator(); + let errorCount = 0; + const monacoWorkbenchPattern = /\.monaco-workbench/; + const restrictedPathPattern = /^src[\/\\]vs[\/\\](base|platform|editor)[\/\\]/; + const layerCheckerDisablePattern = /\/\*\s*stylelint-disable\s+layer-checker\s*\*\//; + + return es.through(function (this, file: FileWithLines) { + const lines = file.__lines || file.contents.toString('utf8').split(/\r\n|\r|\n/); + file.__lines = lines; + + const isRestrictedPath = restrictedPathPattern.test(file.relative); + + // Check if layer-checker is disabled for the entire file + const isLayerCheckerDisabled = lines.some(line => layerCheckerDisablePattern.test(line)); + + lines.forEach((line, i) => { + variableValidator(line, (unknownVariable: string) => { + reporter(file.relative + '(' + (i + 1) + ',1): Unknown variable: ' + unknownVariable, true); + errorCount++; + }); + + if (isRestrictedPath && !isLayerCheckerDisabled && monacoWorkbenchPattern.test(line)) { + reporter(file.relative + '(' + (i + 1) + ',1): The class .monaco-workbench cannot be used in files under src/vs/{base,platform,editor} because only src/vs/workbench applies it', true); + errorCount++; + } + }); + + this.emit('data', file); + }, function () { + if (errorCount > 0) { + reporter('All valid variable names are in `build/lib/stylelint/vscode-known-variables.json`\nTo update that file, run `./scripts/test-documentation.sh|bat.`', false); + } + this.emit('end'); + }); +} + +function stylelint(): NodeJS.ReadWriteStream { + return vfs + .src(Array.from(stylelintFilter), { base: '.', follow: true, allowEmpty: true }) + .pipe(gulpstylelint((message, isError) => { + if (isError) { + console.error(message); + } else { + console.info(message); + } + })) + .pipe(es.through(function () { /* noop, important for the stream to end */ })); +} + +if (import.meta.main) { + stylelint().on('error', (err: Error) => { + console.error(); + console.error(err); + process.exit(1); + }); +} diff --git a/build/tsconfig.build.json b/build/tsconfig.build.json deleted file mode 100644 index 4534420208f32..0000000000000 --- a/build/tsconfig.build.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "allowJs": false, - "checkJs": false, - "noEmit": false, - "skipLibCheck": true - }, - "include": [ - "**/*.ts" - ], - "exclude": [ - "lib/eslint-plugin-vscode/**/*" - ] -} diff --git a/build/tsconfig.json b/build/tsconfig.json index ab72dda392ae6..383d5342c044a 100644 --- a/build/tsconfig.json +++ b/build/tsconfig.json @@ -5,28 +5,22 @@ "ES2024" ], "module": "nodenext", - "alwaysStrict": true, - "removeComments": false, - "preserveConstEnums": true, - "sourceMap": true, + "noEmit": true, + "erasableSyntaxOnly": true, + "verbatimModuleSyntax": true, + "allowImportingTsExtensions": true, "resolveJsonModule": true, - // enable JavaScript type checking for the language service - // use the tsconfig.build.json for compiling which disable JavaScript - // type checking so that JavaScript file are not transpiled - "allowJs": true, + "skipLibCheck": true, "strict": true, "exactOptionalPropertyTypes": false, "useUnknownInCatchVariables": false, "noUnusedLocals": true, - "noUnusedParameters": true, - "newLine": "lf", - "noEmit": true + "noUnusedParameters": true }, - "include": [ - "**/*.ts", - "**/*.js" - ], "exclude": [ - "node_modules/**" + "node_modules/**", + "monaco-editor-playground/**", + "builtin/**", + "vite/**" ] } diff --git a/build/vite/index-workbench.ts b/build/vite/index-workbench.ts new file mode 100644 index 0000000000000..e237f661f5d7c --- /dev/null +++ b/build/vite/index-workbench.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import '../../src/vs/code/browser/workbench/workbench'; +import './setup-dev'; + diff --git a/build/vite/index.html b/build/vite/index.html new file mode 100644 index 0000000000000..c3a0e36b8ef5c --- /dev/null +++ b/build/vite/index.html @@ -0,0 +1,9 @@ + + + + +
+

Use the Playground Launch Config for a better dev experience

+ + + diff --git a/build/vite/index.ts b/build/vite/index.ts new file mode 100644 index 0000000000000..b852612bc66ef --- /dev/null +++ b/build/vite/index.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/// +/* eslint-disable local/code-no-standalone-editor */ + +export * from '../../src/vs/editor/editor.main'; +import './style.css'; +import * as monaco from '../../src/vs/editor/editor.main'; + +globalThis.monaco = monaco; +const root = document.getElementById('sampleContent'); +if (root) { + const d = monaco.editor.createDiffEditor(root); + + d.setModel({ + modified: monaco.editor.createModel(`hello world`), + original: monaco.editor.createModel(`hello monaco`), + }); +} diff --git a/build/vite/package-lock.json b/build/vite/package-lock.json new file mode 100644 index 0000000000000..4fd63116305fa --- /dev/null +++ b/build/vite/package-lock.json @@ -0,0 +1,1043 @@ +{ + "name": "@vscode/sample-source", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@vscode/sample-source", + "version": "0.0.0", + "devDependencies": { + "vite": "^7.1.11" + } + }, + "../lib": { + "name": "monaco-editor-core", + "version": "0.0.0", + "extraneous": true, + "license": "MIT", + "dependencies": { + "postcss-copy": "^7.1.0", + "postcss-copy-assets": "^0.3.1", + "rollup": "^4.35.0", + "rollup-plugin-esbuild": "^6.2.1", + "rollup-plugin-lib-style": "^2.3.2", + "rollup-plugin-postcss": "^4.0.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", + "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", + "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", + "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", + "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", + "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", + "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", + "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", + "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", + "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", + "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", + "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", + "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", + "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", + "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", + "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", + "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", + "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", + "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", + "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", + "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.49.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz", + "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.49.0", + "@rollup/rollup-android-arm64": "4.49.0", + "@rollup/rollup-darwin-arm64": "4.49.0", + "@rollup/rollup-darwin-x64": "4.49.0", + "@rollup/rollup-freebsd-arm64": "4.49.0", + "@rollup/rollup-freebsd-x64": "4.49.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", + "@rollup/rollup-linux-arm-musleabihf": "4.49.0", + "@rollup/rollup-linux-arm64-gnu": "4.49.0", + "@rollup/rollup-linux-arm64-musl": "4.49.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", + "@rollup/rollup-linux-ppc64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-gnu": "4.49.0", + "@rollup/rollup-linux-riscv64-musl": "4.49.0", + "@rollup/rollup-linux-s390x-gnu": "4.49.0", + "@rollup/rollup-linux-x64-gnu": "4.49.0", + "@rollup/rollup-linux-x64-musl": "4.49.0", + "@rollup/rollup-win32-arm64-msvc": "4.49.0", + "@rollup/rollup-win32-ia32-msvc": "4.49.0", + "@rollup/rollup-win32-x64-msvc": "4.49.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/vite": { + "version": "7.1.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", + "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/build/vite/package.json b/build/vite/package.json new file mode 100644 index 0000000000000..ea7b609e28078 --- /dev/null +++ b/build/vite/package.json @@ -0,0 +1,14 @@ +{ + "name": "@vscode/sample-source", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^7.1.11" + } +} diff --git a/build/vite/rollup-url-to-module-plugin/index.mjs b/build/vite/rollup-url-to-module-plugin/index.mjs new file mode 100644 index 0000000000000..8a0168bd4acbf --- /dev/null +++ b/build/vite/rollup-url-to-module-plugin/index.mjs @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * @type {() => import('rollup').Plugin} +*/ +export function urlToEsmPlugin() { + return { + name: 'import-meta-url', + async transform(code, id) { + if (this.environment?.mode === 'dev') { + return; + } + + // Look for `new URL(..., import.meta.url)` patterns. + const regex = /new\s+URL\s*\(\s*(['"`])(.*?)\1\s*,\s*import\.meta\.url\s*\)?/g; + + let match; + let modified = false; + let result = code; + let offset = 0; + + while ((match = regex.exec(code)) !== null) { + let path = match[2]; + + if (!path.startsWith('.') && !path.startsWith('/')) { + path = `./${path}`; + } + const resolved = await this.resolve(path, id); + + if (!resolved) { + continue; + } + + // Add the file as an entry point + const refId = this.emitFile({ + type: 'chunk', + id: resolved.id, + }); + + const start = match.index; + const end = start + match[0].length; + + const replacement = `import.meta.ROLLUP_FILE_URL_OBJ_${refId}`; + + result = result.slice(0, start + offset) + replacement + result.slice(end + offset); + offset += replacement.length - (end - start); + modified = true; + } + + if (!modified) { + return null; + } + + return { + code: result, + map: null + }; + } + }; +} diff --git a/build/vite/setup-dev.ts b/build/vite/setup-dev.ts new file mode 100644 index 0000000000000..c1df486108260 --- /dev/null +++ b/build/vite/setup-dev.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/// + +import { enableHotReload } from '../../src/vs/base/common/hotReload.ts'; +import { InstantiationType, registerSingleton } from '../../src/vs/platform/instantiation/common/extensions.ts'; +import { IWebWorkerService } from '../../src/vs/platform/webWorker/browser/webWorkerService.ts'; +// eslint-disable-next-line local/code-no-standalone-editor +import { StandaloneWebWorkerService } from '../../src/vs/editor/standalone/browser/services/standaloneWebWorkerService.ts'; + +enableHotReload(); +registerSingleton(IWebWorkerService, StandaloneWebWorkerService, InstantiationType.Eager); + +globalThis._VSCODE_DISABLE_CSS_IMPORT_MAP = true; +globalThis._VSCODE_USE_RELATIVE_IMPORTS = true; diff --git a/build/vite/style.css b/build/vite/style.css new file mode 100644 index 0000000000000..b9573061e51fa --- /dev/null +++ b/build/vite/style.css @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +#sampleContent { + height: 400px; + border: 1px solid black; +} diff --git a/build/vite/tsconfig.json b/build/vite/tsconfig.json new file mode 100644 index 0000000000000..454dc14491fe5 --- /dev/null +++ b/build/vite/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "noEmit": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "experimentalDecorators": true, + }, + "include": ["**/*.ts"] +} diff --git a/build/vite/vite.config.ts b/build/vite/vite.config.ts new file mode 100644 index 0000000000000..5736a474d6bab --- /dev/null +++ b/build/vite/vite.config.ts @@ -0,0 +1,193 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createLogger, defineConfig, Plugin } from 'vite'; +import path, { join } from 'path'; +/// @ts-ignore +import { urlToEsmPlugin } from './rollup-url-to-module-plugin/index.mjs'; +import { statSync } from 'fs'; +import { pathToFileURL } from 'url'; + +function injectBuiltinExtensionsPlugin(): Plugin { + let builtinExtensionsCache: unknown[] | null = null; + + function replaceAllOccurrences(str: string, search: string, replace: string): string { + return str.split(search).join(replace); + } + + async function loadBuiltinExtensions() { + if (!builtinExtensionsCache) { + builtinExtensionsCache = await getScannedBuiltinExtensions(path.resolve(__dirname, '../../')); + console.log(`Found ${builtinExtensionsCache!.length} built-in extensions.`); + } + return builtinExtensionsCache; + } + + function asJSON(value: unknown): string { + return escapeHtmlByReplacingCharacters(JSON.stringify(value)); + } + + function escapeHtmlByReplacingCharacters(str: string) { + if (typeof str !== 'string') { + return ''; + } + + const escapeCharacter = (match: string) => { + switch (match) { + case '&': return '&'; + case '<': return '<'; + case '>': return '>'; + case '"': return '"'; + case '\'': return '''; + case '`': return '`'; + default: return match; + } + }; + + return str.replace(/[&<>"'`]/g, escapeCharacter); + } + + const prebuiltExtensionsLocation = '.build/builtInExtensions'; + async function getScannedBuiltinExtensions(vsCodeDevLocation: string) { + // use the build utility as to not duplicate the code + const extensionsUtil = await import(pathToFileURL(path.join(vsCodeDevLocation, 'build', 'lib', 'extensions.js')).toString()); + const localExtensions = extensionsUtil.scanBuiltinExtensions(path.join(vsCodeDevLocation, 'extensions')); + const prebuiltExtensions = extensionsUtil.scanBuiltinExtensions(path.join(vsCodeDevLocation, prebuiltExtensionsLocation)); + for (const ext of localExtensions) { + let browserMain = ext.packageJSON.browser; + if (browserMain) { + if (!browserMain.endsWith('.js')) { + browserMain = browserMain + '.js'; + } + const browserMainLocation = path.join(vsCodeDevLocation, 'extensions', ext.extensionPath, browserMain); + if (!fileExists(browserMainLocation)) { + console.log(`${browserMainLocation} not found. Make sure all extensions are compiled (use 'yarn watch-web').`); + } + } + } + return localExtensions.concat(prebuiltExtensions); + } + + function fileExists(path: string): boolean { + try { + return statSync(path).isFile(); + } catch (err) { + return false; + } + } + + return { + name: 'inject-builtin-extensions', + transformIndexHtml: { + order: 'pre', + async handler(html) { + const search = '{{WORKBENCH_BUILTIN_EXTENSIONS}}'; + if (html.indexOf(search) === -1) { + return html; + } + + const extensions = await loadBuiltinExtensions(); + const h = replaceAllOccurrences(html, search, asJSON(extensions)); + return h; + } + } + }; +} + +function createHotClassSupport(): Plugin { + return { + name: 'createHotClassSupport', + transform: { + order: 'pre', + handler: (code, id) => { + if (id.endsWith('.ts')) { + let needsHMRAccept = false; + const hasCreateHotClass = code.includes('createHotClass'); + const hasDomWidget = code.includes('DomWidget'); + + if (!hasCreateHotClass && !hasDomWidget) { + return undefined; + } + + if (hasCreateHotClass) { + needsHMRAccept = true; + } + + if (hasDomWidget) { + const matches = code.matchAll(/class\s+([a-zA-Z0-9_]+)\s+extends\s+DomWidget/g); + /// @ts-ignore + for (const match of matches) { + const className = match[1]; + code = code + `\n${className}.registerWidgetHotReplacement(${JSON.stringify(id + '#' + className)});`; + needsHMRAccept = true; + } + } + + if (needsHMRAccept) { + code = code + `\n +if (import.meta.hot) { + import.meta.hot.accept(); +}`; + } + return code; + } + return undefined; + }, + } + }; +} + +const logger = createLogger(); +const loggerWarn = logger.warn; + +logger.warn = (msg, options) => { + // amdX and the baseUrl code cannot be analyzed by vite. + // However, they are not needed, so it is okay to silence the warning. + if (msg.indexOf('vs/amdX.ts') !== -1) { + return; + } + if (msg.indexOf('await import(new URL(`vs/workbench/workbench.desktop.main.js`, baseUrl).href)') !== -1) { + return; + } + if (msg.indexOf('const result2 = await import(workbenchUrl);') !== -1) { + return; + } + + // See https://github.com/microsoft/vscode/issues/278153 + if (msg.indexOf('marked.esm.js.map') !== -1 || msg.indexOf('purify.es.mjs.map') !== -1) { + return; + } + + loggerWarn(msg, options); +}; + +export default defineConfig({ + plugins: [ + urlToEsmPlugin(), + injectBuiltinExtensionsPlugin(), + createHotClassSupport() + ], + customLogger: logger, + esbuild: { + tsconfigRaw: { + compilerOptions: { + experimentalDecorators: true, + } + } + }, + root: '../..', // To support /out/... paths + server: { + cors: true, + port: 5199, + origin: 'http://localhost:5199', + fs: { + allow: [ + // To allow loading from sources, not needed when loading monaco-editor from npm package + /// @ts-ignore + join(import.meta.dirname, '../../../') + ] + } + } +}); diff --git a/build/vite/workbench-electron.ts b/build/vite/workbench-electron.ts new file mode 100644 index 0000000000000..49578ca494897 --- /dev/null +++ b/build/vite/workbench-electron.ts @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import './setup-dev'; +import '../../src/vs/code/electron-browser/workbench/workbench'; + diff --git a/build/vite/workbench-vite-electron.html b/build/vite/workbench-vite-electron.html new file mode 100644 index 0000000000000..87019c6c01a3e --- /dev/null +++ b/build/vite/workbench-vite-electron.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/build/vite/workbench-vite.html b/build/vite/workbench-vite.html new file mode 100644 index 0000000000000..99ed4e754154d --- /dev/null +++ b/build/vite/workbench-vite.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/build/win32/Cargo.lock b/build/win32/Cargo.lock index 11558ceaf045c..d35c41e4098f9 100644 --- a/build/win32/Cargo.lock +++ b/build/win32/Cargo.lock @@ -8,6 +8,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + [[package]] name = "byteorder" version = "1.4.3" @@ -76,6 +82,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "getrandom" version = "0.2.7" @@ -84,7 +106,19 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", ] [[package]] @@ -95,13 +129,14 @@ checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "inno_updater" -version = "0.15.0" +version = "0.18.2" dependencies = [ "byteorder", "crc", "slog", "slog-async", "slog-term", + "tempfile", "windows-sys 0.42.0", ] @@ -124,9 +159,15 @@ checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "num_threads" @@ -139,9 +180,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.12.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "proc-macro2" @@ -161,13 +202,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "redox_syscall" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -176,11 +223,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom", + "getrandom 0.2.7", "redox_syscall", "thiserror", ] +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "rustversion" version = "1.0.7" @@ -235,6 +295,19 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "term" version = "0.7.0" @@ -305,6 +378,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "winapi" version = "0.3.9" @@ -456,3 +538,12 @@ name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] diff --git a/build/win32/Cargo.toml b/build/win32/Cargo.toml index 0724862e2735e..40e1a7a60fddc 100644 --- a/build/win32/Cargo.toml +++ b/build/win32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "inno_updater" -version = "0.15.0" +version = "0.18.2" authors = ["Microsoft "] build = "build.rs" @@ -10,6 +10,7 @@ crc = "3.0.1" slog = "2.7.0" slog-async = "2.7.0" slog-term = "2.9.1" +tempfile = "3.5.0" [target.'cfg(windows)'.dependencies.windows-sys] version = "0.42" diff --git a/build/win32/code-insider.iss b/build/win32/code-insider.iss new file mode 100644 index 0000000000000..2cbf252779bf0 --- /dev/null +++ b/build/win32/code-insider.iss @@ -0,0 +1,1740 @@ +#define RootLicenseFileName FileExists(RepoDir + '\LICENSE.rtf') ? 'LICENSE.rtf' : 'LICENSE.txt' +#define LocalizedLanguageFile(Language = "") \ + DirExists(RepoDir + "\licenses") && Language != "" \ + ? ('; LicenseFile: "' + RepoDir + '\licenses\LICENSE-' + Language + '.rtf"') \ + : '; LicenseFile: "' + RepoDir + '\' + RootLicenseFileName + '"' + +[Setup] +AppId={#AppId} +AppName={#NameLong} +AppVerName={#NameVersion} +AppPublisher=Microsoft Corporation +AppPublisherURL=https://code.visualstudio.com/ +AppSupportURL=https://code.visualstudio.com/ +AppUpdatesURL=https://code.visualstudio.com/ +DefaultGroupName={#NameLong} +AllowNoIcons=yes +OutputDir={#OutputDir} +OutputBaseFilename=VSCodeSetup +Compression=lzma +SolidCompression=yes +AppMutex={code:GetAppMutex} +SetupMutex={#AppMutex}setup +WizardImageFile="{#RepoDir}\resources\win32\inno-big-100.bmp,{#RepoDir}\resources\win32\inno-big-125.bmp,{#RepoDir}\resources\win32\inno-big-150.bmp,{#RepoDir}\resources\win32\inno-big-175.bmp,{#RepoDir}\resources\win32\inno-big-200.bmp,{#RepoDir}\resources\win32\inno-big-225.bmp,{#RepoDir}\resources\win32\inno-big-250.bmp" +WizardSmallImageFile="{#RepoDir}\resources\win32\inno-small-100.bmp,{#RepoDir}\resources\win32\inno-small-125.bmp,{#RepoDir}\resources\win32\inno-small-150.bmp,{#RepoDir}\resources\win32\inno-small-175.bmp,{#RepoDir}\resources\win32\inno-small-200.bmp,{#RepoDir}\resources\win32\inno-small-225.bmp,{#RepoDir}\resources\win32\inno-small-250.bmp" +SetupIconFile={#RepoDir}\resources\win32\code.ico +UninstallDisplayIcon={app}\{#ExeBasename}.exe +ChangesEnvironment=true +ChangesAssociations=true +MinVersion=10.0 +SourceDir={#SourceDir} +AppVersion={#Version} +VersionInfoVersion={#RawVersion} +ShowLanguageDialog=auto +ArchitecturesAllowed={#ArchitecturesAllowed} +ArchitecturesInstallIn64BitMode={#ArchitecturesInstallIn64BitMode} +WizardStyle=modern + +// We've seen an uptick on broken installations from updates which were unable +// to shutdown VS Code. We rely on the fact that the update signals +// that VS Code is ready to be shutdown, so we're good to use `force` here. +CloseApplications=force + +#ifdef Sign +SignTool=esrp +#endif + +#if "user" == InstallTarget +DefaultDirName={userpf}\{#DirName} +PrivilegesRequired=lowest +#else +DefaultDirName={pf}\{#DirName} +#endif + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl,{#RepoDir}\build\win32\i18n\messages.en.isl" {#LocalizedLanguageFile} +Name: "german"; MessagesFile: "compiler:Languages\German.isl,{#RepoDir}\build\win32\i18n\messages.de.isl" {#LocalizedLanguageFile("deu")} +Name: "spanish"; MessagesFile: "compiler:Languages\Spanish.isl,{#RepoDir}\build\win32\i18n\messages.es.isl" {#LocalizedLanguageFile("esp")} +Name: "french"; MessagesFile: "compiler:Languages\French.isl,{#RepoDir}\build\win32\i18n\messages.fr.isl" {#LocalizedLanguageFile("fra")} +Name: "italian"; MessagesFile: "compiler:Languages\Italian.isl,{#RepoDir}\build\win32\i18n\messages.it.isl" {#LocalizedLanguageFile("ita")} +Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl,{#RepoDir}\build\win32\i18n\messages.ja.isl" {#LocalizedLanguageFile("jpn")} +Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl,{#RepoDir}\build\win32\i18n\messages.ru.isl" {#LocalizedLanguageFile("rus")} +Name: "korean"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.ko.isl,{#RepoDir}\build\win32\i18n\messages.ko.isl" {#LocalizedLanguageFile("kor")} +Name: "simplifiedChinese"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.zh-cn.isl,{#RepoDir}\build\win32\i18n\messages.zh-cn.isl" {#LocalizedLanguageFile("chs")} +Name: "traditionalChinese"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.zh-tw.isl,{#RepoDir}\build\win32\i18n\messages.zh-tw.isl" {#LocalizedLanguageFile("cht")} +Name: "brazilianPortuguese"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl,{#RepoDir}\build\win32\i18n\messages.pt-br.isl" {#LocalizedLanguageFile("ptb")} +Name: "hungarian"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.hu.isl,{#RepoDir}\build\win32\i18n\messages.hu.isl" {#LocalizedLanguageFile("hun")} +Name: "turkish"; MessagesFile: "compiler:Languages\Turkish.isl,{#RepoDir}\build\win32\i18n\messages.tr.isl" {#LocalizedLanguageFile("trk")} + +[InstallDelete] +Type: filesandordirs; Name: "{app}\{#VersionedResourcesFolder}\resources\app\out"; Check: IsNotBackgroundUpdate +Type: filesandordirs; Name: "{app}\{#VersionedResourcesFolder}\resources\app\plugins"; Check: IsNotBackgroundUpdate +Type: filesandordirs; Name: "{app}\{#VersionedResourcesFolder}\resources\app\extensions"; Check: IsNotBackgroundUpdate +Type: filesandordirs; Name: "{app}\{#VersionedResourcesFolder}\resources\app\node_modules"; Check: IsNotBackgroundUpdate +Type: filesandordirs; Name: "{app}\{#VersionedResourcesFolder}\resources\app\node_modules.asar.unpacked"; Check: IsNotBackgroundUpdate +Type: files; Name: "{app}\{#VersionedResourcesFolder}\resources\app\node_modules.asar"; Check: IsNotBackgroundUpdate +Type: files; Name: "{app}\{#VersionedResourcesFolder}\resources\app\Credits_45.0.2454.85.html"; Check: IsNotBackgroundUpdate + +[UninstallDelete] +Type: filesandordirs; Name: "{app}\_" +Type: filesandordirs; Name: "{app}\bin" +Type: files; Name: "{app}\old_*" +Type: files; Name: "{app}\new_*" +Type: files; Name: "{app}\updating_version" + +[Tasks] +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked +Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1 +Name: "addcontextmenufiles"; Description: "{cm:AddContextMenuFiles,{#NameShort}}"; GroupDescription: "{cm:Other}"; Flags: unchecked +Name: "addcontextmenufolders"; Description: "{cm:AddContextMenuFolders,{#NameShort}}"; GroupDescription: "{cm:Other}"; Flags: unchecked; Check: not (IsWindows11OrLater and QualityIsInsiders) +Name: "associatewithfiles"; Description: "{cm:AssociateWithFiles,{#NameShort}}"; GroupDescription: "{cm:Other}" +Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}" +Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent + +[Dirs] +Name: "{app}"; AfterInstall: DisableAppDirInheritance + +[Files] +Source: "*"; Excludes: "\CodeSignSummary*.md,\tools,\tools\*,\policies,\policies\*,\appx,\appx\*,\resources\app\product.json,\{#ExeBasename}.exe,\{#ExeBasename}.VisualElementsManifest.xml,\bin,\bin\*"; DestDir: "{code:GetDestDir}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "{#ExeBasename}.exe"; DestDir: "{code:GetDestDir}"; DestName: "{code:GetExeBasename}"; Flags: ignoreversion +Source: "{#ExeBasename}.VisualElementsManifest.xml"; DestDir: "{code:GetDestDir}"; DestName: "{code:GetVisualElementsManifest}"; Flags: ignoreversion +Source: "tools\*"; DestDir: "{code:GetDestDir}\{#VersionedResourcesFolder}\tools"; Flags: ignoreversion +Source: "policies\*"; DestDir: "{code:GetDestDir}\{#VersionedResourcesFolder}\policies"; Flags: ignoreversion skipifsourcedoesntexist +Source: "bin\{#TunnelApplicationName}.exe"; DestDir: "{code:GetDestDir}\bin"; DestName: "{code:GetBinDirTunnelApplicationFilename}"; Flags: ignoreversion skipifsourcedoesntexist +Source: "bin\{#ApplicationName}.cmd"; DestDir: "{code:GetDestDir}\bin"; DestName: "{code:GetBinDirApplicationCmdFilename}"; Flags: ignoreversion +Source: "bin\{#ApplicationName}"; DestDir: "{code:GetDestDir}\bin"; DestName: "{code:GetBinDirApplicationFilename}"; Flags: ignoreversion +Source: "{#ProductJsonPath}"; DestDir: "{code:GetDestDir}\{#VersionedResourcesFolder}\resources\app"; Flags: ignoreversion +#ifdef AppxPackageName +#if "user" == InstallTarget +Source: "appx\{#AppxPackage}"; DestDir: "{code:GetDestDir}\{#VersionedResourcesFolder}\appx"; BeforeInstall: RemoveAppxPackage; Flags: ignoreversion; Check: IsWindows11OrLater +Source: "appx\{#AppxPackageDll}"; DestDir: "{code:GetDestDir}\{#VersionedResourcesFolder}\appx"; AfterInstall: AddAppxPackage; Flags: ignoreversion; Check: IsWindows11OrLater +#endif +#endif + +[Icons] +Name: "{group}\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; AppUserModelID: "{#AppUserId}" +Name: "{autodesktop}\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; Tasks: desktopicon; AppUserModelID: "{#AppUserId}" +Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#NameLong}"; Filename: "{app}\{#ExeBasename}.exe"; Tasks: quicklaunchicon; AppUserModelID: "{#AppUserId}" + +[Run] +Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong}}"; Tasks: runcode; Flags: nowait postinstall; Check: ShouldRunAfterUpdate +Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong}}"; Flags: nowait postinstall; Check: WizardNotSilent + +[Registry] +#if "user" == InstallTarget +#define SoftwareClassesRootKey "HKCU" +#else +#define SoftwareClassesRootKey "HKLM" +#endif + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ascx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ascx\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.ascx"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ascx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,ASCX}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ascx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ascx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\xml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ascx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ascx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.asp\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.asp\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.asp"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.asp"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,ASP}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.asp"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.asp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.asp\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.asp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.aspx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.aspx\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.aspx"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.aspx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,ASPX}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.aspx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.aspx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.aspx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.aspx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.bash"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Bash}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash_login\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash_login\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.bash_login"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_login"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Bash Login}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_login"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_login\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_login\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_login\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash_logout\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash_logout\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.bash_logout"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_logout"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Bash Logout}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_logout"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_logout\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_logout\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_logout\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash_profile\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bash_profile\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.bash_profile"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_profile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Bash Profile}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_profile"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_profile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_profile\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bash_profile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bashrc\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bashrc\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.bashrc"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bashrc"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Bash RC}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bashrc"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bashrc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bashrc\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bashrc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bib\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bib\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.bib"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bib"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,BibTeX}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bib"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bib\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bib\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bib\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bowerrc\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.bowerrc\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.bowerrc"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bowerrc"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Bower RC}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bowerrc"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bowerrc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\bower.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bowerrc\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.bowerrc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.c++\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.c++\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.c++"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c++"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c++"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c++\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c++\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.c\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.c\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.c"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\c.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.c\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cc\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cc\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.cc"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cc"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cc"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cc\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cfg\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cfg\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.cfg"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cfg"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Configuration}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cfg"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cfg\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cfg\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cfg\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cjs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cjs\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.cjs"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cjs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JavaScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cjs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cjs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\javascript.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cjs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cjs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.clj\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.clj\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.clj"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clj"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Clojure}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clj"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clj\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clj\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clj\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cljs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cljs\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.cljs"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,ClojureScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cljx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cljx\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.cljx"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,CLJX}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cljx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.clojure\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.clojure\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.clojure"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clojure"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Clojure}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clojure"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clojure\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clojure\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.clojure\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cls\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cls\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.cls"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cls"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,LaTeX}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cls"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cls\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cls\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cls\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.code-workspace\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.code-workspace\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.code-workspace"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Code Workspace}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cmake\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cmake\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.cmake"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cmake"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,CMake}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cmake"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cmake\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cmake\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cmake\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.coffee\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.coffee\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.coffee"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.coffee"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,CoffeeScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.coffee"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.coffee\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.coffee\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.coffee\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.config\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.config\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.config"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.config"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Configuration}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.config"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.config\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.config\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.config\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.containerfile\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.containerfile\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.containerfile"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.containerfile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Containerfile}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.containerfile"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.containerfile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.containerfile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cpp\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cpp\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.cpp"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cpp"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cpp"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cpp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cpp\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cpp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cs\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.cs"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C#}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\csharp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cshtml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cshtml\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.cshtml"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cshtml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,CSHTML}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cshtml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cshtml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cshtml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cshtml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.csproj\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.csproj\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.csproj"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csproj"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C# Project}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csproj"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csproj\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\xml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csproj\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csproj\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.css\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.css\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.css"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.css"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,CSS}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.css"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.css\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\css.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.css\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.css\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.csv\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.csv\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.csv"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csv"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Comma Separated Values}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csv"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csv\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csv\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csv\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.csx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.csx\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.csx"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C# Script}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\csharp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.csx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ctp\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ctp\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.ctp"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ctp"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,CakePHP Template}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ctp"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ctp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ctp\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ctp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cxx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.cxx\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.cxx"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cxx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cxx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cxx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cxx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cxx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dart\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dart\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.dart"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dart"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Dart}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dart"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dart\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dart\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dart\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.diff\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.diff\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.diff"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.diff"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Diff}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.diff"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.diff\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.diff\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.diff\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dockerfile\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dockerfile\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.dockerfile"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dockerfile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Dockerfile}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dockerfile"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dockerfile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dockerfile\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dockerfile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dot\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dot\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.dot"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dot"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Dot}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dot"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dot\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dot\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dot\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dtd\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.dtd\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.dtd"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dtd"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Document Type Definition}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dtd"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dtd\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\xml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dtd\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.dtd\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.editorconfig\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.editorconfig\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.editorconfig"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.editorconfig"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Editor Config}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.editorconfig"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.editorconfig\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.editorconfig\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.editorconfig\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.edn\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.edn\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.edn"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.edn"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Extensible Data Notation}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.edn"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.edn\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.edn\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.edn\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.erb\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.erb\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.erb"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.erb"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Ruby}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.erb"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.erb\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\ruby.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.erb\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.erb\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.eyaml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.eyaml\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.eyaml"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyaml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Hiera Eyaml}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyaml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyaml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\yaml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyaml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyaml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.eyml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.eyml\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.eyml"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Hiera Eyaml}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\yaml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.eyml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fs\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.fs"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,F#}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fsi\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fsi\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.fsi"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsi"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,F# Signature}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsi"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsi\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsi\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsi\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fsscript\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fsscript\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.fsscript"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsscript"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,F# Script}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsscript"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsscript\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsscript\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsscript\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fsx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.fsx\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.fsx"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,F# Script}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.fsx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gemspec\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gemspec\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.gemspec"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gemspec"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Gemspec}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gemspec"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gemspec\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\ruby.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gemspec\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gemspec\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gitattributes\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gitattributes\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.gitattributes"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitattributes"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Git Attributes}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitattributes"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitattributes"; ValueType: string; ValueName: "AlwaysShowExt"; ValueData: ""; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitattributes\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitattributes\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitattributes\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gitconfig\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gitconfig\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.gitconfig"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitconfig"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Git Config}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitconfig"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitconfig"; ValueType: string; ValueName: "AlwaysShowExt"; ValueData: ""; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitconfig\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitconfig\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitconfig\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gitignore\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gitignore\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.gitignore"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitignore"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Git Ignore}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitignore"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitignore"; ValueType: string; ValueName: "AlwaysShowExt"; ValueData: ""; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitignore\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitignore\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gitignore\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.go\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.go\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.go"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.go"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Go}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.go"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.go\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\go.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.go\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.go\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gradle\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.gradle\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.gradle"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gradle"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Gradle}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gradle"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gradle\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gradle\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.gradle\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.groovy\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.groovy\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.groovy"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.groovy"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Groovy}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.groovy"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.groovy\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.groovy\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.groovy\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.h\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.h\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.h"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C Header}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\c.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.handlebars\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.handlebars\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.handlebars"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.handlebars"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Handlebars}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.handlebars"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.handlebars\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.handlebars\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.handlebars\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hbs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hbs\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.hbs"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hbs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Handlebars}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hbs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hbs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hbs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hbs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.h++\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.h++\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.h++"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h++"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++ Header}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h++"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h++\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.h++\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hh\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hh\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.hh"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hh"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++ Header}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hh"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hh\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hh\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hh\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hpp\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hpp\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.hpp"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hpp"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++ Header}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hpp"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hpp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hpp\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hpp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.htm\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.htm\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.htm"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.htm"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,HTML}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.htm"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.htm\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.htm\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.htm\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.html\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.html\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.html"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.html"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,HTML}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.html"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.html\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.html\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.html\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hxx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.hxx\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.hxx"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hxx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,C++ Header}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hxx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hxx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\cpp.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hxx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.hxx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ini\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ini\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.ini"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ini"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,INI}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ini"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ini\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\config.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ini\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ini\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ipynb\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ipynb\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.ipynb"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ipynb"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Jupyter}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ipynb"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ipynb\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ipynb\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ipynb\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jade\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jade\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.jade"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jade"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Jade}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jade"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jade\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\jade.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jade\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jade\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jav\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jav\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.jav"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jav"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Java}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jav"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jav\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\java.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jav\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jav\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.java\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.java\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.java"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.java"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Java}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.java"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.java\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\java.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.java\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.java\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.js\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.js\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.js"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.js"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JavaScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.js"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.js\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\javascript.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.js\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.js\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jsx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jsx\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.jsx"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JavaScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\react.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jscsrc\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jscsrc\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.jscsrc"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jscsrc"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JSCS RC}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jscsrc"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jscsrc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\javascript.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jscsrc\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jscsrc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jshintrc\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jshintrc\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.jshintrc"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshintrc"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JSHint RC}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshintrc"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshintrc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\javascript.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshintrc\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshintrc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jshtm\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jshtm\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.jshtm"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshtm"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JavaScript HTML Template}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshtm"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshtm\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshtm\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jshtm\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.json\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.json\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.json"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.json"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JSON}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.json"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.json\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\json.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.json\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.json\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jsp\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.jsp\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.jsp"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsp"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Java Server Pages}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsp"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsp\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.jsp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.less\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.less\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.less"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.less"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,LESS}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.less"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.less\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\less.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.less\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.less\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.log\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.log\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.log"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.log"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Log file}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.log"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.log\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.log\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.log\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.lua\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.lua\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.lua"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.lua"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Lua}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.lua"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.lua\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.lua\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.lua\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.m\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.m\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.m"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.m"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Objective C}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.m"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.m\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.m\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.m\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.makefile\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.makefile\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.makefile"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.makefile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Makefile}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.makefile"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.makefile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.makefile\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.makefile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.markdown\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.markdown\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.markdown"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.markdown"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.markdown"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.markdown\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.markdown\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.markdown\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.md\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.md\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.md"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.md"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.md"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.md\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.md\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.md\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdoc\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdoc\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.mdoc"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdoc"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,MDoc}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdoc"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdoc\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdoc\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdoc\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdown\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdown\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.mdown"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdown"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdown"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdown\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdown\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdown\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdtext\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdtext\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.mdtext"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtext"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtext"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtext\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtext\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtext\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdtxt\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdtxt\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.mdtxt"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtxt"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtxt"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtxt\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtxt\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdtxt\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdwn\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mdwn\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.mdwn"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdwn"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdwn"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdwn\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdwn\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mdwn\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mk\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mk\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.mk"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mk"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Makefile}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mk"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mk\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mk\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mk\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mkd\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mkd\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.mkd"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkd"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkd"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkd\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkd\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkd\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mkdn\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mkdn\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.mkdn"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkdn"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Markdown}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkdn"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkdn\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\markdown.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkdn\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mkdn\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ml\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.ml"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,OCaml}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mli\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mli\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.mli"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mli"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,OCaml}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mli"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mli\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mli\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mli\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mjs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.mjs\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.mjs"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mjs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,JavaScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mjs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mjs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\javascript.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mjs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.mjs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.npmignore\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.npmignore\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.npmignore"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.npmignore"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,NPM Ignore}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.npmignore"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.npmignore"; ValueType: string; ValueName: "AlwaysShowExt"; ValueData: ""; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.npmignore\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.npmignore\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.npmignore\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.php\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.php\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.php"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.php"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,PHP}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.php"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.php\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\php.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.php\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.php\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.phtml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.phtml\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.phtml"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.phtml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,PHP HTML}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.phtml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.phtml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.phtml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.phtml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pl\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pl\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.pl"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pl6\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pl6\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.pl6"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl6"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl 6}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl6"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl6\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl6\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pl6\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.plist\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.plist\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.plist"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.plist"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Properties file}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.plist"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.plist\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.plist\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.plist\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pm\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pm\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.pm"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl Module}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pm6\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pm6\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.pm6"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm6"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl 6 Module}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm6"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm6\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm6\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pm6\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pod\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pod\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.pod"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pod"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl POD}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pod"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pod\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pod\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pod\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pp\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pp\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.pp"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pp"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pp"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pp\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pp\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pp\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.profile\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.profile\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.profile"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.profile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Profile}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.profile"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.profile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.profile\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.profile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.properties\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.properties\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.properties"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.properties"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Properties}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.properties"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.properties\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.properties\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.properties\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ps1\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ps1\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.ps1"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ps1"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,PowerShell}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ps1"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ps1\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\powershell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ps1\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ps1\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.psd1\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.psd1\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.psd1"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psd1"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,PowerShell Module Manifest}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psd1"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psd1\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\powershell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psd1\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psd1\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.psgi\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.psgi\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.psgi"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psgi"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl CGI}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psgi"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psgi\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psgi\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psgi\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.psm1\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.psm1\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.psm1"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psm1"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,PowerShell Module}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psm1"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psm1\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\powershell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psm1\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.psm1\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.py\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.py\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.py"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.py"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Python}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.py"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.py\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\python.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.py\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.py\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pyi\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.pyi\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.pyi"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pyi"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Python}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pyi"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pyi\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\python.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pyi\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.pyi\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.r\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.r\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.r"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.r"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,R}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.r"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.r\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.r\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.r\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rb\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rb\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.rb"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rb"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Ruby}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rb"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rb\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\ruby.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rb\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rb\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rhistory\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rhistory\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.rhistory"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rhistory"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,R History}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rhistory"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rhistory\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rhistory\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rhistory\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rprofile\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rprofile\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.rprofile"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rprofile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,R Profile}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rprofile"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rprofile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rprofile\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rprofile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rs\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.rs"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Rust}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rst\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rst\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.rst"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rst"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Restructured Text}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rst"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rst\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rst\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rst\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rt\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.rt\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.rt"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rt"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Rich Text}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rt"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rt\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rt\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.rt\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sass\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sass\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.sass"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sass"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Sass}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sass"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sass\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\sass.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sass\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sass\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.scss\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.scss\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.scss"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.scss"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Sass}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.scss"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.scss\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\sass.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.scss\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.scss\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sh\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sh\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.sh"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sh"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SH}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sh"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sh\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sh\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sh\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.shtml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.shtml\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.shtml"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.shtml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SHTML}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.shtml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.shtml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.shtml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.shtml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.sql"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SQL}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\sql.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.svg\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.svg\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.svg"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SVG}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.svg\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.t\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.t\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.t"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.t"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Perl}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.t"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.t\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.t\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.t\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.tex\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.tex\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.tex"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tex"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,LaTeX}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tex"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tex\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tex\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tex\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ts\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.ts\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.ts"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ts"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,TypeScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ts"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ts\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\typescript.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ts\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.ts\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.toml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.toml\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.toml"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.toml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Toml}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.toml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.toml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.toml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.toml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.tsx\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.tsx\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.tsx"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tsx"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,TypeScript}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tsx"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tsx\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\react.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tsx\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.tsx\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.txt\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.txt\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.txt"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.txt"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Text}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.txt"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.txt\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.txt\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.txt\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.vb\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.vb\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.vb"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vb"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Visual Basic}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vb"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vb\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vb\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vb\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.vue\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.vue\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.vue"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vue"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,VUE}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vue"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vue\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\vue.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vue\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.vue\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.wxi\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.wxi\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.wxi"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxi"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,WiX Include}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxi"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxi\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxi\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxi\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.wxl\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.wxl\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.wxl"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxl"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,WiX Localization}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxl"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxl\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxl\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxl\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.wxs\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.wxs\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.wxs"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxs"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,WiX}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxs"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxs\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxs\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.wxs\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.xaml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.xaml\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.xaml"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xaml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,XAML}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xaml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xaml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\xml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xaml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xaml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.xhtml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.xhtml\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.xhtml"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xhtml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,HTML}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xhtml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xhtml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\html.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xhtml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xhtml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.xml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.xml\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.xml"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,XML}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\xml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.xml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.yaml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.yaml\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.yaml"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yaml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Yaml}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yaml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yaml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\yaml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yaml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yaml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.yml\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.yml\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.yml"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yml"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Yaml}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yml"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yml\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\yaml.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yml\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.yml\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.zsh\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.zsh\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.zsh"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.zsh"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,ZSH}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.zsh"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.zsh\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\shell.ico"; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.zsh\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.zsh\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,{#NameLong}}"; Flags: uninsdeletekey +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico" +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile\shell\open"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe""" +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1""" + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Applications\{#ExeBasename}.exe"; ValueType: none; ValueName: ""; Flags: uninsdeletekey +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Applications\{#ExeBasename}.exe\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#VersionedResourcesFolder}\resources\app\resources\win32\default.ico" +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Applications\{#ExeBasename}.exe\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe""" +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Applications\{#ExeBasename}.exe\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1""" + +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}ContextMenu"; ValueType: expandsz; ValueName: "Title"; ValueData: "{cm:OpenWithCodeContextMenu,{#ShellNameShort}}"; Tasks: addcontextmenufiles; Flags: uninsdeletekey; Check: IsWindows11OrLater and QualityIsInsiders +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\*\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "{cm:OpenWithCodeContextMenu,{#ShellNameShort}}"; Tasks: addcontextmenufiles; Flags: uninsdeletekey; Check: not (IsWindows11OrLater and QualityIsInsiders) +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\*\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufiles; Check: not (IsWindows11OrLater and QualityIsInsiders) +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\*\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: addcontextmenufiles; Check: not (IsWindows11OrLater and QualityIsInsiders) +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "{cm:OpenWithCodeContextMenu,{#ShellNameShort}}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey; Check: not (IsWindows11OrLater and QualityIsInsiders) +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders; Check: not (IsWindows11OrLater and QualityIsInsiders) +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders; Check: not (IsWindows11OrLater and QualityIsInsiders) +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\background\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "{cm:OpenWithCodeContextMenu,{#ShellNameShort}}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey; Check: not (IsWindows11OrLater and QualityIsInsiders) +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\background\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders; Check: not (IsWindows11OrLater and QualityIsInsiders) +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\directory\background\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders; Check: not (IsWindows11OrLater and QualityIsInsiders) +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Drive\shell\{#RegValueName}"; ValueType: expandsz; ValueName: ""; ValueData: "{cm:OpenWithCodeContextMenu,{#ShellNameShort}}"; Tasks: addcontextmenufolders; Flags: uninsdeletekey; Check: not (IsWindows11OrLater and QualityIsInsiders) +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Drive\shell\{#RegValueName}"; ValueType: expandsz; ValueName: "Icon"; ValueData: "{app}\{#ExeBasename}.exe"; Tasks: addcontextmenufolders; Check: not (IsWindows11OrLater and QualityIsInsiders) +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\Drive\shell\{#RegValueName}\command"; ValueType: expandsz; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%V"""; Tasks: addcontextmenufolders; Check: not (IsWindows11OrLater and QualityIsInsiders) + +; Environment +#if "user" == InstallTarget +#define EnvironmentRootKey "HKCU" +#define EnvironmentKey "Environment" +#define Uninstall64RootKey "HKCU64" +#define Uninstall32RootKey "HKCU32" +#else +#define EnvironmentRootKey "HKLM" +#define EnvironmentKey "System\CurrentControlSet\Control\Session Manager\Environment" +#define Uninstall64RootKey "HKLM64" +#define Uninstall32RootKey "HKLM32" +#endif + +Root: {#EnvironmentRootKey}; Subkey: "{#EnvironmentKey}"; ValueType: expandsz; ValueName: "Path"; ValueData: "{code:AddToPath|{app}\bin}"; Tasks: addtopath; Check: NeedsAddToPath(ExpandConstant('{app}\bin')) + +[Code] +function IsBackgroundUpdate(): Boolean; +begin + Result := ExpandConstant('{param:update|false}') <> 'false'; +end; + +function IsNotBackgroundUpdate(): Boolean; +begin + Result := not IsBackgroundUpdate(); +end; + +// Don't allow installing conflicting architectures +function InitializeSetup(): Boolean; +var + RegKey: String; + ThisArch: String; + AltArch: String; +begin + Result := True; + + #if "user" == InstallTarget + if not WizardSilent() and IsAdmin() then begin + if MsgBox('This User Installer is not meant to be run as an Administrator. If you would like to install VS Code for all users in this system, download the System Installer instead from https://code.visualstudio.com. Are you sure you want to continue?', mbError, MB_OKCANCEL) = IDCANCEL then begin + Result := False; + end; + end; + #endif + + #if "user" == InstallTarget + #if "arm64" == Arch + #define IncompatibleArchRootKey "HKLM32" + #else + #define IncompatibleArchRootKey "HKLM64" + #endif + + if Result and not WizardSilent() then begin + RegKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + copy('{#IncompatibleTargetAppId}', 2, 38) + '_is1'; + + if RegKeyExists({#IncompatibleArchRootKey}, RegKey) then begin + if MsgBox('{#NameShort} is already installed on this system for all users. We recommend first uninstalling that version before installing this one. Are you sure you want to continue the installation?', mbConfirmation, MB_YESNO) = IDNO then begin + Result := False; + end; + end; + end; + #endif + +end; + +function WizardNotSilent(): Boolean; +begin + Result := not WizardSilent(); +end; + +// Updates + +var + ShouldRestartTunnelService: Boolean; + +function StopTunnelOtherProcesses(): Boolean; +var + WaitCounter: Integer; + TaskKilled: Integer; +begin + Log('Stopping all tunnel services (at ' + ExpandConstant('"{app}\bin\{#TunnelApplicationName}.exe"') + ')'); + ShellExec('', 'powershell.exe', '-Command "Get-WmiObject Win32_Process | Where-Object { $_.ExecutablePath -eq ' + ExpandConstant('''{app}\bin\{#TunnelApplicationName}.exe''') + ' } | Select @{Name=''Id''; Expression={$_.ProcessId}} | Stop-Process -Force"', '', SW_HIDE, ewWaitUntilTerminated, TaskKilled) + + WaitCounter := 10; + while (WaitCounter > 0) and CheckForMutexes('{#TunnelMutex}') do + begin + Log('Tunnel process is is still running, waiting'); + Sleep(500); + WaitCounter := WaitCounter - 1 + end; + + if CheckForMutexes('{#TunnelMutex}') then + begin + Log('Unable to stop tunnel processes'); + Result := False; + end + else + Result := True; +end; + +procedure StopTunnelServiceIfNeeded(); +var + StopServiceResultCode: Integer; + WaitCounter: Integer; +begin + ShouldRestartTunnelService := False; + if CheckForMutexes('{#TunnelServiceMutex}') then begin + // stop the tunnel service + Log('Stopping the tunnel service using ' + ExpandConstant('"{app}\bin\{#ApplicationName}.cmd"')); + ShellExec('', ExpandConstant('"{app}\bin\{#ApplicationName}.cmd"'), 'tunnel service uninstall', '', SW_HIDE, ewWaitUntilTerminated, StopServiceResultCode); + + Log('Stopping the tunnel service completed with result code ' + IntToStr(StopServiceResultCode)); + + WaitCounter := 10; + while (WaitCounter > 0) and CheckForMutexes('{#TunnelServiceMutex}') do + begin + Log('Tunnel service is still running, waiting'); + Sleep(500); + WaitCounter := WaitCounter - 1 + end; + if CheckForMutexes('{#TunnelServiceMutex}') then + Log('Unable to stop tunnel service') + else + ShouldRestartTunnelService := True; + end +end; + + +// called before the wizard checks for running application +function PrepareToInstall(var NeedsRestart: Boolean): String; +begin + if IsNotBackgroundUpdate() then + StopTunnelServiceIfNeeded(); + + if IsNotBackgroundUpdate() and not StopTunnelOtherProcesses() then + Result := '{#NameShort} is still running a tunnel process. Please stop the tunnel before installing.' + else + Result := ''; +end; + +// VS Code will create a flag file before the update starts (/update=C:\foo\bar) +// - if the file exists at this point, the user quit Code before the update finished, so don't start Code after update +// - otherwise, the user has accepted to apply the update and Code should start +function LockFileExists(): Boolean; +begin + Result := FileExists(ExpandConstant('{param:update}')) +end; + +// Check if VS Code created a session-end flag file to indicate OS is shutting down +// This prevents calling inno_updater.exe during system shutdown +function SessionEndFileExists(): Boolean; +begin + Result := FileExists(ExpandConstant('{param:sessionend}')) +end; + +function ShouldRunAfterUpdate(): Boolean; +begin + if IsBackgroundUpdate() then + Result := not LockFileExists() + else + Result := True; +end; + +function IsWindows11OrLater(): Boolean; +begin + Result := (GetWindowsVersion >= $0A0055F0); +end; + +function GetAppMutex(Value: string): string; +begin + if IsBackgroundUpdate() then + Result := '' + else + Result := '{#AppMutex}'; +end; + +function GetDestDir(Value: string): string; +begin + Result := ExpandConstant('{app}'); +end; + +function GetVisualElementsManifest(Value: string): string; +begin + if IsBackgroundUpdate() then + Result := ExpandConstant('new_{#ExeBasename}.VisualElementsManifest.xml') + else + Result := ExpandConstant('{#ExeBasename}.VisualElementsManifest.xml'); +end; + +function GetExeBasename(Value: string): string; +begin + if IsBackgroundUpdate() then + Result := ExpandConstant('new_{#ExeBasename}.exe') + else + Result := ExpandConstant('{#ExeBasename}.exe'); +end; + +function GetBinDirTunnelApplicationFilename(Value: string): string; +begin + if IsBackgroundUpdate() then + Result := ExpandConstant('new_{#TunnelApplicationName}.exe') + else + Result := ExpandConstant('{#TunnelApplicationName}.exe'); +end; + +function GetBinDirApplicationFilename(Value: string): string; +begin + if IsBackgroundUpdate() then + Result := ExpandConstant('new_{#ApplicationName}') + else + Result := ExpandConstant('{#ApplicationName}'); +end; + +function GetBinDirApplicationCmdFilename(Value: string): string; +begin + if IsBackgroundUpdate() then + Result := ExpandConstant('new_{#ApplicationName}.cmd') + else + Result := ExpandConstant('{#ApplicationName}.cmd'); +end; + +function BoolToStr(Value: Boolean): String; +begin + if Value then + Result := 'true' + else + Result := 'false'; +end; + +function QualityIsInsiders(): boolean; +begin + if '{#Quality}' = 'insider' then + Result := True + else + Result := False; +end; + +#ifdef AppxPackageName +var + AppxPackageFullname: String; + +procedure ExecAndGetFirstLineLog(const S: String; const Error, FirstLine: Boolean); +begin + if not Error and (AppxPackageFullname = '') and (Trim(S) <> '') then + AppxPackageFullname := S; + Log(S); +end; + +function AppxPackageInstalled(const name: String; var ResultCode: Integer): Boolean; +begin + AppxPackageFullname := ''; + try + Log('Get-AppxPackage for package with name: ' + name); + ExecAndLogOutput('powershell.exe', '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command ' + AddQuotes('Get-AppxPackage -Name ''' + name + ''' | Select-Object -ExpandProperty PackageFullName'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode, @ExecAndGetFirstLineLog); + except + Log(GetExceptionMessage); + end; + if (AppxPackageFullname <> '') then + Result := True + else + Result := False +end; + +procedure AddAppxPackage(); +var + AddAppxPackageResultCode: Integer; +begin + if not SessionEndFileExists() and not AppxPackageInstalled(ExpandConstant('{#AppxPackageName}'), AddAppxPackageResultCode) then begin + Log('Installing appx ' + AppxPackageFullname + ' ...'); + ShellExec('', 'powershell.exe', '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command ' + AddQuotes('Add-AppxPackage -Path ''' + ExpandConstant('{app}\{#VersionedResourcesFolder}\appx\{#AppxPackage}') + ''' -ExternalLocation ''' + ExpandConstant('{app}\{#VersionedResourcesFolder}\appx') + ''''), '', SW_HIDE, ewWaitUntilTerminated, AddAppxPackageResultCode); + Log('Add-AppxPackage complete.'); + end; +end; + +procedure RemoveAppxPackage(); +var + RemoveAppxPackageResultCode: Integer; +begin + // Remove the old context menu package + // Following condition can be removed after two versions. + if QualityIsInsiders() and not SessionEndFileExists() and AppxPackageInstalled('Microsoft.VSCodeInsiders', RemoveAppxPackageResultCode) then begin + Log('Deleting old appx ' + AppxPackageFullname + ' installation...'); + ShellExec('', 'powershell.exe', '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command ' + AddQuotes('Remove-AppxPackage -Package ''' + AppxPackageFullname + ''''), '', SW_HIDE, ewWaitUntilTerminated, RemoveAppxPackageResultCode); + DeleteFile(ExpandConstant('{app}\appx\code_insiders_explorer_{#Arch}.appx')); + DeleteFile(ExpandConstant('{app}\appx\code_insiders_explorer_command.dll')); + end; + if not SessionEndFileExists() and AppxPackageInstalled(ExpandConstant('{#AppxPackageName}'), RemoveAppxPackageResultCode) then begin + Log('Removing current ' + AppxPackageFullname + ' appx installation...'); + ShellExec('', 'powershell.exe', '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command ' + AddQuotes('Remove-AppxPackage -Package ''' + AppxPackageFullname + ''''), '', SW_HIDE, ewWaitUntilTerminated, RemoveAppxPackageResultCode); + Log('Remove-AppxPackage for current appx installation complete.'); + end; +end; +#endif + +procedure CurStepChanged(CurStep: TSetupStep); +var + UpdateResultCode: Integer; + StartServiceResultCode: Integer; +begin + if CurStep = ssPostInstall then + begin +#ifdef AppxPackageName + // Remove the old context menu registry keys for insiders + if QualityIsInsiders() and WizardIsTaskSelected('addcontextmenufiles') then begin + RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\*\shell\{#RegValueName}'); + RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\directory\shell\{#RegValueName}'); + RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\directory\background\shell\{#RegValueName}'); + RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\Drive\shell\{#RegValueName}'); + end; +#endif + + if IsBackgroundUpdate() then + begin + SaveStringToFile(ExpandConstant('{app}\updating_version'), '{#Commit}', False); + CreateMutex('{#AppMutex}-ready'); + + Log('Checking whether application is still running...'); + while (CheckForMutexes('{#AppMutex}')) do + begin + Sleep(1000) + end; + Log('Application appears not to be running.'); + + if not SessionEndFileExists() then begin + StopTunnelServiceIfNeeded(); + Log('Invoking inno_updater for background update'); + Exec(ExpandConstant('{app}\{#VersionedResourcesFolder}\tools\inno_updater.exe'), ExpandConstant('"{app}\{#ExeBasename}.exe" ' + BoolToStr(LockFileExists()) + ' "{cm:UpdatingVisualStudioCode}"'), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode); + DeleteFile(ExpandConstant('{app}\updating_version')); + Log('inno_updater completed successfully'); + #if "system" == InstallTarget + Log('Invoking inno_updater to remove previous installation folder'); + Exec(ExpandConstant('{app}\{#VersionedResourcesFolder}\tools\inno_updater.exe'), ExpandConstant('"--gc" "{app}\{#ExeBasename}.exe" "{#VersionedResourcesFolder}"'), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode); + Log('inno_updater completed gc successfully'); + #endif + end else begin + Log('Skipping inno_updater.exe call because OS session is ending'); + end; + end else begin + Log('Invoking inno_updater to remove previous installation folder'); + Exec(ExpandConstant('{app}\{#VersionedResourcesFolder}\tools\inno_updater.exe'), ExpandConstant('"--gc" "{app}\{#ExeBasename}.exe" "{#VersionedResourcesFolder}"'), '', SW_SHOW, ewWaitUntilTerminated, UpdateResultCode); + Log('inno_updater completed gc successfully'); + end; + + if ShouldRestartTunnelService then + begin + // start the tunnel service + Log('Restarting the tunnel service...'); + ShellExec('', ExpandConstant('"{app}\bin\{#ApplicationName}.cmd"'), 'tunnel service install', '', SW_HIDE, ewWaitUntilTerminated, StartServiceResultCode); + Log('Starting the tunnel service completed with result code ' + IntToStr(StartServiceResultCode)); + ShouldRestartTunnelService := False + end; + end; +end; + +// https://stackoverflow.com/a/23838239/261019 +procedure Explode(var Dest: TArrayOfString; Text: String; Separator: String); +var + i, p: Integer; +begin + i := 0; + repeat + SetArrayLength(Dest, i+1); + p := Pos(Separator,Text); + if p > 0 then begin + Dest[i] := Copy(Text, 1, p-1); + Text := Copy(Text, p + Length(Separator), Length(Text)); + i := i + 1; + end else begin + Dest[i] := Text; + Text := ''; + end; + until Length(Text)=0; +end; + +function NeedsAddToPath(VSCode: string): boolean; +var + OrigPath: string; +begin + if not RegQueryStringValue({#EnvironmentRootKey}, '{#EnvironmentKey}', 'Path', OrigPath) + then begin + Result := True; + exit; + end; + Result := Pos(';' + VSCode + ';', ';' + OrigPath + ';') = 0; +end; + +function AddToPath(VSCode: string): string; +var + OrigPath: string; +begin + RegQueryStringValue({#EnvironmentRootKey}, '{#EnvironmentKey}', 'Path', OrigPath) + + if (Length(OrigPath) > 0) and (OrigPath[Length(OrigPath)] = ';') then + Result := OrigPath + VSCode + else + Result := OrigPath + ';' + VSCode +end; + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +var + Path: string; + VSCodePath: string; + Parts: TArrayOfString; + NewPath: string; + i: Integer; +begin + if not CurUninstallStep = usUninstall then begin + exit; + end; +#ifdef AppxPackageName + #if "user" == InstallTarget + RemoveAppxPackage(); + #endif +#endif + if not RegQueryStringValue({#EnvironmentRootKey}, '{#EnvironmentKey}', 'Path', Path) + then begin + exit; + end; + NewPath := ''; + VSCodePath := ExpandConstant('{app}\bin') + Explode(Parts, Path, ';'); + for i:=0 to GetArrayLength(Parts)-1 do begin + if CompareText(Parts[i], VSCodePath) <> 0 then begin + NewPath := NewPath + Parts[i]; + + if i < GetArrayLength(Parts) - 1 then begin + NewPath := NewPath + ';'; + end; + end; + end; + RegWriteExpandStringValue({#EnvironmentRootKey}, '{#EnvironmentKey}', 'Path', NewPath); +end; + +#ifdef Debug + #expr SaveToFile(AddBackslash(SourcePath) + "code-processed.iss") +#endif + +// https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/icacls +// https://docs.microsoft.com/en-US/windows/security/identity-protection/access-control/security-identifiers +procedure DisableAppDirInheritance(); +var + ResultCode: Integer; + Permissions: string; +begin + Permissions := '/grant:r "*S-1-5-18:(OI)(CI)F" /grant:r "*S-1-5-32-544:(OI)(CI)F" /grant:r "*S-1-5-11:(OI)(CI)RX" /grant:r "*S-1-5-32-545:(OI)(CI)RX"'; + + #if "user" == InstallTarget + Permissions := Permissions + Format(' /grant:r "*S-1-3-0:(OI)(CI)F" /grant:r "%s:(OI)(CI)F"', [GetUserNameString()]); + #endif + + Exec(ExpandConstant('{sys}\icacls.exe'), ExpandConstant('"{app}" /inheritancelevel:r ') + Permissions, '', SW_HIDE, ewWaitUntilTerminated, ResultCode); +end; diff --git a/build/win32/code.iss b/build/win32/code.iss index f52a1a784b7e2..cc11cbe80c157 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -279,7 +279,7 @@ Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.cls\s Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.code-workspace\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.code-workspace\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.code-workspace"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,Code Workspace}"; Flags: uninsdeletekey; Tasks: associatewithfiles -Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles +Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\default.ico"; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace\shell\open"; ValueType: string; ValueName: "Icon"; ValueData: """{app}\{#ExeBasename}.exe"""; Tasks: associatewithfiles Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.code-workspace\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles @@ -1357,7 +1357,7 @@ var TaskKilled: Integer; begin Log('Stopping all tunnel services (at ' + ExpandConstant('"{app}\bin\{#TunnelApplicationName}.exe"') + ')'); - ShellExec('', 'powershell.exe', '-Command "Get-WmiObject Win32_Process | Where-Object { $_.ExecutablePath -eq ' + ExpandConstant('''{app}\bin\{#TunnelApplicationName}.exe''') + ' } | Select @{Name=''Id''; Expression={$_.ProcessId}} | Stop-Process -Force"', '', SW_HIDE, ewWaitUntilTerminated, TaskKilled) + ShellExec('', 'powershell.exe', '-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command "Get-WmiObject Win32_Process | Where-Object { $_.ExecutablePath -eq ' + ExpandConstant('''{app}\bin\{#TunnelApplicationName}.exe''') + ' } | Select @{Name=''Id''; Expression={$_.ProcessId}} | Stop-Process -Force"', '', SW_HIDE, ewWaitUntilTerminated, TaskKilled) WaitCounter := 10; while (WaitCounter > 0) and CheckForMutexes('{#TunnelMutex}') do @@ -1534,9 +1534,8 @@ begin if CurStep = ssPostInstall then begin #ifdef AppxPackageName - if not WizardIsTaskSelected('addcontextmenufiles') then begin - RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\{#RegValueName}ContextMenu'); - end else begin + // Remove the old context menu registry keys for insiders + if QualityIsInsiders() and WizardIsTaskSelected('addcontextmenufiles') then begin RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\*\shell\{#RegValueName}'); RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\directory\shell\{#RegValueName}'); RegDeleteKeyIncludingSubkeys({#EnvironmentRootKey}, 'Software\Classes\directory\background\shell\{#RegValueName}'); diff --git a/build/win32/explorer-dll-fetcher.js b/build/win32/explorer-dll-fetcher.js deleted file mode 100644 index dfb9ce97ff4ac..0000000000000 --- a/build/win32/explorer-dll-fetcher.js +++ /dev/null @@ -1,64 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.downloadExplorerDll = downloadExplorerDll; -const fs_1 = __importDefault(require("fs")); -const debug_1 = __importDefault(require("debug")); -const path_1 = __importDefault(require("path")); -const get_1 = require("@electron/get"); -const product_json_1 = __importDefault(require("../../product.json")); -const d = (0, debug_1.default)('explorer-dll-fetcher'); -async function downloadExplorerDll(outDir, quality = 'stable', targetArch = 'x64') { - const fileNamePrefix = quality === 'insider' ? 'code_insider' : 'code'; - const fileName = `${fileNamePrefix}_explorer_command_${targetArch}.dll`; - if (!await fs_1.default.existsSync(outDir)) { - await fs_1.default.mkdirSync(outDir, { recursive: true }); - } - // Read and parse checksums file - const checksumsFilePath = path_1.default.join(path_1.default.dirname(__dirname), 'checksums', 'explorer-dll.txt'); - const checksumsContent = fs_1.default.readFileSync(checksumsFilePath, 'utf8'); - const checksums = {}; - checksumsContent.split('\n').forEach(line => { - const trimmedLine = line.trim(); - if (trimmedLine) { - const [checksum, filename] = trimmedLine.split(/\s+/); - if (checksum && filename) { - checksums[filename] = checksum; - } - } - }); - d(`downloading ${fileName}`); - const artifact = await (0, get_1.downloadArtifact)({ - isGeneric: true, - version: 'v4.0.0-350164', - artifactName: fileName, - checksums, - mirrorOptions: { - mirror: 'https://github.com/microsoft/vscode-explorer-command/releases/download/', - customDir: 'v4.0.0-350164', - customFilename: fileName - } - }); - d(`moving ${artifact} to ${outDir}`); - await fs_1.default.copyFileSync(artifact, path_1.default.join(outDir, fileName)); -} -async function main(outputDir) { - const arch = process.env['VSCODE_ARCH']; - if (!outputDir) { - throw new Error('Required build env not set'); - } - await downloadExplorerDll(outputDir, product_json_1.default.quality, arch); -} -if (require.main === module) { - main(process.argv[2]).catch(err => { - console.error(err); - process.exit(1); - }); -} -//# sourceMappingURL=explorer-dll-fetcher.js.map \ No newline at end of file diff --git a/build/win32/explorer-dll-fetcher.ts b/build/win32/explorer-dll-fetcher.ts index 724a35bc56894..09bd269184324 100644 --- a/build/win32/explorer-dll-fetcher.ts +++ b/build/win32/explorer-dll-fetcher.ts @@ -2,14 +2,18 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -'use strict'; - import fs from 'fs'; import debug from 'debug'; import path from 'path'; import { downloadArtifact } from '@electron/get'; -import product from '../../product.json'; +import productJson from '../../product.json' with { type: 'json' }; + +interface ProductConfiguration { + quality?: string; + [key: string]: unknown; +} + +const product: ProductConfiguration = productJson; const d = debug('explorer-dll-fetcher'); @@ -22,7 +26,7 @@ export async function downloadExplorerDll(outDir: string, quality: string = 'sta } // Read and parse checksums file - const checksumsFilePath = path.join(path.dirname(__dirname), 'checksums', 'explorer-dll.txt'); + const checksumsFilePath = path.join(path.dirname(import.meta.dirname), 'checksums', 'explorer-dll.txt'); const checksumsContent = fs.readFileSync(checksumsFilePath, 'utf8'); const checksums: Record = {}; @@ -39,12 +43,12 @@ export async function downloadExplorerDll(outDir: string, quality: string = 'sta d(`downloading ${fileName}`); const artifact = await downloadArtifact({ isGeneric: true, - version: 'v4.0.0-350164', + version: 'v5.0.0-377200', artifactName: fileName, checksums, mirrorOptions: { mirror: 'https://github.com/microsoft/vscode-explorer-command/releases/download/', - customDir: 'v4.0.0-350164', + customDir: 'v5.0.0-377200', customFilename: fileName } }); @@ -60,10 +64,10 @@ async function main(outputDir?: string): Promise { throw new Error('Required build env not set'); } - await downloadExplorerDll(outputDir, (product as any).quality, arch); + await downloadExplorerDll(outputDir, product.quality, arch); } -if (require.main === module) { +if (import.meta.main) { main(process.argv[2]).catch(err => { console.error(err); process.exit(1); diff --git a/build/win32/inno_updater.exe b/build/win32/inno_updater.exe index 168549868473e..c3c4a0cd2bcb8 100644 Binary files a/build/win32/inno_updater.exe and b/build/win32/inno_updater.exe differ diff --git a/cglicenses.json b/cglicenses.json index 9651f2e2c7236..8ee75c0fb3402 100644 --- a/cglicenses.json +++ b/cglicenses.json @@ -70,7 +70,7 @@ }, { // Reason: The license cannot be found by the tool due to access controls on the repository - "name": "tas-client-umd", + "name": "tas-client", "fullLicenseText": [ "MIT License", "Copyright (c) 2020 - present Microsoft Corporation", @@ -535,7 +535,7 @@ ] }, { - "name":"vscode-markdown-languageserver", + "name": "vscode-markdown-languageserver", "fullLicenseText": [ "MIT License", "", @@ -617,5 +617,112 @@ { "name": "gethostname", "fullLicenseTextUri": "https://codeberg.org/swsnr/gethostname.rs/raw/commit/d1a7e1162c20106a1df2cdbba9eb2d5174037b3c/LICENSE" + }, + { + // Reason: Unlicense. Does not include a clear Copyright statement + "name": "robust-predicates", + "fullLicenseText": [ + "Unlicense", + "", + "Copyright (c) mourner. All rights reserved.", + "", + "This is free and unencumbered software released into the public domain.", + "", + "Anyone is free to copy, modify, publish, use, compile, sell, or", + "distribute this software, either in source code form or as a compiled", + "binary, for any purpose, commercial or non-commercial, and by any", + "means.", + "", + "In jurisdictions that recognize copyright laws, the author or authors", + "of this software dedicate any and all copyright interest in the", + "software to the public domain. We make this dedication for the benefit", + "of the public at large and to the detriment of our heirs and", + "successors. We intend this dedication to be an overt act of", + "relinquishment in perpetuity of all present and future rights to this", + "software under copyright law.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,", + "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", + "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.", + "IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR", + "OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,", + "ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR", + "OTHER DEALINGS IN THE SOFTWARE.", + "", + "For more information, please refer to " + ] + }, + { + "name": "@isaacs/balanced-match", + "fullLicenseText": [ + "MIT License", + "", + "Copyright Isaac Z. Schlueter ", + "", + "Original code Copyright Julian Gruber ", + "", + "Port to TypeScript Copyright Isaac Z. Schlueter ", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy of", + "this software and associated documentation files (the \"Software\"), to deal in", + "the Software without restriction, including without limitation the rights to", + "use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies", + "of the Software, and to permit persons to whom the Software is furnished to do", + "so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE.", + "" + ] + }, + { + "name": "@isaacs/brace-expansion", + "fullLicenseText": [ + "MIT License", + "", + "Copyright (c) 2013 Julian Gruber ", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE.", + "" + ] + }, + { + // Reason: mono-repo + "name": "@jridgewell/gen-mapping", + "fullLicenseTextUri": "https://raw.githubusercontent.com/jridgewell/sourcemaps/refs/heads/main/packages/gen-mapping/LICENSE" + }, + { + // Reason: mono-repo + "name": "@jridgewell/sourcemap-codec", + "fullLicenseTextUri": "https://raw.githubusercontent.com/jridgewell/sourcemaps/refs/heads/main/packages/sourcemap-codec/LICENSE" + }, + { + // Reason: mono-repo + "name": "@jridgewell/trace-mapping", + "fullLicenseTextUri": "https://raw.githubusercontent.com/jridgewell/sourcemaps/refs/heads/main/packages/trace-mapping/LICENSE" } ] diff --git a/cgmanifest.json b/cgmanifest.json index 660e9d91ebf46..cab19515d67ab 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "chromium", "repositoryUrl": "https://chromium.googlesource.com/chromium/src", - "commitHash": "5f45b4744e3d5ba82c2ca6d942f1e7a516110752" + "commitHash": "4d74005947d2522c31942de3d609355124455643" } }, "licenseDetail": [ @@ -40,7 +40,7 @@ "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ], "isOnlyProductionDependency": true, - "version": "138.0.7204.100" + "version": "142.0.7444.235" }, { "component": { @@ -516,11 +516,12 @@ "git": { "name": "nodejs", "repositoryUrl": "https://github.com/nodejs/node", - "commitHash": "3567fa7d70fee8831ab2891e6940ca63be568b08" + "commitHash": "6ac4ab19ad02803f03b54501193397563e99988e", + "tag": "22.21.1" } }, "isOnlyProductionDependency": true, - "version": "22.17.0" + "version": "22.21.1" }, { "component": { @@ -528,12 +529,13 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "e845d20789367bdfce1d46549ea83a2716b16922" + "commitHash": "4d18062d0f0ca34c455bc7ec032dd7959a0365b6", + "tag": "39.2.7" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "37.2.3" + "version": "39.2.7" }, { "component": { @@ -587,12 +589,12 @@ "git": { "name": "spdlog original", "repositoryUrl": "https://github.com/gabime/spdlog", - "commitHash": "4fba14c79f356ae48d6141c561bf9fd7ba33fabd" + "commitHash": "7e635fca68d014934b4af8a1cf874f63989352b7" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "0.14.0" + "version": "1.12.0" }, { "component": { @@ -600,11 +602,11 @@ "git": { "name": "vscode-codicons", "repositoryUrl": "https://github.com/microsoft/vscode-codicons", - "commitHash": "ccdcf91d57d3a5a1d6b620d95d518bab4d75984d" + "commitHash": "906a02039fe8d29721f3eec1e46406be8c4bee39" } }, "license": "MIT and Creative Commons Attribution 4.0", - "version": "0.0.14" + "version": "0.0.41" }, { "component": { @@ -634,12 +636,13 @@ "git": { "name": "ripgrep", "repositoryUrl": "https://github.com/BurntSushi/ripgrep", - "commitHash": "973de50c9ef451da2cfcdfa86f2b2711d8d6ff48" + "commitHash": "af6b6c543b224d348a8876f0c06245d9ea7929c5", + "tag": "13.0.0" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "0.10.0" + "version": "13.0.0" }, { "name": "@vscode/win32-app-container-tokens", diff --git a/cli/ThirdPartyNotices.txt b/cli/ThirdPartyNotices.txt index 739b714d9c0ca..fb163505cec74 100644 --- a/cli/ThirdPartyNotices.txt +++ b/cli/ThirdPartyNotices.txt @@ -4390,7 +4390,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- js-sys 0.3.69 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/js-sys +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/js-sys Copyright (c) 2014 Alex Crichton @@ -4422,7 +4422,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- keyring 2.3.3 - MIT OR Apache-2.0 -https://github.com/hwchen/keyring-rs +https://github.com/open-source-cooperative/keyring-rs Copyright (c) 2016 keyring Developers @@ -5404,7 +5404,7 @@ OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- openssl 0.10.72 - Apache-2.0 -https://github.com/sfackler/rust-openssl +https://github.com/rust-openssl/rust-openssl Copyright 2011-2017 Google Inc. 2013 Jack Lloyd @@ -5483,7 +5483,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- openssl-sys 0.9.107 - MIT -https://github.com/sfackler/rust-openssl +https://github.com/rust-openssl/rust-openssl The MIT License (MIT) @@ -7383,7 +7383,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- regex-automata 0.4.6 - MIT OR Apache-2.0 -https://github.com/rust-lang/regex/tree/master/regex-automata +https://github.com/rust-lang/regex Copyright (c) 2014 The Rust Project Developers @@ -7415,7 +7415,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- regex-syntax 0.8.3 - MIT OR Apache-2.0 -https://github.com/rust-lang/regex/tree/master/regex-syntax +https://github.com/rust-lang/regex Copyright (c) 2014 The Rust Project Developers @@ -8592,6 +8592,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [//]: # (crates) [`ascon‑hash`]: ./ascon-hash +[`bash‑hash`]: ./bash-hash [`belt‑hash`]: ./belt-hash [`blake2`]: ./blake2 [`fsb`]: ./fsb @@ -8635,6 +8636,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [//]: # (algorithms) [Ascon]: https://ascon.iaik.tugraz.at +[Bash]: https://apmi.bsu.by/assets/files/std/bash-spec241.pdf [BelT]: https://ru.wikipedia.org/wiki/BelT [BLAKE2]: https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2 [FSB]: https://en.wikipedia.org/wiki/Fast_syndrome-based_hash @@ -8686,6 +8688,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [//]: # (crates) [`ascon‑hash`]: ./ascon-hash +[`bash‑hash`]: ./bash-hash [`belt‑hash`]: ./belt-hash [`blake2`]: ./blake2 [`fsb`]: ./fsb @@ -8729,6 +8732,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [//]: # (algorithms) [Ascon]: https://ascon.iaik.tugraz.at +[Bash]: https://apmi.bsu.by/assets/files/std/bash-spec241.pdf [BelT]: https://ru.wikipedia.org/wiki/BelT [BLAKE2]: https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2 [FSB]: https://en.wikipedia.org/wiki/Fast_syndrome-based_hash @@ -10506,7 +10510,7 @@ THE SOFTWARE. wasi 0.11.0+wasi-snapshot-preview1 - Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT wasi 0.9.0+wasi-snapshot-preview1 - Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT -https://github.com/bytecodealliance/wasi +https://github.com/bytecodealliance/wasi-rs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated @@ -10536,7 +10540,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- wasm-bindgen 0.2.92 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen +https://github.com/wasm-bindgen/wasm-bindgen Copyright (c) 2014 Alex Crichton @@ -10568,7 +10572,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- wasm-bindgen-backend 0.2.92 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/backend Copyright (c) 2014 Alex Crichton @@ -10600,7 +10604,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- wasm-bindgen-futures 0.4.42 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/futures Copyright (c) 2014 Alex Crichton @@ -10632,7 +10636,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- wasm-bindgen-macro 0.2.92 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/macro Copyright (c) 2014 Alex Crichton @@ -10664,7 +10668,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- wasm-bindgen-macro-support 0.2.92 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro-support +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/macro-support Copyright (c) 2014 Alex Crichton @@ -10696,7 +10700,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- wasm-bindgen-shared 0.2.92 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/shared +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/shared Copyright (c) 2014 Alex Crichton @@ -10758,7 +10762,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- web-sys 0.3.69 - MIT OR Apache-2.0 -https://github.com/rustwasm/wasm-bindgen/tree/master/crates/web-sys +https://github.com/wasm-bindgen/wasm-bindgen/tree/master/crates/web-sys Copyright (c) 2014 Alex Crichton @@ -11510,7 +11514,7 @@ ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation a --------------------------------------------------------- zbus 3.15.2 - MIT -https://github.com/dbus2/zbus/ +https://github.com/z-galaxy/zbus/ The MIT License (MIT) @@ -11544,7 +11548,7 @@ DEALINGS IN THE SOFTWARE. --------------------------------------------------------- zbus_macros 3.15.2 - MIT -https://github.com/dbus2/zbus/ +https://github.com/z-galaxy/zbus/ The MIT License (MIT) @@ -11580,33 +11584,7 @@ DEALINGS IN THE SOFTWARE. zbus_names 2.6.1 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Copyright (c) 2024 Zeeshan Ali Khan & zbus contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -11718,7 +11696,7 @@ ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation a --------------------------------------------------------- zeroize 1.7.0 - Apache-2.0 OR MIT -https://github.com/RustCrypto/utils/tree/master/zeroize +https://github.com/RustCrypto/utils All crates licensed under either of @@ -11902,33 +11880,7 @@ licences; see files named LICENSE.*.txt for details. zvariant 3.15.2 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Copyright (c) 2024 Zeeshan Ali Khan & zbus contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -11936,33 +11888,7 @@ DEALINGS IN THE SOFTWARE. zvariant_derive 3.15.2 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Copyright (c) 2024 Zeeshan Ali Khan & zbus contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- --------------------------------------------------------- @@ -11970,31 +11896,5 @@ DEALINGS IN THE SOFTWARE. zvariant_utils 1.0.1 - MIT https://github.com/dbus2/zbus/ -The MIT License (MIT) - -Copyright (c) 2024 Zeeshan Ali Khan & zbus contributors - -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +LICENSE-MIT --------------------------------------------------------- \ No newline at end of file diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index 52c5af6d7d42c..6301bdd3104e5 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -686,6 +686,10 @@ pub struct BaseServerArgs { /// Set the root path for extensions. #[clap(long)] pub extensions_dir: Option, + + /// Reconnection grace time in seconds. Defaults to 10800 (3 hours). + #[clap(long)] + pub reconnection_grace_time: Option, } impl BaseServerArgs { @@ -700,6 +704,10 @@ impl BaseServerArgs { if let Some(d) = &self.extensions_dir { csa.extensions_dir = Some(d.clone()); } + + if let Some(t) = self.reconnection_grace_time { + csa.reconnection_grace_time = Some(t); + } } } diff --git a/cli/src/tunnels/code_server.rs b/cli/src/tunnels/code_server.rs index cf00bc4283581..bbabadcf90a20 100644 --- a/cli/src/tunnels/code_server.rs +++ b/cli/src/tunnels/code_server.rs @@ -74,6 +74,8 @@ pub struct CodeServerArgs { pub connection_token: Option, pub connection_token_file: Option, pub without_connection_token: bool, + // reconnection + pub reconnection_grace_time: Option, } impl CodeServerArgs { @@ -120,6 +122,9 @@ impl CodeServerArgs { if let Some(i) = self.log { args.push(format!("--log={i}")); } + if let Some(t) = self.reconnection_grace_time { + args.push(format!("--reconnection-grace-time={t}")); + } for extension in &self.install_extensions { args.push(format!("--install-extension={extension}")); diff --git a/cli/src/update_service.rs b/cli/src/update_service.rs index 9033914818804..55f1dadccdf67 100644 --- a/cli/src/update_service.rs +++ b/cli/src/update_service.rs @@ -56,8 +56,15 @@ fn quality_download_segment(quality: options::Quality) -> &'static str { } } -fn get_update_endpoint() -> Result<&'static str, CodeError> { - VSCODE_CLI_UPDATE_ENDPOINT.ok_or_else(|| CodeError::UpdatesNotConfigured("no service url")) +fn get_update_endpoint() -> Result { + if let Ok(url) = std::env::var("VSCODE_CLI_UPDATE_URL") { + if !url.is_empty() { + return Ok(url); + } + } + VSCODE_CLI_UPDATE_ENDPOINT + .map(|s| s.to_string()) + .ok_or_else(|| CodeError::UpdatesNotConfigured("no service url")) } impl UpdateService { @@ -78,7 +85,7 @@ impl UpdateService { .ok_or_else(|| CodeError::UnsupportedPlatform(platform.to_string()))?; let download_url = format!( "{}/api/versions/{}/{}/{}", - update_endpoint, + &update_endpoint, version, download_segment, quality_download_segment(quality), @@ -119,7 +126,7 @@ impl UpdateService { .ok_or_else(|| CodeError::UnsupportedPlatform(platform.to_string()))?; let download_url = format!( "{}/api/latest/{}/{}", - update_endpoint, + &update_endpoint, download_segment, quality_download_segment(quality), ); @@ -156,7 +163,7 @@ impl UpdateService { let download_url = format!( "{}/commit:{}/{}/{}", - update_endpoint, + &update_endpoint, release.commit, download_segment, quality_download_segment(release.quality), diff --git a/eslint.config.js b/eslint.config.js index b1e83f734e8bb..2955c68a9e4d0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,17 +6,15 @@ import fs from 'fs'; import path from 'path'; import tseslint from 'typescript-eslint'; -import { fileURLToPath } from 'url'; import stylisticTs from '@stylistic/eslint-plugin-ts'; -import * as pluginLocal from './.eslint-plugin-local/index.js'; +import * as pluginLocal from './.eslint-plugin-local/index.ts'; import pluginJsdoc from 'eslint-plugin-jsdoc'; import pluginHeader from 'eslint-plugin-header'; pluginHeader.rules.header.meta.schema = false; -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const ignores = fs.readFileSync(path.join(__dirname, '.eslint-ignore'), 'utf8') +const ignores = fs.readFileSync(path.join(import.meta.dirname, '.eslint-ignore'), 'utf8') .toString() .split(/\r\n|\n/) .filter(line => line && !line.startsWith('#')); @@ -77,17 +75,23 @@ export default tseslint.config( 'context' ], // non-complete list of globals that are easy to access unintentionally 'no-var': 'warn', - 'semi': 'off', + 'semi': 'warn', 'local/code-translation-remind': 'warn', 'local/code-no-native-private': 'warn', 'local/code-parameter-properties-must-have-explicit-accessibility': 'warn', 'local/code-no-nls-in-standalone-editor': 'warn', 'local/code-no-potentially-unsafe-disposables': 'warn', 'local/code-no-dangerous-type-assertions': 'warn', + 'local/code-no-any-casts': 'warn', 'local/code-no-standalone-editor': 'warn', 'local/code-no-unexternalized-strings': 'warn', 'local/code-must-use-super-dispose': 'warn', 'local/code-declare-service-brand': 'warn', + 'local/code-no-reader-after-await': 'warn', + 'local/code-no-observable-get-in-reactive-context': 'warn', + 'local/code-no-localized-model-description': 'warn', + 'local/code-policy-localization-key-match': 'warn', + 'local/code-no-localization-template-literals': 'error', 'local/code-no-deep-import-of-internal': ['error', { '.*Internal': true, 'searchExtTypesInternal': false }], 'local/code-layering': [ 'warn', @@ -129,7 +133,7 @@ export default tseslint.config( // TS { files: [ - '**/*.ts', + '**/*.{ts,tsx,mts,cts}', ], languageOptions: { parser: tseslint.parser, @@ -141,6 +145,8 @@ export default tseslint.config( 'jsdoc': pluginJsdoc, }, rules: { + // Disable built-in semi rules in favor of stylistic + 'semi': 'off', '@stylistic/ts/semi': 'warn', '@stylistic/ts/member-delimiter-style': 'warn', 'local/code-no-unused-expressions': [ @@ -176,6 +182,618 @@ export default tseslint.config( ] } }, + // Disallow 'in' operator except in type predicates + { + files: [ + '**/*.ts', + '.eslint-plugin-local/**/*.ts', // Explicitly include files under dot directories + ], + ignores: [ + 'src/bootstrap-node.ts', + 'build/lib/extensions.ts', + 'build/lib/test/render.test.ts', + 'extensions/debug-auto-launch/src/extension.ts', + 'extensions/emmet/src/updateImageSize.ts', + 'extensions/emmet/src/util.ts', + 'extensions/github-authentication/src/node/fetch.ts', + 'extensions/terminal-suggest/src/fig/figInterface.ts', + 'extensions/terminal-suggest/src/fig/fig-autocomplete-shared/mixins.ts', + 'extensions/terminal-suggest/src/fig/fig-autocomplete-shared/specMetadata.ts', + 'extensions/terminal-suggest/src/terminalSuggestMain.ts', + 'extensions/terminal-suggest/src/test/env/pathExecutableCache.test.ts', + 'extensions/tunnel-forwarding/src/extension.ts', + 'extensions/typescript-language-features/src/utils/platform.ts', + 'extensions/typescript-language-features/web/src/webServer.ts', + 'src/vs/base/browser/broadcast.ts', + 'src/vs/base/browser/canIUse.ts', + 'src/vs/base/browser/dom.ts', + 'src/vs/base/browser/markdownRenderer.ts', + 'src/vs/base/browser/touch.ts', + 'src/vs/base/common/async.ts', + 'src/vs/base/common/desktopEnvironmentInfo.ts', + 'src/vs/base/common/objects.ts', + 'src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts', + 'src/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts', + 'src/vs/base/test/common/snapshot.ts', + 'src/vs/base/test/common/timeTravelScheduler.ts', + 'src/vs/editor/browser/controller/editContext/native/debugEditContext.ts', + 'src/vs/editor/browser/gpu/gpuUtils.ts', + 'src/vs/editor/browser/gpu/taskQueue.ts', + 'src/vs/editor/browser/view.ts', + 'src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts', + 'src/vs/editor/browser/widget/diffEditor/utils.ts', + 'src/vs/editor/browser/widget/multiDiffEditor/multiDiffEditorWidgetImpl.ts', + 'src/vs/editor/common/config/editorOptions.ts', + 'src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteContribution.ts', + 'src/vs/editor/contrib/dropOrPasteInto/browser/copyPasteController.ts', + 'src/vs/editor/contrib/dropOrPasteInto/browser/edit.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/model/provideInlineCompletions.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts', + 'src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts', + 'src/vs/platform/configuration/common/configuration.ts', + 'src/vs/platform/configuration/common/configurationModels.ts', + 'src/vs/platform/contextkey/browser/contextKeyService.ts', + 'src/vs/platform/contextkey/test/common/scanner.test.ts', + 'src/vs/platform/dataChannel/browser/forwardingTelemetryService.ts', + 'src/vs/platform/hover/browser/hoverService.ts', + 'src/vs/platform/hover/browser/hoverWidget.ts', + 'src/vs/platform/instantiation/common/instantiationService.ts', + 'src/vs/platform/mcp/common/mcpManagementCli.ts', + 'src/vs/workbench/api/browser/mainThreadChatSessions.ts', + 'src/vs/workbench/api/browser/mainThreadDebugService.ts', + 'src/vs/workbench/api/browser/mainThreadTesting.ts', + 'src/vs/workbench/api/common/extHost.api.impl.ts', + 'src/vs/workbench/api/common/extHostChatAgents2.ts', + 'src/vs/workbench/api/common/extHostChatSessions.ts', + 'src/vs/workbench/api/common/extHostDebugService.ts', + 'src/vs/workbench/api/common/extHostNotebookKernels.ts', + 'src/vs/workbench/api/common/extHostQuickOpen.ts', + 'src/vs/workbench/api/common/extHostRequireInterceptor.ts', + 'src/vs/workbench/api/common/extHostTypeConverters.ts', + 'src/vs/workbench/api/common/extHostTypes.ts', + 'src/vs/workbench/api/node/loopbackServer.ts', + 'src/vs/workbench/api/node/proxyResolver.ts', + 'src/vs/workbench/api/test/common/extHostTypeConverters.test.ts', + 'src/vs/workbench/api/test/common/testRPCProtocol.ts', + 'src/vs/workbench/api/worker/extHostExtensionService.ts', + 'src/vs/workbench/browser/parts/paneCompositeBar.ts', + 'src/vs/workbench/browser/parts/titlebar/titlebarPart.ts', + 'src/vs/workbench/browser/workbench.ts', + 'src/vs/workbench/common/notifications.ts', + 'src/vs/workbench/contrib/accessibility/browser/accessibleView.ts', + 'src/vs/workbench/contrib/chat/browser/chatAttachmentResolveService.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatAttachmentsContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatConfirmationWidget.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatElicitationContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatReferencesContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/chatTreeContentPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/abstractToolConfirmationSubPart.ts', + 'src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts', + 'src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSessionStorage.ts', + 'src/vs/workbench/contrib/chat/browser/chatInlineAnchorWidget.ts', + 'src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts', + 'src/vs/workbench/contrib/chat/browser/contrib/chatInputCompletions.ts', + 'src/vs/workbench/contrib/chat/common/chatModel.ts', + 'src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.test.ts', + 'src/vs/workbench/contrib/chat/test/common/promptSyntax/testUtils/mockFilesystem.ts', + 'src/vs/workbench/contrib/chat/test/common/tools/manageTodoListTool.test.ts', + 'src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts', + 'src/vs/workbench/contrib/debug/browser/variablesView.ts', + 'src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts', + 'src/vs/workbench/contrib/debug/common/debugModel.ts', + 'src/vs/workbench/contrib/debug/common/debugger.ts', + 'src/vs/workbench/contrib/debug/common/replAccessibilityAnnouncer.ts', + 'src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts', + 'src/vs/workbench/contrib/editTelemetry/browser/helpers/documentWithAnnotatedEdits.ts', + 'src/vs/workbench/contrib/extensions/common/extensionQuery.ts', + 'src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts', + 'src/vs/workbench/contrib/issue/browser/issueFormService.ts', + 'src/vs/workbench/contrib/issue/browser/issueQuickAccess.ts', + 'src/vs/workbench/contrib/markers/browser/markersView.ts', + 'src/vs/workbench/contrib/mcp/browser/mcpElicitationService.ts', + 'src/vs/workbench/contrib/mcp/common/mcpLanguageModelToolContribution.ts', + 'src/vs/workbench/contrib/mcp/common/mcpResourceFilesystem.ts', + 'src/vs/workbench/contrib/mcp/common/mcpSamplingLog.ts', + 'src/vs/workbench/contrib/mcp/common/mcpServer.ts', + 'src/vs/workbench/contrib/mcp/common/mcpServerRequestHandler.ts', + 'src/vs/workbench/contrib/mcp/test/common/mcpRegistryTypes.ts', + 'src/vs/workbench/contrib/mcp/test/common/mcpServerRequestHandler.test.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/cellOutputActions.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/chat/notebook.chat.contribution.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts', + 'src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts', + 'src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts', + 'src/vs/workbench/contrib/output/browser/outputView.ts', + 'src/vs/workbench/contrib/preferences/browser/settingsTree.ts', + 'src/vs/workbench/contrib/remoteTunnel/electron-browser/remoteTunnel.contribution.ts', + 'src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts', + 'src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts', + 'src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts', + 'src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/taskHelpers.ts', + 'src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/monitoring/outputMonitor.ts', + 'src/vs/workbench/contrib/testing/browser/explorerProjections/listProjection.ts', + 'src/vs/workbench/contrib/testing/browser/explorerProjections/treeProjection.ts', + 'src/vs/workbench/contrib/testing/browser/testCoverageBars.ts', + 'src/vs/workbench/contrib/testing/browser/testExplorerActions.ts', + 'src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts', + 'src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts', + 'src/vs/workbench/contrib/testing/browser/testResultsView/testResultsTree.ts', + 'src/vs/workbench/contrib/testing/common/testCoverageService.ts', + 'src/vs/workbench/contrib/testing/common/testResultService.ts', + 'src/vs/workbench/contrib/testing/common/testingChatAgentTool.ts', + 'src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts', + 'src/vs/workbench/contrib/themes/browser/themes.contribution.ts', + 'src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.contribution.ts', + 'src/vs/workbench/services/environment/electron-browser/environmentService.ts', + 'src/vs/workbench/services/keybinding/common/keybindingIO.ts', + 'src/vs/workbench/services/preferences/common/preferencesValidation.ts', + 'src/vs/workbench/services/remote/common/tunnelModel.ts', + 'src/vs/workbench/services/search/common/textSearchManager.ts', + 'src/vs/workbench/test/browser/workbenchTestServices.ts', + 'test/automation/src/playwrightDriver.ts', + '.eslint-plugin-local/**/*', + ], + plugins: { + 'local': pluginLocal, + }, + rules: { + 'local/code-no-in-operator': 'warn', + } + }, + // Strict no explicit `any` + { + files: [ + // Extensions + 'extensions/git/src/**/*.ts', + 'extensions/git-base/src/**/*.ts', + 'extensions/github/src/**/*.ts', + // vscode + 'src/**/*.ts', + ], + ignores: [ + // Extensions + 'extensions/git/src/commands.ts', + 'extensions/git/src/decorators.ts', + 'extensions/git/src/git.ts', + 'extensions/git/src/util.ts', + 'extensions/git-base/src/decorators.ts', + 'extensions/github/src/util.ts', + // vscode d.ts + 'src/vs/amdX.ts', + 'src/vs/monaco.d.ts', + 'src/vscode-dts/**', + // Base + 'src/vs/base/browser/dom.ts', + 'src/vs/base/browser/mouseEvent.ts', + 'src/vs/base/node/processes.ts', + 'src/vs/base/common/arrays.ts', + 'src/vs/base/common/async.ts', + 'src/vs/base/common/console.ts', + 'src/vs/base/common/decorators.ts', + 'src/vs/base/common/errorMessage.ts', + 'src/vs/base/common/errors.ts', + 'src/vs/base/common/event.ts', + 'src/vs/base/common/hotReload.ts', + 'src/vs/base/common/hotReloadHelpers.ts', + 'src/vs/base/common/json.ts', + 'src/vs/base/common/jsonSchema.ts', + 'src/vs/base/common/lifecycle.ts', + 'src/vs/base/common/map.ts', + 'src/vs/base/common/marshalling.ts', + 'src/vs/base/common/objects.ts', + 'src/vs/base/common/performance.ts', + 'src/vs/base/common/platform.ts', + 'src/vs/base/common/processes.ts', + 'src/vs/base/common/types.ts', + 'src/vs/base/common/uriIpc.ts', + 'src/vs/base/common/verifier.ts', + 'src/vs/base/common/observableInternal/base.ts', + 'src/vs/base/common/observableInternal/changeTracker.ts', + 'src/vs/base/common/observableInternal/set.ts', + 'src/vs/base/common/observableInternal/transaction.ts', + 'src/vs/base/common/worker/webWorkerBootstrap.ts', + 'src/vs/base/test/common/mock.ts', + 'src/vs/base/test/common/snapshot.ts', + 'src/vs/base/test/common/timeTravelScheduler.ts', + 'src/vs/base/test/common/troubleshooting.ts', + 'src/vs/base/test/common/utils.ts', + 'src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts', + 'src/vs/base/browser/ui/grid/grid.ts', + 'src/vs/base/browser/ui/grid/gridview.ts', + 'src/vs/base/browser/ui/list/listPaging.ts', + 'src/vs/base/browser/ui/list/listView.ts', + 'src/vs/base/browser/ui/list/listWidget.ts', + 'src/vs/base/browser/ui/list/rowCache.ts', + 'src/vs/base/browser/ui/sash/sash.ts', + 'src/vs/base/browser/ui/table/tableWidget.ts', + 'src/vs/base/parts/ipc/common/ipc.net.ts', + 'src/vs/base/parts/ipc/common/ipc.ts', + 'src/vs/base/parts/ipc/electron-main/ipcMain.ts', + 'src/vs/base/parts/ipc/node/ipc.cp.ts', + 'src/vs/base/common/observableInternal/experimental/reducer.ts', + 'src/vs/base/common/observableInternal/experimental/utils.ts', + 'src/vs/base/common/observableInternal/logging/consoleObservableLogger.ts', + 'src/vs/base/common/observableInternal/logging/debugGetDependencyGraph.ts', + 'src/vs/base/common/observableInternal/logging/logging.ts', + 'src/vs/base/common/observableInternal/observables/baseObservable.ts', + 'src/vs/base/common/observableInternal/observables/derived.ts', + 'src/vs/base/common/observableInternal/observables/derivedImpl.ts', + 'src/vs/base/common/observableInternal/observables/observableFromEvent.ts', + 'src/vs/base/common/observableInternal/observables/observableSignalFromEvent.ts', + 'src/vs/base/common/observableInternal/reactions/autorunImpl.ts', + 'src/vs/base/common/observableInternal/utils/utils.ts', + 'src/vs/base/common/observableInternal/utils/utilsCancellation.ts', + 'src/vs/base/parts/ipc/test/node/testService.ts', + 'src/vs/base/common/observableInternal/logging/debugger/debuggerRpc.ts', + 'src/vs/base/common/observableInternal/logging/debugger/devToolsLogger.ts', + 'src/vs/base/common/observableInternal/logging/debugger/rpc.ts', + 'src/vs/base/test/browser/ui/grid/util.ts', + // Platform + 'src/vs/platform/commands/common/commands.ts', + 'src/vs/platform/contextkey/browser/contextKeyService.ts', + 'src/vs/platform/contextkey/common/contextkey.ts', + 'src/vs/platform/contextview/browser/contextView.ts', + 'src/vs/platform/debug/common/extensionHostDebugIpc.ts', + 'src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts', + 'src/vs/platform/diagnostics/common/diagnostics.ts', + 'src/vs/platform/download/common/downloadIpc.ts', + 'src/vs/platform/extensions/common/extensions.ts', + 'src/vs/platform/instantiation/common/descriptors.ts', + 'src/vs/platform/instantiation/common/extensions.ts', + 'src/vs/platform/instantiation/common/instantiation.ts', + 'src/vs/platform/instantiation/common/instantiationService.ts', + 'src/vs/platform/instantiation/common/serviceCollection.ts', + 'src/vs/platform/keybinding/common/keybinding.ts', + 'src/vs/platform/keybinding/common/keybindingResolver.ts', + 'src/vs/platform/keybinding/common/keybindingsRegistry.ts', + 'src/vs/platform/keybinding/common/resolvedKeybindingItem.ts', + 'src/vs/platform/languagePacks/node/languagePacks.ts', + 'src/vs/platform/list/browser/listService.ts', + 'src/vs/platform/log/browser/log.ts', + 'src/vs/platform/log/common/log.ts', + 'src/vs/platform/log/common/logIpc.ts', + 'src/vs/platform/log/electron-main/logIpc.ts', + 'src/vs/platform/observable/common/wrapInHotClass.ts', + 'src/vs/platform/observable/common/wrapInReloadableClass.ts', + 'src/vs/platform/policy/common/policyIpc.ts', + 'src/vs/platform/profiling/common/profilingTelemetrySpec.ts', + 'src/vs/platform/quickinput/browser/quickInputActions.ts', + 'src/vs/platform/quickinput/common/quickInput.ts', + 'src/vs/platform/registry/common/platform.ts', + 'src/vs/platform/remote/browser/browserSocketFactory.ts', + 'src/vs/platform/remote/browser/remoteAuthorityResolverService.ts', + 'src/vs/platform/remote/common/remoteAgentConnection.ts', + 'src/vs/platform/remote/common/remoteAuthorityResolver.ts', + 'src/vs/platform/remote/electron-browser/electronRemoteResourceLoader.ts', + 'src/vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts', + 'src/vs/platform/remoteTunnel/node/remoteTunnelService.ts', + 'src/vs/platform/request/common/request.ts', + 'src/vs/platform/request/common/requestIpc.ts', + 'src/vs/platform/request/electron-utility/requestService.ts', + 'src/vs/platform/request/node/proxy.ts', + 'src/vs/platform/telemetry/browser/errorTelemetry.ts', + 'src/vs/platform/telemetry/common/errorTelemetry.ts', + 'src/vs/platform/telemetry/common/remoteTelemetryChannel.ts', + 'src/vs/platform/telemetry/node/errorTelemetry.ts', + 'src/vs/platform/theme/common/iconRegistry.ts', + 'src/vs/platform/theme/common/tokenClassificationRegistry.ts', + 'src/vs/platform/update/common/updateIpc.ts', + 'src/vs/platform/update/electron-main/updateService.snap.ts', + 'src/vs/platform/url/common/urlIpc.ts', + 'src/vs/platform/userDataProfile/common/userDataProfileIpc.ts', + 'src/vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc.ts', + 'src/vs/platform/userDataSync/common/abstractSynchronizer.ts', + 'src/vs/platform/userDataSync/common/extensionsMerge.ts', + 'src/vs/platform/userDataSync/common/extensionsSync.ts', + 'src/vs/platform/userDataSync/common/globalStateMerge.ts', + 'src/vs/platform/userDataSync/common/globalStateSync.ts', + 'src/vs/platform/userDataSync/common/settingsMerge.ts', + 'src/vs/platform/userDataSync/common/settingsSync.ts', + 'src/vs/platform/userDataSync/common/userDataSync.ts', + 'src/vs/platform/userDataSync/common/userDataSyncIpc.ts', + 'src/vs/platform/userDataSync/common/userDataSyncServiceIpc.ts', + 'src/vs/platform/webview/common/webviewManagerService.ts', + 'src/vs/platform/instantiation/test/common/instantiationServiceMock.ts', + 'src/vs/platform/keybinding/test/common/mockKeybindingService.ts', + // Editor + 'src/vs/editor/standalone/browser/standaloneEditor.ts', + 'src/vs/editor/standalone/browser/standaloneLanguages.ts', + 'src/vs/editor/standalone/browser/standaloneServices.ts', + 'src/vs/editor/test/browser/testCodeEditor.ts', + 'src/vs/editor/test/common/testTextModel.ts', + 'src/vs/editor/contrib/bracketMatching/browser/bracketMatching.ts', + 'src/vs/editor/contrib/codeAction/browser/codeAction.ts', + 'src/vs/editor/contrib/codeAction/browser/codeActionCommands.ts', + 'src/vs/editor/contrib/codeAction/common/types.ts', + 'src/vs/editor/contrib/colorPicker/browser/colorDetector.ts', + 'src/vs/editor/contrib/diffEditorBreadcrumbs/browser/contribution.ts', + 'src/vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorContribution.ts', + 'src/vs/editor/contrib/find/browser/findController.ts', + 'src/vs/editor/contrib/find/browser/findModel.ts', + 'src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts', + 'src/vs/editor/contrib/gotoSymbol/browser/symbolNavigation.ts', + 'src/vs/editor/contrib/hover/browser/hoverActions.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/structuredLogger.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/utils.ts', + 'src/vs/editor/contrib/smartSelect/browser/smartSelect.ts', + 'src/vs/editor/contrib/stickyScroll/browser/stickyScrollModelProvider.ts', + 'src/vs/editor/contrib/unicodeHighlighter/browser/unicodeHighlighter.ts', + 'src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts', + 'src/vs/editor/standalone/common/monarch/monarchCommon.ts', + 'src/vs/editor/standalone/common/monarch/monarchCompile.ts', + 'src/vs/editor/standalone/common/monarch/monarchLexer.ts', + 'src/vs/editor/standalone/common/monarch/monarchTypes.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/controller/commands.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/model/typingSpeed.ts', + 'src/vs/editor/contrib/inlineCompletions/test/browser/utils.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/view/ghostText/ghostTextView.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/components/gutterIndicatorView.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/inlineEditsViews/debugVisualization.ts', + 'src/vs/editor/contrib/inlineCompletions/browser/view/inlineEdits/utils/utils.ts', + // Workbench + 'src/vs/workbench/api/browser/mainThreadChatSessions.ts', + 'src/vs/workbench/api/common/extHost.api.impl.ts', + 'src/vs/workbench/api/common/extHost.protocol.ts', + 'src/vs/workbench/api/common/extHostChatSessions.ts', + 'src/vs/workbench/api/common/extHostCodeInsets.ts', + 'src/vs/workbench/api/common/extHostCommands.ts', + 'src/vs/workbench/api/common/extHostConsoleForwarder.ts', + 'src/vs/workbench/api/common/extHostDataChannels.ts', + 'src/vs/workbench/api/common/extHostDebugService.ts', + 'src/vs/workbench/api/common/extHostExtensionActivator.ts', + 'src/vs/workbench/api/common/extHostExtensionService.ts', + 'src/vs/workbench/api/common/extHostFileSystemConsumer.ts', + 'src/vs/workbench/api/common/extHostFileSystemEventService.ts', + 'src/vs/workbench/api/common/extHostLanguageFeatures.ts', + 'src/vs/workbench/api/common/extHostLanguageModelTools.ts', + 'src/vs/workbench/api/common/extHostMcp.ts', + 'src/vs/workbench/api/common/extHostMemento.ts', + 'src/vs/workbench/api/common/extHostMessageService.ts', + 'src/vs/workbench/api/common/extHostNotebookDocument.ts', + 'src/vs/workbench/api/common/extHostNotebookDocumentSaveParticipant.ts', + 'src/vs/workbench/api/common/extHostRequireInterceptor.ts', + 'src/vs/workbench/api/common/extHostRpcService.ts', + 'src/vs/workbench/api/common/extHostSCM.ts', + 'src/vs/workbench/api/common/extHostSearch.ts', + 'src/vs/workbench/api/common/extHostStatusBar.ts', + 'src/vs/workbench/api/common/extHostStoragePaths.ts', + 'src/vs/workbench/api/common/extHostTelemetry.ts', + 'src/vs/workbench/api/common/extHostTesting.ts', + 'src/vs/workbench/api/common/extHostTextEditor.ts', + 'src/vs/workbench/api/common/extHostTimeline.ts', + 'src/vs/workbench/api/common/extHostTreeViews.ts', + 'src/vs/workbench/api/common/extHostTypeConverters.ts', + 'src/vs/workbench/api/common/extHostTypes.ts', + 'src/vs/workbench/api/common/extHostTypes/es5ClassCompat.ts', + 'src/vs/workbench/api/common/extHostTypes/location.ts', + 'src/vs/workbench/api/common/extHostWebview.ts', + 'src/vs/workbench/api/common/extHostWebviewMessaging.ts', + 'src/vs/workbench/api/common/extHostWebviewPanels.ts', + 'src/vs/workbench/api/common/extHostWebviewView.ts', + 'src/vs/workbench/api/common/extHostWorkspace.ts', + 'src/vs/workbench/api/common/extensionHostMain.ts', + 'src/vs/workbench/api/common/shared/tasks.ts', + 'src/vs/workbench/api/node/extHostAuthentication.ts', + 'src/vs/workbench/api/node/extHostCLIServer.ts', + 'src/vs/workbench/api/node/extHostConsoleForwarder.ts', + 'src/vs/workbench/api/node/extHostDownloadService.ts', + 'src/vs/workbench/api/node/extHostExtensionService.ts', + 'src/vs/workbench/api/node/extHostMcpNode.ts', + 'src/vs/workbench/api/node/extensionHostProcess.ts', + 'src/vs/workbench/api/node/proxyResolver.ts', + 'src/vs/workbench/api/test/common/testRPCProtocol.ts', + 'src/vs/workbench/api/worker/extHostConsoleForwarder.ts', + 'src/vs/workbench/api/worker/extHostExtensionService.ts', + 'src/vs/workbench/api/worker/extensionHostWorker.ts', + 'src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts', + 'src/vs/workbench/contrib/accessibilitySignals/browser/commands.ts', + 'src/vs/workbench/contrib/authentication/browser/actions/manageTrustedMcpServersForAccountAction.ts', + 'src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts', + 'src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts', + 'src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPreview.ts', + 'src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts', + 'src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts', + 'src/vs/workbench/contrib/codeEditor/electron-browser/selectionClipboard.ts', + 'src/vs/workbench/contrib/commands/common/commands.contribution.ts', + 'src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts', + 'src/vs/workbench/contrib/comments/browser/commentsView.ts', + 'src/vs/workbench/contrib/comments/browser/reactionsAction.ts', + 'src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts', + 'src/vs/workbench/contrib/customEditor/browser/customEditors.ts', + 'src/vs/workbench/contrib/customEditor/common/customEditor.ts', + 'src/vs/workbench/contrib/debug/browser/debugActionViewItems.ts', + 'src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts', + 'src/vs/workbench/contrib/debug/browser/debugCommands.ts', + 'src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts', + 'src/vs/workbench/contrib/debug/browser/debugEditorActions.ts', + 'src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts', + 'src/vs/workbench/contrib/debug/browser/debugHover.ts', + 'src/vs/workbench/contrib/debug/browser/debugService.ts', + 'src/vs/workbench/contrib/debug/browser/debugSession.ts', + 'src/vs/workbench/contrib/debug/browser/rawDebugSession.ts', + 'src/vs/workbench/contrib/debug/browser/repl.ts', + 'src/vs/workbench/contrib/debug/browser/replViewer.ts', + 'src/vs/workbench/contrib/debug/browser/variablesView.ts', + 'src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts', + 'src/vs/workbench/contrib/debug/common/abstractDebugAdapter.ts', + 'src/vs/workbench/contrib/debug/common/debugger.ts', + 'src/vs/workbench/contrib/debug/common/replModel.ts', + 'src/vs/workbench/contrib/debug/test/common/mockDebug.ts', + 'src/vs/workbench/contrib/editSessions/common/workspaceStateSync.ts', + 'src/vs/workbench/contrib/editTelemetry/browser/helpers/documentWithAnnotatedEdits.ts', + 'src/vs/workbench/contrib/editTelemetry/browser/helpers/utils.ts', + 'src/vs/workbench/contrib/editTelemetry/browser/telemetry/arcTelemetrySender.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionEditor.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionRecommendationNotificationService.ts', + 'src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionsActions.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionsActivationProgress.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionsViews.ts', + 'src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts', + 'src/vs/workbench/contrib/extensions/common/extensions.ts', + 'src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts', + 'src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts', + 'src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts', + 'src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts', + 'src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts', + 'src/vs/workbench/contrib/markers/browser/markers.contribution.ts', + 'src/vs/workbench/contrib/markers/browser/markersView.ts', + 'src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts', + 'src/vs/workbench/contrib/mergeEditor/browser/utils.ts', + 'src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts', + 'src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts', + 'src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/chat/cellChatActions.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/editActions.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/notebookIndentationActions.ts', + 'src/vs/workbench/contrib/notebook/browser/controller/sectionActions.ts', + 'src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts', + 'src/vs/workbench/contrib/notebook/browser/diff/inlineDiff/notebookDeletedCellDecorator.ts', + 'src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts', + 'src/vs/workbench/contrib/notebook/browser/outputEditor/notebookOutputEditor.ts', + 'src/vs/workbench/contrib/notebook/browser/services/notebookEditorServiceImpl.ts', + 'src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts', + 'src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts', + 'src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts', + 'src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts', + 'src/vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel.ts', + 'src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts', + 'src/vs/workbench/contrib/notebook/browser/viewParts/notebookHorizontalTracker.ts', + 'src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts', + 'src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts', + 'src/vs/workbench/contrib/notebook/common/model/notebookMetadataTextModel.ts', + 'src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts', + 'src/vs/workbench/contrib/notebook/common/notebookCommon.ts', + 'src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts', + 'src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts', + 'src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts', + 'src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts', + 'src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts', + 'src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts', + 'src/vs/workbench/contrib/preferences/browser/settingsTree.ts', + 'src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts', + 'src/vs/workbench/contrib/remote/browser/tunnelView.ts', + 'src/vs/workbench/contrib/search/browser/AISearch/aiSearchModel.ts', + 'src/vs/workbench/contrib/search/browser/AISearch/aiSearchModelBase.ts', + 'src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchModel.ts', + 'src/vs/workbench/contrib/search/browser/notebookSearch/notebookSearchModelBase.ts', + 'src/vs/workbench/contrib/search/browser/notebookSearch/searchNotebookHelpers.ts', + 'src/vs/workbench/contrib/search/browser/replace.ts', + 'src/vs/workbench/contrib/search/browser/replaceService.ts', + 'src/vs/workbench/contrib/search/browser/searchActionsCopy.ts', + 'src/vs/workbench/contrib/search/browser/searchActionsFind.ts', + 'src/vs/workbench/contrib/search/browser/searchActionsNav.ts', + 'src/vs/workbench/contrib/search/browser/searchActionsRemoveReplace.ts', + 'src/vs/workbench/contrib/search/browser/searchActionsTextQuickAccess.ts', + 'src/vs/workbench/contrib/search/browser/searchActionsTopBar.ts', + 'src/vs/workbench/contrib/search/browser/searchMessage.ts', + 'src/vs/workbench/contrib/search/browser/searchResultsView.ts', + 'src/vs/workbench/contrib/search/browser/searchTreeModel/fileMatch.ts', + 'src/vs/workbench/contrib/search/browser/searchTreeModel/folderMatch.ts', + 'src/vs/workbench/contrib/search/browser/searchTreeModel/searchModel.ts', + 'src/vs/workbench/contrib/search/browser/searchTreeModel/searchResult.ts', + 'src/vs/workbench/contrib/search/browser/searchTreeModel/searchTreeCommon.ts', + 'src/vs/workbench/contrib/search/browser/searchTreeModel/textSearchHeading.ts', + 'src/vs/workbench/contrib/search/browser/searchView.ts', + 'src/vs/workbench/contrib/search/test/browser/mockSearchTree.ts', + 'src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts', + 'src/vs/workbench/contrib/searchEditor/browser/searchEditorActions.ts', + 'src/vs/workbench/contrib/searchEditor/browser/searchEditorInput.ts', + 'src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts', + 'src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts', + 'src/vs/workbench/contrib/snippets/browser/snippetsService.ts', + 'src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts', + 'src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts', + 'src/vs/workbench/contrib/tasks/browser/task.contribution.ts', + 'src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts', + 'src/vs/workbench/contrib/tasks/common/jsonSchema_v1.ts', + 'src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts', + 'src/vs/workbench/contrib/tasks/common/problemMatcher.ts', + 'src/vs/workbench/contrib/tasks/common/taskConfiguration.ts', + 'src/vs/workbench/contrib/tasks/common/taskSystem.ts', + 'src/vs/workbench/contrib/tasks/common/tasks.ts', + 'src/vs/workbench/contrib/testing/common/storedValue.ts', + 'src/vs/workbench/contrib/testing/test/browser/testObjectTree.ts', + 'src/vs/workbench/contrib/typeHierarchy/browser/typeHierarchy.contribution.ts', + 'src/vs/workbench/contrib/typeHierarchy/common/typeHierarchy.ts', + 'src/vs/workbench/contrib/webview/browser/overlayWebview.ts', + 'src/vs/workbench/contrib/webview/browser/webview.ts', + 'src/vs/workbench/contrib/webview/browser/webviewElement.ts', + 'src/vs/workbench/contrib/webviewPanel/browser/webviewEditor.ts', + 'src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts', + 'src/vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService.ts', + 'src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts', + 'src/vs/workbench/contrib/welcomeWalkthrough/browser/walkThroughPart.ts', + 'src/vs/workbench/services/authentication/common/authentication.ts', + 'src/vs/workbench/services/authentication/test/browser/authenticationQueryServiceMocks.ts', + 'src/vs/workbench/services/commands/common/commandService.ts', + 'src/vs/workbench/services/configurationResolver/common/configurationResolver.ts', + 'src/vs/workbench/services/configurationResolver/common/configurationResolverExpression.ts', + 'src/vs/workbench/services/extensions/common/extensionHostManager.ts', + 'src/vs/workbench/services/extensions/common/extensionsRegistry.ts', + 'src/vs/workbench/services/extensions/common/lazyPromise.ts', + 'src/vs/workbench/services/extensions/common/polyfillNestedWorker.protocol.ts', + 'src/vs/workbench/services/extensions/common/rpcProtocol.ts', + 'src/vs/workbench/services/extensions/worker/polyfillNestedWorker.ts', + 'src/vs/workbench/services/keybinding/browser/keybindingService.ts', + 'src/vs/workbench/services/keybinding/browser/keyboardLayoutService.ts', + 'src/vs/workbench/services/keybinding/common/keybindingEditing.ts', + 'src/vs/workbench/services/keybinding/common/keymapInfo.ts', + 'src/vs/workbench/services/language/common/languageService.ts', + 'src/vs/workbench/services/outline/browser/outline.ts', + 'src/vs/workbench/services/outline/browser/outlineService.ts', + 'src/vs/workbench/services/preferences/common/preferences.ts', + 'src/vs/workbench/services/preferences/common/preferencesModels.ts', + 'src/vs/workbench/services/preferences/common/preferencesValidation.ts', + 'src/vs/workbench/services/remote/common/tunnelModel.ts', + 'src/vs/workbench/services/search/common/replace.ts', + 'src/vs/workbench/services/search/common/search.ts', + 'src/vs/workbench/services/search/common/searchExtConversionTypes.ts', + 'src/vs/workbench/services/search/common/searchExtTypes.ts', + 'src/vs/workbench/services/search/node/fileSearch.ts', + 'src/vs/workbench/services/search/node/rawSearchService.ts', + 'src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts', + 'src/vs/workbench/services/textMate/common/TMGrammarFactory.ts', + 'src/vs/workbench/services/themes/browser/fileIconThemeData.ts', + 'src/vs/workbench/services/themes/browser/productIconThemeData.ts', + 'src/vs/workbench/services/themes/common/colorThemeData.ts', + 'src/vs/workbench/services/themes/common/plistParser.ts', + 'src/vs/workbench/services/themes/common/themeExtensionPoints.ts', + 'src/vs/workbench/services/themes/common/workbenchThemeService.ts', + 'src/vs/workbench/test/browser/workbenchTestServices.ts', + 'src/vs/workbench/test/common/workbenchTestServices.ts', + 'src/vs/workbench/test/electron-browser/workbenchTestServices.ts', + // Server + 'src/vs/server/node/remoteAgentEnvironmentImpl.ts', + 'src/vs/server/node/remoteExtensionHostAgentServer.ts', + 'src/vs/server/node/remoteExtensionsScanner.ts', + // Tests + '**/*.test.ts', + '**/*.integrationTest.ts' + ], + languageOptions: { + parser: tseslint.parser, + }, + plugins: { + '@typescript-eslint': tseslint.plugin, + }, + rules: { + '@typescript-eslint/no-explicit-any': [ + 'warn', + { + 'fixToUnknown': false + } + ] + } + }, // Tests { files: [ @@ -188,10 +806,10 @@ export default tseslint.config( 'local': pluginLocal, }, rules: { + 'local/code-no-dangerous-type-assertions': 'off', 'local/code-must-use-super-dispose': 'off', 'local/code-no-test-only': 'error', 'local/code-no-test-async-suite': 'warn', - 'local/code-no-unexternalized-strings': 'off', 'local/code-must-use-result': [ 'warn', [ @@ -309,7 +927,8 @@ export default tseslint.config( 'terminate', 'trigger', 'unregister', - 'write' + 'write', + 'commit' ] } ] @@ -564,6 +1183,34 @@ export default tseslint.config( { 'selector': `MemberExpression[object.name='document'][property.name='execCommand']`, 'message': 'Use .document.execCommand to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant.' + }, + { + 'selector': 'CallExpression[callee.property.name=\'querySelector\']', + 'message': 'querySelector should not be used as relying on selectors is very fragile. Use dom.ts h() to build your elements and access them directly.' + }, + { + 'selector': 'CallExpression[callee.property.name=\'querySelectorAll\']', + 'message': 'querySelectorAll should not be used as relying on selectors is very fragile. Use dom.ts h() to build your elements and access them directly.' + }, + { + 'selector': 'CallExpression[callee.property.name=\'getElementById\']', + 'message': 'getElementById should not be used as relying on selectors is very fragile. Use dom.ts h() to build your elements and access them directly.' + }, + { + 'selector': 'CallExpression[callee.property.name=\'getElementsByClassName\']', + 'message': 'getElementsByClassName should not be used as relying on selectors is very fragile. Use dom.ts h() to build your elements and access them directly.' + }, + { + 'selector': 'CallExpression[callee.property.name=\'getElementsByTagName\']', + 'message': 'getElementsByTagName should not be used as relying on selectors is very fragile. Use dom.ts h() to build your elements and access them directly.' + }, + { + 'selector': 'CallExpression[callee.property.name=\'getElementsByName\']', + 'message': 'getElementsByName should not be used as relying on selectors is very fragile. Use dom.ts h() to build your elements and access them directly.' + }, + { + 'selector': 'CallExpression[callee.property.name=\'getElementsByTagNameNS\']', + 'message': 'getElementsByTagNameNS should not be used as relying on selectors is very fragile. Use dom.ts h() to build your elements and access them directly.' } ], 'no-restricted-globals': [ @@ -765,6 +1412,17 @@ export default tseslint.config( 'local': pluginLocal, }, rules: { + 'no-restricted-imports': [ + 'warn', + { + 'patterns': [ + { + 'group': ['dompurify*'], + 'message': 'Use domSanitize instead of dompurify directly' + }, + ] + } + ], 'local/code-import-patterns': [ 'warn', { @@ -781,7 +1439,7 @@ export default tseslint.config( // - electron-main 'when': 'hasNode', 'allow': [ - '@parcel/watcher', + '@vscode/watcher', '@vscode/sqlite3', '@vscode/vscode-languagedetection', '@vscode/ripgrep', @@ -813,8 +1471,9 @@ export default tseslint.config( 'readline', 'stream', 'string_decoder', - 'tas-client-umd', + 'tas-client', 'tls', + 'undici', 'undici-types', 'url', 'util', @@ -901,7 +1560,7 @@ export default tseslint.config( 'vs/base/~', 'vs/base/parts/*/~', 'vs/platform/*/~', - 'tas-client-umd', // node module allowed even in /common/ + 'tas-client', // node module allowed even in /common/ '@microsoft/1ds-core-js', // node module allowed even in /common/ '@microsoft/1ds-post-js', // node module allowed even in /common/ '@xterm/headless' // node module allowed even in /common/ @@ -1019,7 +1678,7 @@ export default tseslint.config( 'when': 'test', 'pattern': 'vs/workbench/contrib/*/~' }, // TODO@layers - 'tas-client-umd', // node module allowed even in /common/ + 'tas-client', // node module allowed even in /common/ 'vscode-textmate', // node module allowed even in /common/ '@vscode/vscode-languagedetection', // node module allowed even in /common/ '@vscode/tree-sitter-wasm', // type import @@ -1073,6 +1732,7 @@ export default tseslint.config( // terminalContrib is one extra folder deep 'vs/workbench/contrib/terminalContrib/*/~', 'vscode-notebook-renderer', // Type only import + '@vscode/tree-sitter-wasm', // type import { 'when': 'hasBrowser', 'pattern': '@xterm/xterm' @@ -1198,7 +1858,7 @@ export default tseslint.config( 'vs/workbench/api/~', 'vs/workbench/services/*/~', 'vs/workbench/contrib/*/~', - 'vs/workbench/workbench.common.main.js' + 'vs/workbench/workbench.web.main.js' ] }, { @@ -1225,7 +1885,7 @@ export default tseslint.config( ] }, { - 'target': 'src/vs/{loader.d.ts,monaco.d.ts,nls.ts,nls.messages.ts}', + 'target': 'src/vs/{monaco.d.ts,nls.ts}', 'restrictions': [] }, { @@ -1270,7 +1930,6 @@ export default tseslint.config( 'test/automation', 'test/smoke/**', '@vscode/*', - '@parcel/*', '@playwright/*', '*' // node modules ] @@ -1280,7 +1939,6 @@ export default tseslint.config( 'restrictions': [ 'test/automation/**', '@vscode/*', - '@parcel/*', 'playwright-core/**', '@playwright/*', '*' // node modules @@ -1291,7 +1949,6 @@ export default tseslint.config( 'restrictions': [ 'test/integration/**', '@vscode/*', - '@parcel/*', '@playwright/*', '*' // node modules ] @@ -1301,10 +1958,20 @@ export default tseslint.config( 'restrictions': [ 'test/monaco/**', '@vscode/*', - '@parcel/*', '@playwright/*', '*' // node modules ] + }, + { + 'target': 'test/mcp/**', + 'restrictions': [ + 'test/automation', + 'test/mcp/**', + '@vscode/*', + '@playwright/*', + '@modelcontextprotocol/sdk/**/*', + '*' // node modules + ] } ] } @@ -1396,17 +2063,38 @@ export default tseslint.config( ] } }, - // typescript-language-features + // Additional extension strictness rules { files: [ + 'extensions/markdown-language-features/**/*.ts', + 'extensions/mermaid-chat-features/**/*.ts', + 'extensions/media-preview/**/*.ts', + 'extensions/simple-browser/**/*.ts', 'extensions/typescript-language-features/**/*.ts', ], languageOptions: { parser: tseslint.parser, parserOptions: { project: [ + // Markdown + 'extensions/markdown-language-features/tsconfig.json', + 'extensions/markdown-language-features/notebook/tsconfig.json', + 'extensions/markdown-language-features/preview-src/tsconfig.json', + + // Media preview + 'extensions/media-preview/tsconfig.json', + + // Media preview + 'extensions/simple-browser/tsconfig.json', + 'extensions/simple-browser/preview-src/tsconfig.json', + + // Mermaid chat features + 'extensions/mermaid-chat-features/tsconfig.json', + 'extensions/mermaid-chat-features/chat-webview-src/tsconfig.json', + + // TypeScript 'extensions/typescript-language-features/tsconfig.json', - 'extensions/typescript-language-features/web/tsconfig.json' + 'extensions/typescript-language-features/web/tsconfig.json', ], } }, @@ -1416,6 +2104,7 @@ export default tseslint.config( rules: { '@typescript-eslint/prefer-optional-chain': 'warn', '@typescript-eslint/prefer-readonly': 'warn', + '@typescript-eslint/consistent-generic-constructors': ['warn', 'constructor'], } }, ); diff --git a/extensions/configuration-editing/extension-browser.webpack.config.js b/extensions/configuration-editing/extension-browser.webpack.config.js index 51c7746415d6e..1136c92520837 100644 --- a/extensions/configuration-editing/extension-browser.webpack.config.js +++ b/extensions/configuration-editing/extension-browser.webpack.config.js @@ -2,16 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import path from 'path'; +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const path = require('path'); -const withBrowserDefaults = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, entry: { extension: './src/configurationEditingMain.ts' }, @@ -20,7 +16,7 @@ module.exports = withBrowserDefaults({ }, resolve: { alias: { - './node/net': path.resolve(__dirname, 'src', 'browser', 'net'), + './node/net': path.resolve(import.meta.dirname, 'src', 'browser', 'net'), } } }); diff --git a/extensions/configuration-editing/extension.webpack.config.js b/extensions/configuration-editing/extension.webpack.config.js index 1b18dd86a9911..519fc2e359f44 100644 --- a/extensions/configuration-editing/extension.webpack.config.js +++ b/extensions/configuration-editing/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { extension: './src/configurationEditingMain.ts', }, diff --git a/extensions/configuration-editing/tsconfig.json b/extensions/configuration-editing/tsconfig.json index 3013ee542271c..7106538eb99e7 100644 --- a/extensions/configuration-editing/tsconfig.json +++ b/extensions/configuration-editing/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/cpp/language-configuration.json b/extensions/cpp/language-configuration.json index cb1fb733b9998..a4468a758f967 100644 --- a/extensions/cpp/language-configuration.json +++ b/extensions/cpp/language-configuration.json @@ -93,8 +93,8 @@ "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", "folding": { "markers": { - "start": "^\\s*#pragma\\s+region\\b", - "end": "^\\s*#pragma\\s+endregion\\b" + "start": "^\\s*#\\s*pragma\\s+region\\b", + "end": "^\\s*#\\s*pragma\\s+endregion\\b" } }, "indentationRules": { diff --git a/extensions/csharp/cgmanifest.json b/extensions/csharp/cgmanifest.json index 58ae5ece50ae2..61e941c3488cb 100644 --- a/extensions/csharp/cgmanifest.json +++ b/extensions/csharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "dotnet/csharp-tmLanguage", "repositoryUrl": "https://github.com/dotnet/csharp-tmLanguage", - "commitHash": "1381bedfb087c18aca67af8278050d11bc9d9349" + "commitHash": "965478e687f08d3b2ee4fe17104d3f41638bdca2" } }, "license": "MIT", diff --git a/extensions/csharp/syntaxes/csharp.tmLanguage.json b/extensions/csharp/syntaxes/csharp.tmLanguage.json index 1afcc3053b603..b360a96cb65c4 100644 --- a/extensions/csharp/syntaxes/csharp.tmLanguage.json +++ b/extensions/csharp/syntaxes/csharp.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/dotnet/csharp-tmLanguage/commit/1381bedfb087c18aca67af8278050d11bc9d9349", + "version": "https://github.com/dotnet/csharp-tmLanguage/commit/965478e687f08d3b2ee4fe17104d3f41638bdca2", "name": "C#", "scopeName": "source.cs", "patterns": [ @@ -3423,7 +3423,7 @@ ] }, "interpolation": { - "name": "meta.interpolation.cs", + "name": "meta.embedded.interpolation.cs", "begin": "(?<=[^\\{]|^)((?:\\{\\{)*)(\\{)(?=[^\\{])", "beginCaptures": { "1": { @@ -3578,7 +3578,7 @@ } }, "raw-interpolation": { - "name": "meta.interpolation.cs", + "name": "meta.embedded.interpolation.cs", "begin": "(?<=[^\\{]|^)((?:\\{)*)(\\{)(?=[^\\{])", "beginCaptures": { "1": { @@ -3601,7 +3601,7 @@ ] }, "double-raw-interpolation": { - "name": "meta.interpolation.cs", + "name": "meta.embedded.interpolation.cs", "begin": "(?<=[^\\{][^\\{]|^)((?:\\{)*)(\\{\\{)(?=[^\\{])", "beginCaptures": { "1": { @@ -5238,6 +5238,9 @@ }, { "include": "#preprocessor-pragma-checksum" + }, + { + "include": "#preprocessor-app-directive" } ] }, @@ -5447,6 +5450,129 @@ } } }, + "preprocessor-app-directive": { + "begin": "\\s*(:)\\s*", + "beginCaptures": { + "1": { + "name": "punctuation.separator.colon.cs" + } + }, + "end": "(?=$)", + "patterns": [ + { + "include": "#preprocessor-app-directive-package" + }, + { + "include": "#preprocessor-app-directive-property" + }, + { + "include": "#preprocessor-app-directive-project" + }, + { + "include": "#preprocessor-app-directive-sdk" + }, + { + "include": "#preprocessor-app-directive-generic" + } + ] + }, + "preprocessor-app-directive-package": { + "match": "\\b(package)\\b\\s*([_[:alpha:]][_.[:alnum:]]*)?(@)?(.*)?\\s*", + "captures": { + "1": { + "name": "keyword.preprocessor.package.cs" + }, + "2": { + "patterns": [ + { + "include": "#preprocessor-app-directive-package-name" + } + ] + }, + "3": { + "name": "punctuation.separator.at.cs" + }, + "4": { + "name": "string.unquoted.preprocessor.message.cs" + } + } + }, + "preprocessor-app-directive-property": { + "match": "\\b(property)\\b\\s*([_[:alpha:]][_[:alnum:]]*)?(=)?(.*)?\\s*", + "captures": { + "1": { + "name": "keyword.preprocessor.property.cs" + }, + "2": { + "name": "entity.name.variable.preprocessor.symbol.cs" + }, + "3": { + "name": "punctuation.separator.equals.cs" + }, + "4": { + "name": "string.unquoted.preprocessor.message.cs" + } + } + }, + "preprocessor-app-directive-project": { + "match": "\\b(project)\\b\\s*(.*)?\\s*", + "captures": { + "1": { + "name": "keyword.preprocessor.project.cs" + }, + "2": { + "name": "string.unquoted.preprocessor.message.cs" + } + } + }, + "preprocessor-app-directive-sdk": { + "match": "\\b(sdk)\\b\\s*([_[:alpha:]][_.[:alnum:]]*)?(@)?(.*)?\\s*", + "captures": { + "1": { + "name": "keyword.preprocessor.sdk.cs" + }, + "2": { + "patterns": [ + { + "include": "#preprocessor-app-directive-package-name" + } + ] + }, + "3": { + "name": "punctuation.separator.at.cs" + }, + "4": { + "name": "string.unquoted.preprocessor.message.cs" + } + } + }, + "preprocessor-app-directive-package-name": { + "patterns": [ + { + "match": "(\\.)([_[:alpha:]][_[:alnum:]]*)", + "captures": { + "1": { + "name": "punctuation.dot.cs" + }, + "2": { + "name": "entity.name.variable.preprocessor.symbol.cs" + } + } + }, + { + "name": "entity.name.variable.preprocessor.symbol.cs", + "match": "[_[:alpha:]][_[:alnum:]]*" + } + ] + }, + "preprocessor-app-directive-generic": { + "match": "\\b(.*)?\\s*", + "captures": { + "1": { + "name": "string.unquoted.preprocessor.message.cs" + } + } + }, "preprocessor-expression": { "patterns": [ { diff --git a/extensions/css-language-features/client/src/cssClient.ts b/extensions/css-language-features/client/src/cssClient.ts index 4e90b3482e442..49bacd90a5c88 100644 --- a/extensions/css-language-features/client/src/cssClient.ts +++ b/extensions/css-language-features/client/src/cssClient.ts @@ -83,7 +83,9 @@ export async function startClient(context: ExtensionContext, newLanguageClient: } return r; } - const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; + function isThenable(obj: unknown): obj is Thenable { + return !!obj && typeof (obj as unknown as Thenable).then === 'function'; + } const r = next(document, position, context, token); if (isThenable(r)) { diff --git a/extensions/css-language-features/client/tsconfig.json b/extensions/css-language-features/client/tsconfig.json index 5284e0938583e..51303a368a2d5 100644 --- a/extensions/css-language-features/client/tsconfig.json +++ b/extensions/css-language-features/client/tsconfig.json @@ -6,6 +6,9 @@ "webworker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/css-language-features/extension-browser.webpack.config.js b/extensions/css-language-features/extension-browser.webpack.config.js index cb2e13c7ed316..ea4a69dd9c139 100644 --- a/extensions/css-language-features/extension-browser.webpack.config.js +++ b/extensions/css-language-features/extension-browser.webpack.config.js @@ -2,21 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; +import path from 'path'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; -const path = require('path'); - -module.exports = withBrowserDefaults({ - context: path.join(__dirname, 'client'), +export default withBrowserDefaults({ + context: path.join(import.meta.dirname, 'client'), entry: { extension: './src/browser/cssClientMain.ts' }, output: { filename: 'cssClientMain.js', - path: path.join(__dirname, 'client', 'dist', 'browser') + path: path.join(import.meta.dirname, 'client', 'dist', 'browser') } }); diff --git a/extensions/css-language-features/extension.webpack.config.js b/extensions/css-language-features/extension.webpack.config.js index a931210ab32ea..d8a29c8797dd7 100644 --- a/extensions/css-language-features/extension.webpack.config.js +++ b/extensions/css-language-features/extension.webpack.config.js @@ -2,21 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; +import path from 'path'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); -const path = require('path'); - -module.exports = withDefaults({ - context: path.join(__dirname, 'client'), +export default withDefaults({ + context: path.join(import.meta.dirname, 'client'), entry: { extension: './src/node/cssClientMain.ts', }, output: { filename: 'cssClientMain.js', - path: path.join(__dirname, 'client', 'dist', 'node') + path: path.join(import.meta.dirname, 'client', 'dist', 'node') } }); diff --git a/extensions/css-language-features/package-lock.json b/extensions/css-language-features/package-lock.json index dcd8acd1aabff..42656ea4bae20 100644 --- a/extensions/css-language-features/package-lock.json +++ b/extensions/css-language-features/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "vscode-languageclient": "^10.0.0-next.16", + "vscode-languageclient": "^10.0.0-next.18", "vscode-uri": "^3.1.0" }, "devDependencies": { @@ -19,6 +19,27 @@ "vscode": "^1.77.0" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@types/node": { "version": "22.13.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz", @@ -29,27 +50,13 @@ "undici-types": "~6.20.0" } }, - "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==", - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { "node": "20 || >=22" @@ -78,35 +85,35 @@ "license": "MIT" }, "node_modules/vscode-jsonrpc": { - "version": "9.0.0-next.8", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.8.tgz", - "integrity": "sha512-pN6L5eiNBvUpNFBJvudaZ83klir0T/wLFCDpYhpOEsKXyhsWyYsNMzoG7BK6zJoZLHGSSsaTJDjCcPwnLgUyPQ==", + "version": "9.0.0-next.10", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.10.tgz", + "integrity": "sha512-P+UOjuG/B1zkLM+bGIdmBwSkDejxtgo6EjG0pIkwnFBI0a2Mb7od36uUu8CPbECeQuh+n3zGcNwDl16DhuJ5IA==", "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/vscode-languageclient": { - "version": "10.0.0-next.16", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.16.tgz", - "integrity": "sha512-aVJ950olGncxehPezP61wsEHjB3zgDETCThH1FTQ4V9EZ9mcVSNfjXM0SWC+VYF3nZulI2hBZe0od2Ajib4hNA==", + "version": "10.0.0-next.18", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.18.tgz", + "integrity": "sha512-Dpcr0VEEf4SuMW17TFCuKovhvbCx6/tHTnmFyLW1KTJCdVmNG08hXVAmw8Z/izec7TQlzEvzw5PvRfYGzdtr5Q==", "license": "MIT", "dependencies": { - "minimatch": "^10.0.1", + "minimatch": "^10.0.3", "semver": "^7.7.1", - "vscode-languageserver-protocol": "3.17.6-next.13" + "vscode-languageserver-protocol": "3.17.6-next.15" }, "engines": { "vscode": "^1.91.0" } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.17.6-next.13", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.13.tgz", - "integrity": "sha512-IE+/j+OOqJ392KMhcexIGt9MVqcTZ4n7DVyaSp5txuC1kNUnfzxlkPzzDwo0p7hdINLCfWjbcjuW5tGYLof4Vw==", + "version": "3.17.6-next.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.15.tgz", + "integrity": "sha512-aoWX1wwGCndzfrTRhGKVpKAPVy9+WYhUtZW/PJQfHODmVwhVwb4we68CgsQZRTl36t8ZqlSOO2c2TdBPW7hrCw==", "license": "MIT", "dependencies": { - "vscode-jsonrpc": "9.0.0-next.8", + "vscode-jsonrpc": "9.0.0-next.10", "vscode-languageserver-types": "3.17.6-next.6" } }, diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index cd46d998e658c..ab57bc0c9b2c4 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -994,7 +994,7 @@ ] }, "dependencies": { - "vscode-languageclient": "^10.0.0-next.16", + "vscode-languageclient": "^10.0.0-next.18", "vscode-uri": "^3.1.0" }, "devDependencies": { diff --git a/extensions/css-language-features/server/extension-browser.webpack.config.js b/extensions/css-language-features/server/extension-browser.webpack.config.js index 5378e6a031ee2..131d293a7c50c 100644 --- a/extensions/css-language-features/server/extension-browser.webpack.config.js +++ b/extensions/css-language-features/server/extension-browser.webpack.config.js @@ -2,22 +2,18 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../../shared.webpack.config.mjs'; +import path from 'path'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../../shared.webpack.config').browser; -const path = require('path'); - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, entry: { extension: './src/browser/cssServerWorkerMain.ts', }, output: { filename: 'cssServerMain.js', - path: path.join(__dirname, 'dist', 'browser'), + path: path.join(import.meta.dirname, 'dist', 'browser'), libraryTarget: 'var', library: 'serverExportVar' } diff --git a/extensions/css-language-features/server/extension.webpack.config.js b/extensions/css-language-features/server/extension.webpack.config.js index db80cfebe98c3..5f07bd8f0a1a2 100644 --- a/extensions/css-language-features/server/extension.webpack.config.js +++ b/extensions/css-language-features/server/extension.webpack.config.js @@ -2,21 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../../shared.webpack.config.mjs'; +import path from 'path'; -//@ts-check - -'use strict'; - -const withDefaults = require('../../shared.webpack.config'); -const path = require('path'); - -module.exports = withDefaults({ - context: path.join(__dirname), +export default withDefaults({ + context: path.join(import.meta.dirname), entry: { extension: './src/node/cssServerNodeMain.ts', }, output: { filename: 'cssServerMain.js', - path: path.join(__dirname, 'dist', 'node'), + path: path.join(import.meta.dirname, 'dist', 'node'), } }); diff --git a/extensions/css-language-features/server/package-lock.json b/extensions/css-language-features/server/package-lock.json index 95454ebf76c53..60165842552ee 100644 --- a/extensions/css-language-features/server/package-lock.json +++ b/extensions/css-language-features/server/package-lock.json @@ -10,12 +10,12 @@ "license": "MIT", "dependencies": { "@vscode/l10n": "^0.0.18", - "vscode-css-languageservice": "^6.3.7", - "vscode-languageserver": "^10.0.0-next.13", + "vscode-css-languageservice": "^6.3.9", + "vscode-languageserver": "^10.0.0-next.15", "vscode-uri": "^3.1.0" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x" }, "engines": { @@ -23,10 +23,11 @@ } }, "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.10", @@ -51,9 +52,9 @@ "license": "MIT" }, "node_modules/vscode-css-languageservice": { - "version": "6.3.7", - "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.7.tgz", - "integrity": "sha512-5TmXHKllPzfkPhW4UE9sODV3E0bIOJPOk+EERKllf2SmAczjfTmYeq5txco+N3jpF8KIZ6loj/JptpHBQuVQRA==", + "version": "6.3.9", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.9.tgz", + "integrity": "sha512-1tLWfp+TDM5ZuVWht3jmaY5y7O6aZmpeXLoHl5bv1QtRsRKt4xYGRMmdJa5Pqx/FTkgRbsna9R+Gn2xE+evVuA==", "license": "MIT", "dependencies": { "@vscode/l10n": "^0.0.18", @@ -63,33 +64,33 @@ } }, "node_modules/vscode-jsonrpc": { - "version": "9.0.0-next.8", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.8.tgz", - "integrity": "sha512-pN6L5eiNBvUpNFBJvudaZ83klir0T/wLFCDpYhpOEsKXyhsWyYsNMzoG7BK6zJoZLHGSSsaTJDjCcPwnLgUyPQ==", + "version": "9.0.0-next.10", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.10.tgz", + "integrity": "sha512-P+UOjuG/B1zkLM+bGIdmBwSkDejxtgo6EjG0pIkwnFBI0a2Mb7od36uUu8CPbECeQuh+n3zGcNwDl16DhuJ5IA==", "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/vscode-languageserver": { - "version": "10.0.0-next.13", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-10.0.0-next.13.tgz", - "integrity": "sha512-4tSufM2XrNrrzBUGPcYh62qBYhm41yFwFZBgJ63I1dPHRh1aZPK65+TcVa3nG0/K62Q9phhk87TWdQFp+UnYFA==", + "version": "10.0.0-next.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-10.0.0-next.15.tgz", + "integrity": "sha512-vs+bwci/lM83ZhrR9t8DcZ2AgS2CKx4i6Yw86teKKkqlzlrYWTixuBd9w6H/UP9s8EGBvii0jnbjQd6wsKJ0ig==", "license": "MIT", "dependencies": { - "vscode-languageserver-protocol": "3.17.6-next.13" + "vscode-languageserver-protocol": "3.17.6-next.15" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.17.6-next.13", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.13.tgz", - "integrity": "sha512-IE+/j+OOqJ392KMhcexIGt9MVqcTZ4n7DVyaSp5txuC1kNUnfzxlkPzzDwo0p7hdINLCfWjbcjuW5tGYLof4Vw==", + "version": "3.17.6-next.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.15.tgz", + "integrity": "sha512-aoWX1wwGCndzfrTRhGKVpKAPVy9+WYhUtZW/PJQfHODmVwhVwb4we68CgsQZRTl36t8ZqlSOO2c2TdBPW7hrCw==", "license": "MIT", "dependencies": { - "vscode-jsonrpc": "9.0.0-next.8", + "vscode-jsonrpc": "9.0.0-next.10", "vscode-languageserver-types": "3.17.6-next.6" } }, diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 093366e038916..1d6d0cd2cbc87 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -11,12 +11,12 @@ "browser": "./dist/browser/cssServerMain", "dependencies": { "@vscode/l10n": "^0.0.18", - "vscode-css-languageservice": "^6.3.7", - "vscode-languageserver": "^10.0.0-next.13", + "vscode-css-languageservice": "^6.3.9", + "vscode-languageserver": "^10.0.0-next.15", "vscode-uri": "^3.1.0" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x" }, "scripts": { diff --git a/extensions/css-language-features/server/src/cssServer.ts b/extensions/css-language-features/server/src/cssServer.ts index 8b365f41b6be2..b46e20bb7c157 100644 --- a/extensions/css-language-features/server/src/cssServer.ts +++ b/extensions/css-language-features/server/src/cssServer.ts @@ -68,14 +68,15 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { - const initializationOptions = params.initializationOptions as any || {}; + const initializationOptions = params.initializationOptions || {}; - workspaceFolders = (params).workspaceFolders; - if (!Array.isArray(workspaceFolders)) { + if (!Array.isArray(params.workspaceFolders)) { workspaceFolders = []; if (params.rootPath) { workspaceFolders.push({ name: '', uri: URI.file(params.rootPath).toString(true) }); } + } else { + workspaceFolders = params.workspaceFolders; } requestService = getRequestService(initializationOptions?.handledSchemas || ['file'], connection, runtime); @@ -166,10 +167,10 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // The settings have changed. Is send on server activation as well. connection.onDidChangeConfiguration(change => { - updateConfiguration(change.settings as any); + updateConfiguration(change.settings as { [languageId: string]: LanguageSettings }); }); - function updateConfiguration(settings: any) { + function updateConfiguration(settings: { [languageId: string]: LanguageSettings }) { for (const languageId in languageServices) { languageServices[languageId].configure(settings[languageId]); } diff --git a/extensions/css-language-features/server/tsconfig.json b/extensions/css-language-features/server/tsconfig.json index 4f24a50855ccd..0b49ec72b8ff7 100644 --- a/extensions/css-language-features/server/tsconfig.json +++ b/extensions/css-language-features/server/tsconfig.json @@ -7,6 +7,9 @@ "WebWorker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*" diff --git a/extensions/debug-auto-launch/extension.webpack.config.js b/extensions/debug-auto-launch/extension.webpack.config.js index b474e65cbb130..0c857b362f5da 100644 --- a/extensions/debug-auto-launch/extension.webpack.config.js +++ b/extensions/debug-auto-launch/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { extension: './src/extension.ts', }, diff --git a/extensions/debug-auto-launch/src/extension.ts b/extensions/debug-auto-launch/src/extension.ts index 7d06c56d47ffc..a17b47ecf7d5b 100644 --- a/extensions/debug-auto-launch/src/extension.ts +++ b/extensions/debug-auto-launch/src/extension.ts @@ -33,6 +33,8 @@ const TEXT_STATE_DESCRIPTION = { [State.Smart]: vscode.l10n.t("Auto attach when running scripts that aren't in a node_modules folder"), [State.OnlyWithFlag]: vscode.l10n.t('Only auto attach when the `--inspect` flag is given') }; + +const TEXT_TOGGLE_TITLE = vscode.l10n.t('Toggle Auto Attach'); const TEXT_TOGGLE_WORKSPACE = vscode.l10n.t('Toggle auto attach in this workspace'); const TEXT_TOGGLE_GLOBAL = vscode.l10n.t('Toggle auto attach on this machine'); const TEXT_TEMP_DISABLE = vscode.l10n.t('Temporarily disable auto attach in this session'); @@ -134,7 +136,8 @@ async function toggleAutoAttachSetting(context: vscode.ExtensionContext, scope?: quickPick.activeItems = isTemporarilyDisabled ? [items[0]] : quickPick.items.filter(i => 'state' in i && i.state === current); - quickPick.title = isGlobalScope ? TEXT_TOGGLE_GLOBAL : TEXT_TOGGLE_WORKSPACE; + quickPick.title = TEXT_TOGGLE_TITLE; + quickPick.placeholder = isGlobalScope ? TEXT_TOGGLE_GLOBAL : TEXT_TOGGLE_WORKSPACE; quickPick.buttons = [ { iconPath: new vscode.ThemeIcon(isGlobalScope ? 'folder' : 'globe'), diff --git a/extensions/debug-auto-launch/tsconfig.json b/extensions/debug-auto-launch/tsconfig.json index bfcf873c749ee..22c47de77dbe1 100644 --- a/extensions/debug-auto-launch/tsconfig.json +++ b/extensions/debug-auto-launch/tsconfig.json @@ -2,9 +2,11 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "downlevelIteration": true, "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/debug-server-ready/extension.webpack.config.js b/extensions/debug-server-ready/extension.webpack.config.js index b474e65cbb130..0c857b362f5da 100644 --- a/extensions/debug-server-ready/extension.webpack.config.js +++ b/extensions/debug-server-ready/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { extension: './src/extension.ts', }, diff --git a/extensions/debug-server-ready/tsconfig.json b/extensions/debug-server-ready/tsconfig.json index 9bf747283ca99..21e3648ffed62 100644 --- a/extensions/debug-server-ready/tsconfig.json +++ b/extensions/debug-server-ready/tsconfig.json @@ -2,9 +2,11 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "downlevelIteration": true, "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/docker/cgmanifest.json b/extensions/docker/cgmanifest.json index 8462de7dd7283..942ba14ebd8f9 100644 --- a/extensions/docker/cgmanifest.json +++ b/extensions/docker/cgmanifest.json @@ -6,13 +6,14 @@ "git": { "name": "language-docker", "repositoryUrl": "https://github.com/moby/moby", - "commitHash": "c2029cb2574647e4bc28ed58486b8e85883eedb9" + "commitHash": "bea959c7b793b32a893820b97c4eadc7c87fabb0", + "tag": "28.3.3" } }, "license": "Apache-2.0", "description": "The file syntaxes/docker.tmLanguage was included from https://github.com/moby/moby/blob/master/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage.", - "version": "0.0.0" + "version": "28.3.3" } ], "version": 1 -} \ No newline at end of file +} diff --git a/extensions/docker/package.json b/extensions/docker/package.json index 9309bd51f9f8f..a9a3dfdd9bf3f 100644 --- a/extensions/docker/package.json +++ b/extensions/docker/package.json @@ -9,7 +9,6 @@ "vscode": "*" }, "scripts": { - "update-grammar": "node ../node_modules/vscode-grammar-updater/bin moby/moby contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage ./syntaxes/docker.tmLanguage.json" }, "categories": ["Programming Languages"], "contributes": { diff --git a/extensions/dotenv/.vscodeignore b/extensions/dotenv/.vscodeignore new file mode 100644 index 0000000000000..0a622e7e30046 --- /dev/null +++ b/extensions/dotenv/.vscodeignore @@ -0,0 +1,2 @@ +test/** +cgmanifest.json diff --git a/extensions/dotenv/cgmanifest.json b/extensions/dotenv/cgmanifest.json new file mode 100644 index 0000000000000..637e505549ba9 --- /dev/null +++ b/extensions/dotenv/cgmanifest.json @@ -0,0 +1,40 @@ +{ + "registrations": [ + { + "component": { + "type": "git", + "git": { + "name": "dotenv-org/dotenv-vscode", + "repositoryUrl": "https://github.com/dotenv-org/dotenv-vscode", + "commitHash": "e7e41baa5b23e01c1ff0567a4e596c24860e7def" + } + }, + "licenseDetail": [ + "MIT License", + "", + "Copyright (c) 2022 Scott Motte", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the \"Software\"), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all", + "copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE", + "SOFTWARE." + ], + "license": "MIT License", + "version": "0.26.0" + } + ], + "version": 1 +} \ No newline at end of file diff --git a/extensions/dotenv/language-configuration.json b/extensions/dotenv/language-configuration.json new file mode 100644 index 0000000000000..77e01182dddaa --- /dev/null +++ b/extensions/dotenv/language-configuration.json @@ -0,0 +1,24 @@ +{ + "comments": { + "lineComment": "#" + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ] +} diff --git a/extensions/dotenv/package.json b/extensions/dotenv/package.json new file mode 100644 index 0000000000000..2adbc86ff36d9 --- /dev/null +++ b/extensions/dotenv/package.json @@ -0,0 +1,48 @@ +{ + "name": "dotenv", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "engines": { + "vscode": "*" + }, + "scripts": { + "update-grammar": "node ../node_modules/vscode-grammar-updater/bin dotenv-org/dotenv-vscode syntaxes/dotenv.tmLanguage.json ./syntaxes/dotenv.tmLanguage.json" + }, + "categories": ["Programming Languages"], + "contributes": { + "languages": [ + { + "id": "dotenv", + "extensions": [ + ".env" + ], + "filenames": [ + ".env", + ".flaskenv", + "user-dirs.dirs" + ], + "filenamePatterns": [ + ".env.*" + ], + "aliases": [ + "Dotenv" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "dotenv", + "scopeName": "source.dotenv", + "path": "./syntaxes/dotenv.tmLanguage.json" + } + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + } +} diff --git a/extensions/dotenv/package.nls.json b/extensions/dotenv/package.nls.json new file mode 100644 index 0000000000000..acebc95515754 --- /dev/null +++ b/extensions/dotenv/package.nls.json @@ -0,0 +1,4 @@ +{ + "displayName": "Dotenv Language Basics", + "description": "Provides syntax highlighting and bracket matching in dotenv files." +} diff --git a/extensions/dotenv/syntaxes/dotenv.tmLanguage.json b/extensions/dotenv/syntaxes/dotenv.tmLanguage.json new file mode 100644 index 0000000000000..1cf105b0c1880 --- /dev/null +++ b/extensions/dotenv/syntaxes/dotenv.tmLanguage.json @@ -0,0 +1,127 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/dotenv-org/dotenv-vscode/blob/master/syntaxes/dotenv.tmLanguage.json", + "If you want to provide a fix or improvement, please create a pull request against the original repository.", + "Once accepted there, we are happy to receive an update request." + ], + "version": "https://github.com/dotenv-org/dotenv-vscode/commit/e7e41baa5b23e01c1ff0567a4e596c24860e7def", + "scopeName": "source.dotenv", + "patterns": [ + { + "comment": "Full Line Comment", + "match": "^\\s?(#.*$)\\n", + "captures": { + "1": { + "patterns": [ + { + "include": "#line-comment" + } + ] + } + } + }, + { + "comment": "ENV entry", + "match": "^\\s?(.*?)\\s?(\\=)(.*)$", + "captures": { + "1": { + "patterns": [ + { + "include": "#key" + } + ] + }, + "2": { + "name": "keyword.operator.assignment.dotenv" + }, + "3": { + "name": "property.value.dotenv", + "patterns": [ + { + "include": "#line-comment" + }, + { + "include": "#double-quoted-string" + }, + { + "include": "#single-quoted-string" + }, + { + "include": "#interpolation" + } + ] + } + } + } + ], + "repository": { + "variable": { + "comment": "env variable", + "match": "[a-zA-Z_]+[a-zA-Z0-9_]*" + }, + "line-comment": { + "comment": "Comment", + "match": "#.*$", + "name": "comment.line.dotenv" + }, + "interpolation": { + "comment": "Interpolation (variable substitution)", + "match": "(\\$\\{)(.*)(\\})", + "captures": { + "1": { + "name": "keyword.interpolation.begin.dotenv" + }, + "2": { + "name": "variable.interpolation.dotenv" + }, + "3": { + "name": "keyword.interpolation.end.dotenv" + } + } + }, + "escape-characters": { + "comment": "Escape characters", + "match": "\\\\[nrtfb\"'\\\\]|\\\\u[0123456789ABCDEF]{4}", + "name": "constant.character.escape.dotenv" + }, + "double-quoted-string": { + "comment": "Double Quoted String", + "match": "\"(.*)\"", + "name": "string.quoted.double.dotenv", + "captures": { + "1": { + "patterns": [ + { + "include": "#interpolation" + }, + { + "include": "#escape-characters" + } + ] + } + } + }, + "single-quoted-string": { + "comment": "Single Quoted String", + "match": "'(.*)'", + "name": "string.quoted.single.dotenv" + }, + "key": { + "comment": "Key", + "match": "(export\\s)?(.*)", + "captures": { + "1": { + "name": "keyword.key.export.dotenv" + }, + "2": { + "name": "variable.key.dotenv", + "patterns": [ + { + "include": "#variable" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/extensions/emmet/extension-browser.webpack.config.js b/extensions/emmet/extension-browser.webpack.config.js index 9fc8e4817a5f2..ce7ea8d197b90 100644 --- a/extensions/emmet/extension-browser.webpack.config.js +++ b/extensions/emmet/extension-browser.webpack.config.js @@ -2,16 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withBrowserDefaults - = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, entry: { extension: './src/browser/emmetBrowserMain.ts' }, diff --git a/extensions/emmet/extension.webpack.config.js b/extensions/emmet/extension.webpack.config.js index 1176314bb1b1b..2c6094112e17b 100644 --- a/extensions/emmet/extension.webpack.config.js +++ b/extensions/emmet/extension.webpack.config.js @@ -2,22 +2,18 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import path from 'path'; -//@ts-check +import withDefaults from '../shared.webpack.config.mjs'; -'use strict'; - -const path = require('path'); - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { extension: './src/node/emmetNodeMain.ts', }, output: { - path: path.join(__dirname, 'dist', 'node'), + path: path.join(import.meta.dirname, 'dist', 'node'), filename: 'emmetNodeMain.js' } }); diff --git a/extensions/emmet/src/emmetCommon.ts b/extensions/emmet/src/emmetCommon.ts index daed62d7c655b..da364e19fa43c 100644 --- a/extensions/emmet/src/emmetCommon.ts +++ b/extensions/emmet/src/emmetCommon.ts @@ -171,7 +171,7 @@ function refreshCompletionProviders(_: vscode.ExtensionContext) { return undefined; } const item = items.items[0]; - if (!item) { + if (!item || !item.insertText) { return undefined; } const range = item.range as vscode.Range; @@ -184,8 +184,8 @@ function refreshCompletionProviders(_: vscode.ExtensionContext) { return [ { - insertText: item.insertText as any, - filterText: item.label as any, + insertText: item.insertText, + filterText: item.label, range } ]; diff --git a/extensions/emmet/src/test/completion.test.ts b/extensions/emmet/src/test/completion.test.ts index 4f74ba92e25e0..bf61f338f21c7 100644 --- a/extensions/emmet/src/test/completion.test.ts +++ b/extensions/emmet/src/test/completion.test.ts @@ -41,14 +41,14 @@ suite('Tests for completion in CSS embedded in HTML', () => { { label: 'widows: ;', documentation: `widows: |;` } ]); } catch (e) { - assert.strictEqual(e.message, "Didn't find completion item with label widows: ;"); + assert.strictEqual(e.message, `Didn't find completion item with label widows: ;`); } try { await testCompletionProvider('css', `.foo { wid| }`, [ { label: 'widows: ;', documentation: `widows: |;` } ]); } catch (e) { - assert.strictEqual(e.message, "Didn't find completion item with label widows: ;"); + assert.strictEqual(e.message, `Didn't find completion item with label widows: ;`); } await testCompletionProvider('css', `.foo { wido| }`, [ { label: 'widows: ;', documentation: `widows: |;` } diff --git a/extensions/emmet/src/updateImageSize.ts b/extensions/emmet/src/updateImageSize.ts index 23838576c50b8..204388f005557 100644 --- a/extensions/emmet/src/updateImageSize.ts +++ b/extensions/emmet/src/updateImageSize.ts @@ -273,8 +273,12 @@ function getAttributeQuote(editor: TextEditor, attr: Attribute): string { */ function findUrlToken(editor: TextEditor, node: Property, pos: Position): CssToken | undefined { const offset = editor.document.offsetAt(pos); - for (let i = 0, il = (node as any).parsedValue.length, url; i < il; i++) { - iterateCSSToken((node as any).parsedValue[i], (token: CssToken) => { + if (!('parsedValue' in node) || !Array.isArray(node.parsedValue)) { + return undefined; + } + + for (let i = 0, il = node.parsedValue.length, url; i < il; i++) { + iterateCSSToken(node.parsedValue[i], (token: CssToken) => { if (token.type === 'url' && token.start <= offset && token.end >= offset) { url = token; return false; @@ -286,7 +290,7 @@ function findUrlToken(editor: TextEditor, node: Property, pos: Position): CssTok return url; } } - return; + return undefined; } /** diff --git a/extensions/emmet/src/util.ts b/extensions/emmet/src/util.ts index adbfe963a5b9e..e934df84e7133 100644 --- a/extensions/emmet/src/util.ts +++ b/extensions/emmet/src/util.ts @@ -354,7 +354,7 @@ export function getFlatNode(root: FlatNode | undefined, offset: number, includeN || (includeNodeBoundary && nodeStart <= offset && nodeEnd >= offset)) { return getFlatNodeChildren(child.children) ?? child; } - else if ('close' in child) { + else if ('close' in child) { // We have an HTML node in this case. // In case this node is an invalid unpaired HTML node, // we still want to search its children diff --git a/extensions/emmet/tsconfig.json b/extensions/emmet/tsconfig.json index 994fa2395377d..a6353d515d34b 100644 --- a/extensions/emmet/tsconfig.json +++ b/extensions/emmet/tsconfig.json @@ -1,7 +1,11 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "outDir": "./out" + "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], + "skipLibCheck": true }, "exclude": [ "node_modules", diff --git a/extensions/esbuild-webview-common.js b/extensions/esbuild-webview-common.js deleted file mode 100644 index 12cd1c58875d3..0000000000000 --- a/extensions/esbuild-webview-common.js +++ /dev/null @@ -1,93 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check - -/** - * @fileoverview Common build script for extension scripts used in in webviews. - */ - -const path = require('path'); -const esbuild = require('esbuild'); - -/** - * @typedef {Partial & { - * entryPoints: string[] | Record | { in: string, out: string }[]; - * outdir: string; - * }} BuildOptions - */ - -/** - * Build the source code once using esbuild. - * - * @param {BuildOptions} options - * @param {(outDir: string) => unknown} [didBuild] - */ -async function build(options, didBuild) { - await esbuild.build({ - bundle: true, - minify: true, - sourcemap: false, - format: 'esm', - platform: 'browser', - target: ['es2024'], - ...options, - }); - - await didBuild?.(options.outdir); -} - -/** - * Build the source code once using esbuild, logging errors instead of throwing. - * - * @param {BuildOptions} options - * @param {(outDir: string) => unknown} [didBuild] - */ -async function tryBuild(options, didBuild) { - try { - await build(options, didBuild); - } catch (err) { - console.error(err); - } -} - -/** - * @param {{ - * srcDir: string; - * outdir: string; - * entryPoints: string[] | Record | { in: string, out: string }[]; - * additionalOptions?: Partial - * }} config - * @param {string[]} args - * @param {(outDir: string) => unknown} [didBuild] - */ -module.exports.run = async function (config, args, didBuild) { - let outdir = config.outdir; - const outputRootIndex = args.indexOf('--outputRoot'); - if (outputRootIndex >= 0) { - const outputRoot = args[outputRootIndex + 1]; - const outputDirName = path.basename(outdir); - outdir = path.join(outputRoot, outputDirName); - } - - /** @type {BuildOptions} */ - const resolvedOptions = { - entryPoints: config.entryPoints, - outdir, - logOverride: { - 'import-is-undefined': 'error', - }, - ...(config.additionalOptions || {}), - }; - - const isWatch = args.indexOf('--watch') >= 0; - if (isWatch) { - await tryBuild(resolvedOptions, didBuild); - - const watcher = require('@parcel/watcher'); - watcher.subscribe(config.srcDir, () => tryBuild(resolvedOptions, didBuild)); - } else { - return build(resolvedOptions, didBuild).catch(() => process.exit(1)); - } -}; diff --git a/extensions/esbuild-webview-common.mjs b/extensions/esbuild-webview-common.mjs new file mode 100644 index 0000000000000..7b704b3b7f334 --- /dev/null +++ b/extensions/esbuild-webview-common.mjs @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +/** + * @fileoverview Common build script for extension scripts used in in webviews. + */ +import path from 'node:path'; +import esbuild from 'esbuild'; + +/** + * @typedef {Partial & { + * entryPoints: string[] | Record | { in: string, out: string }[]; + * outdir: string; + * }} BuildOptions + */ + +/** + * Build the source code once using esbuild. + * + * @param {BuildOptions} options + * @param {(outDir: string) => unknown} [didBuild] + */ +async function build(options, didBuild) { + await esbuild.build({ + bundle: true, + minify: true, + sourcemap: false, + format: 'esm', + platform: 'browser', + target: ['es2024'], + ...options, + }); + + await didBuild?.(options.outdir); +} + +/** + * Build the source code once using esbuild, logging errors instead of throwing. + * + * @param {BuildOptions} options + * @param {(outDir: string) => unknown} [didBuild] + */ +async function tryBuild(options, didBuild) { + try { + await build(options, didBuild); + } catch (err) { + console.error(err); + } +} + +/** + * @param {{ + * srcDir: string; + * outdir: string; + * entryPoints: string[] | Record | { in: string, out: string }[]; + * additionalOptions?: Partial + * }} config + * @param {string[]} args + * @param {(outDir: string) => unknown} [didBuild] + */ +export async function run(config, args, didBuild) { + let outdir = config.outdir; + const outputRootIndex = args.indexOf('--outputRoot'); + if (outputRootIndex >= 0) { + const outputRoot = args[outputRootIndex + 1]; + const outputDirName = path.basename(outdir); + outdir = path.join(outputRoot, outputDirName); + } + + /** @type {BuildOptions} */ + const resolvedOptions = { + entryPoints: config.entryPoints, + outdir, + logOverride: { + 'import-is-undefined': 'error', + }, + ...(config.additionalOptions || {}), + }; + + const isWatch = args.indexOf('--watch') >= 0; + if (isWatch) { + await tryBuild(resolvedOptions, didBuild); + const watcher = await import('@vscode/watcher'); + watcher.subscribe(config.srcDir, () => tryBuild(resolvedOptions, didBuild)); + } else { + return build(resolvedOptions, didBuild).catch(() => process.exit(1)); + } +} diff --git a/extensions/extension-editing/extension-browser.webpack.config.js b/extensions/extension-editing/extension-browser.webpack.config.js index 1018f45a81a88..fffaf0ebe0439 100644 --- a/extensions/extension-editing/extension-browser.webpack.config.js +++ b/extensions/extension-editing/extension-browser.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, entry: { extension: './src/extensionEditingBrowserMain.ts' }, diff --git a/extensions/extension-editing/extension.webpack.config.js b/extensions/extension-editing/extension.webpack.config.js index ab1c951fdda7b..1d7ce38597d25 100644 --- a/extensions/extension-editing/extension.webpack.config.js +++ b/extensions/extension-editing/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { extension: './src/extensionEditingMain.ts', }, diff --git a/extensions/extension-editing/src/extensionLinter.ts b/extensions/extension-editing/src/extensionLinter.ts index be7eea1a49b85..187100b563fec 100644 --- a/extensions/extension-editing/src/extensionLinter.ts +++ b/extensions/extension-editing/src/extensionLinter.ts @@ -33,7 +33,7 @@ const dataUrlsNotValid = l10n.t("Data URLs are not a valid image source."); const relativeUrlRequiresHttpsRepository = l10n.t("Relative image URLs require a repository with HTTPS protocol to be specified in the package.json."); const relativeBadgeUrlRequiresHttpsRepository = l10n.t("Relative badge URLs require a repository with HTTPS protocol to be specified in this package.json."); const apiProposalNotListed = l10n.t("This proposal cannot be used because for this extension the product defines a fixed set of API proposals. You can test your extension but before publishing you MUST reach out to the VS Code team."); -const bumpEngineForImplicitActivationEvents = l10n.t("This activation event can be removed for extensions targeting engine version ^1.75 as VS Code will generate these automatically from your package.json contribution declarations."); +const bumpEngineForImplicitActivationEvents = l10n.t("This activation event can be removed for extensions targeting engine version ^1.75.0 as VS Code will generate these automatically from your package.json contribution declarations."); const starActivation = l10n.t("Using '*' activation is usually a bad idea as it impacts performance."); const parsingErrorHeader = l10n.t("Error parsing the when-clause:"); diff --git a/extensions/extension-editing/tsconfig.json b/extensions/extension-editing/tsconfig.json index 3714bd099587b..796a159a61c21 100644 --- a/extensions/extension-editing/tsconfig.json +++ b/extensions/extension-editing/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "./out", "typeRoots": [ - "node_modules/@types" + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/fsharp/cgmanifest.json b/extensions/fsharp/cgmanifest.json index 75e24896d8e8c..d5c42026169b3 100644 --- a/extensions/fsharp/cgmanifest.json +++ b/extensions/fsharp/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "ionide/ionide-fsgrammar", "repositoryUrl": "https://github.com/ionide/ionide-fsgrammar", - "commitHash": "c62c78404d0b2c14816aae61ac0688663a5990a3" + "commitHash": "0cb968a4b8fdb2e0656b95342cdffbeff04a1248" } }, "license": "MIT", diff --git a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json index 8ba555b77727a..fd1b6b09aa174 100644 --- a/extensions/fsharp/syntaxes/fsharp.tmLanguage.json +++ b/extensions/fsharp/syntaxes/fsharp.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/ionide/ionide-fsgrammar/commit/c62c78404d0b2c14816aae61ac0688663a5990a3", + "version": "https://github.com/ionide/ionide-fsgrammar/commit/0cb968a4b8fdb2e0656b95342cdffbeff04a1248", "name": "fsharp", "scopeName": "source.fsharp", "patterns": [ @@ -525,15 +525,6 @@ }, "comments": { "patterns": [ - { - "name": "comment.literate.command.fsharp", - "match": "(\\(\\*{3}.*\\*{3}\\))", - "beginCaptures": { - "1": { - "name": "comment.block.fsharp" - } - } - }, { "name": "comment.block.markdown.fsharp", "begin": "^\\s*(\\(\\*\\*(?!\\)))((?!\\*\\)).)*$", @@ -543,7 +534,7 @@ "name": "comment.block.fsharp" } }, - "endCaptures": { + "whileCaptures": { "1": { "name": "comment.block.fsharp" } @@ -807,13 +798,16 @@ ] }, { - "match": "(:)\\s*([?[:alpha:]0-9'`^._ ]+)", + "match": "(:)\\s*([?[:alpha:]0-9'`^._ ]+)(\\|\\s*(null))?", "captures": { "1": { "name": "keyword.symbol.fsharp" }, "2": { "name": "entity.name.type.fsharp" + }, + "4": { + "name": "entity.name.type.fsharp" } } }, @@ -936,8 +930,8 @@ "patterns": [ { "name": "binding.fsharp", - "begin": "\\b(let mutable|static let mutable|static let|let inline|let|and|member val|member inline|static member inline|static member val|static member|default|member|override|let!)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", - "end": "\\s*((with\\b)|(=|\\n+=|(?<=\\=)))", + "begin": "\\b(let mutable|static let mutable|static let|let inline|let|and inline|and|member val|member inline|static member inline|static member val|static member|default|member|override|let!)(\\s+rec|mutable)?(\\s+\\[\\<.*\\>\\])?\\s*(private|internal|public)?\\s+(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?", + "end": "\\s*((with inline|with)\\b|(=|\\n+=|(?<=\\=)))", "beginCaptures": { "1": { "name": "keyword.fsharp" @@ -1522,7 +1516,7 @@ "match": "(\\(|\\))" }, { - "match": "(\\?{0,1})([[:alpha:]0-9'`^._]+|``[[:alpha:]0-9'`^:,._ ]+``)\\s*(:{0,1})(\\s*([?[:alpha:]0-9'`<>._ ]+)){0,1}", + "match": "(\\?{0,1})([[:alpha:]0-9'`^._]+|``[[:alpha:]0-9'`^:,._ ]+``)\\s*(:{0,1})(\\s*([?[:alpha:]0-9'`<>._ ]+)){0,1}(\\|\\s*(null))?", "captures": { "1": { "name": "keyword.symbol.fsharp" @@ -1535,6 +1529,9 @@ }, "4": { "name": "entity.name.type.fsharp" + }, + "7": { + "name": "entity.name.type.fsharp" } } }, @@ -1834,7 +1831,7 @@ "patterns": [ { "name": "keyword.control.directive.fsharp", - "match": "\\s?(#if|#elif|#elseif|#else|#endif|#light|#nowarn)", + "match": "\\s?(#if|#elif|#elseif|#else|#endif|#light|#nowarn|#warnon)", "captures": {} } ] diff --git a/extensions/git-base/extension-browser.webpack.config.js b/extensions/git-base/extension-browser.webpack.config.js index 636198c41f8d5..fcdf954744c12 100644 --- a/extensions/git-base/extension-browser.webpack.config.js +++ b/extensions/git-base/extension-browser.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, entry: { extension: './src/extension.ts' }, diff --git a/extensions/git-base/extension.webpack.config.js b/extensions/git-base/extension.webpack.config.js index 06bc95eaef76b..0bea7c7e821e4 100644 --- a/extensions/git-base/extension.webpack.config.js +++ b/extensions/git-base/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { extension: './src/extension.ts' }, diff --git a/extensions/git-base/languages/ignore.language-configuration.json b/extensions/git-base/languages/ignore.language-configuration.json index ad8b8ee5cd864..03e8ed3611c45 100644 --- a/extensions/git-base/languages/ignore.language-configuration.json +++ b/extensions/git-base/languages/ignore.language-configuration.json @@ -8,7 +8,6 @@ { "open": "(", "close": ")" }, { "open": "'", "close": "'", "notIn": ["string", "comment"] }, { "open": "\"", "close": "\"", "notIn": ["string"] }, - { "open": "`", "close": "`", "notIn": ["string", "comment"] }, - { "open": "/**", "close": " */", "notIn": ["string"] } + { "open": "`", "close": "`", "notIn": ["string", "comment"] } ] } diff --git a/extensions/git-base/src/api/api1.ts b/extensions/git-base/src/api/api1.ts index 005a793035635..19038bc1eec48 100644 --- a/extensions/git-base/src/api/api1.ts +++ b/extensions/git-base/src/api/api1.ts @@ -14,7 +14,7 @@ export class ApiImpl implements API { constructor(private _model: Model) { } pickRemoteSource(options: PickRemoteSourceOptions): Promise { - return pickRemoteSource(this._model, options as any); + return pickRemoteSource(this._model, options); } getRemoteSourceActions(url: string): Promise { @@ -30,11 +30,11 @@ export function registerAPICommands(extension: GitBaseExtensionImpl): Disposable const disposables: Disposable[] = []; disposables.push(commands.registerCommand('git-base.api.getRemoteSources', (opts?: PickRemoteSourceOptions) => { - if (!extension.model) { + if (!extension.model || !opts) { return; } - return pickRemoteSource(extension.model, opts as any); + return pickRemoteSource(extension.model, opts); })); return Disposable.from(...disposables); diff --git a/extensions/git-base/src/decorators.ts b/extensions/git-base/src/decorators.ts index f7c25b24ef9bf..067d32cdb5ffa 100644 --- a/extensions/git-base/src/decorators.ts +++ b/extensions/git-base/src/decorators.ts @@ -48,22 +48,10 @@ function _throttle(fn: Function, key: string): Function { } function decorate(decorator: (fn: Function, key: string) => Function): Function { - return (_target: any, key: string, descriptor: any) => { - let fnKey: string | null = null; - let fn: Function | null = null; - - if (typeof descriptor.value === 'function') { - fnKey = 'value'; - fn = descriptor.value; - } else if (typeof descriptor.get === 'function') { - fnKey = 'get'; - fn = descriptor.get; - } - - if (!fn || !fnKey) { + return function (original: any, context: ClassMethodDecoratorContext) { + if (context.kind !== 'method') { throw new Error('not supported'); } - - descriptor[fnKey] = decorator(fn, key); + return decorator(original, context.name.toString()); }; } diff --git a/extensions/git-base/src/extension.ts b/extensions/git-base/src/extension.ts index 17ffb89f82d23..453d8f7850f18 100644 --- a/extensions/git-base/src/extension.ts +++ b/extensions/git-base/src/extension.ts @@ -3,14 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionContext } from 'vscode'; +import { ExtensionContext, languages } from 'vscode'; import { registerAPICommands } from './api/api1'; import { GitBaseExtensionImpl } from './api/extension'; import { Model } from './model'; +import { GitCommitFoldingProvider } from './foldingProvider'; export function activate(context: ExtensionContext): GitBaseExtensionImpl { const apiImpl = new GitBaseExtensionImpl(new Model()); context.subscriptions.push(registerAPICommands(apiImpl)); + // Register folding provider for git-commit language + context.subscriptions.push( + languages.registerFoldingRangeProvider('git-commit', new GitCommitFoldingProvider()) + ); + return apiImpl; } diff --git a/extensions/git-base/src/foldingProvider.ts b/extensions/git-base/src/foldingProvider.ts new file mode 100644 index 0000000000000..b1c1cc451718e --- /dev/null +++ b/extensions/git-base/src/foldingProvider.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +export class GitCommitFoldingProvider implements vscode.FoldingRangeProvider { + + provideFoldingRanges( + document: vscode.TextDocument, + _context: vscode.FoldingContext, + _token: vscode.CancellationToken + ): vscode.ProviderResult { + const ranges: vscode.FoldingRange[] = []; + + let commentBlockStart: number | undefined; + let currentDiffStart: number | undefined; + + for (let i = 0; i < document.lineCount; i++) { + const line = document.lineAt(i); + const lineText = line.text; + + // Check for comment lines (lines starting with #) + if (lineText.startsWith('#')) { + // Close any active diff block when we encounter a comment + if (currentDiffStart !== undefined) { + // Only create fold if there are at least 2 lines + if (i - currentDiffStart > 1) { + ranges.push(new vscode.FoldingRange(currentDiffStart, i - 1)); + } + currentDiffStart = undefined; + } + + if (commentBlockStart === undefined) { + commentBlockStart = i; + } + } else { + // End of comment block + if (commentBlockStart !== undefined) { + // Only create fold if there are at least 2 lines + if (i - commentBlockStart > 1) { + ranges.push(new vscode.FoldingRange( + commentBlockStart, + i - 1, + vscode.FoldingRangeKind.Comment + )); + } + commentBlockStart = undefined; + } + } + + // Check for diff sections (lines starting with "diff --git") + if (lineText.startsWith('diff --git ')) { + // If there's a previous diff block, close it + if (currentDiffStart !== undefined) { + // Only create fold if there are at least 2 lines + if (i - currentDiffStart > 1) { + ranges.push(new vscode.FoldingRange(currentDiffStart, i - 1)); + } + } + // Start new diff block + currentDiffStart = i; + } + } + + // Handle end-of-document cases + + // If comment block extends to end of document + if (commentBlockStart !== undefined) { + if (document.lineCount - commentBlockStart > 1) { + ranges.push(new vscode.FoldingRange( + commentBlockStart, + document.lineCount - 1, + vscode.FoldingRangeKind.Comment + )); + } + } + + // If diff block extends to end of document + if (currentDiffStart !== undefined) { + if (document.lineCount - currentDiffStart > 1) { + ranges.push(new vscode.FoldingRange( + currentDiffStart, + document.lineCount - 1 + )); + } + } + + return ranges; + } +} diff --git a/extensions/git-base/src/remoteSource.ts b/extensions/git-base/src/remoteSource.ts index eb86b27367aab..9c6f1b02fa4d0 100644 --- a/extensions/git-base/src/remoteSource.ts +++ b/extensions/git-base/src/remoteSource.ts @@ -123,6 +123,7 @@ export async function getRemoteSourceActions(model: Model, url: string): Promise return remoteSourceActions; } +export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions): Promise; export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise; export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch: true }): Promise; export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise { diff --git a/extensions/git-base/src/test/foldingProvider.test.ts b/extensions/git-base/src/test/foldingProvider.test.ts new file mode 100644 index 0000000000000..69f7d35bf185f --- /dev/null +++ b/extensions/git-base/src/test/foldingProvider.test.ts @@ -0,0 +1,258 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import * as assert from 'assert'; +import * as vscode from 'vscode'; +import { GitCommitFoldingProvider } from '../foldingProvider'; + +suite('GitCommitFoldingProvider', () => { + + function createMockDocument(content: string): vscode.TextDocument { + const lines = content.split('\n'); + return { + lineCount: lines.length, + lineAt: (index: number) => ({ + text: lines[index] || '', + lineNumber: index + }), + } as vscode.TextDocument; + } + + const mockContext: vscode.FoldingContext = {} as vscode.FoldingContext; + const mockToken: vscode.CancellationToken = { isCancellationRequested: false } as vscode.CancellationToken; + + test('empty document returns no folding ranges', () => { + const provider = new GitCommitFoldingProvider(); + const doc = createMockDocument(''); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken); + + assert.strictEqual(Array.isArray(ranges) ? ranges.length : 0, 0); + }); + + test('single line document returns no folding ranges', () => { + const provider = new GitCommitFoldingProvider(); + const doc = createMockDocument('commit message'); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken); + + assert.strictEqual(Array.isArray(ranges) ? ranges.length : 0, 0); + }); + + test('single comment line returns no folding ranges', () => { + const provider = new GitCommitFoldingProvider(); + const doc = createMockDocument('# Comment'); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken); + + assert.strictEqual(Array.isArray(ranges) ? ranges.length : 0, 0); + }); + + test('two comment lines create one folding range', () => { + const provider = new GitCommitFoldingProvider(); + const content = '# Comment 1\n# Comment 2'; + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 1); + assert.strictEqual(ranges[0].start, 0); + assert.strictEqual(ranges[0].end, 1); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + }); + + test('multiple comment lines create one folding range', () => { + const provider = new GitCommitFoldingProvider(); + const content = '# Comment 1\n# Comment 2\n# Comment 3\n# Comment 4'; + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 1); + assert.strictEqual(ranges[0].start, 0); + assert.strictEqual(ranges[0].end, 3); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + }); + + test('comment block followed by content', () => { + const provider = new GitCommitFoldingProvider(); + const content = '# Comment 1\n# Comment 2\nCommit message'; + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 1); + assert.strictEqual(ranges[0].start, 0); + assert.strictEqual(ranges[0].end, 1); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + }); + + test('comment block at end of document', () => { + const provider = new GitCommitFoldingProvider(); + const content = 'Commit message\n\n# Comment 1\n# Comment 2'; + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 1); + assert.strictEqual(ranges[0].start, 2); + assert.strictEqual(ranges[0].end, 3); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + }); + + test('multiple separated comment blocks', () => { + const provider = new GitCommitFoldingProvider(); + const content = '# Comment 1\n# Comment 2\n\nCommit message\n\n# Comment 3\n# Comment 4'; + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 2); + assert.strictEqual(ranges[0].start, 0); + assert.strictEqual(ranges[0].end, 1); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + assert.strictEqual(ranges[1].start, 5); + assert.strictEqual(ranges[1].end, 6); + assert.strictEqual(ranges[1].kind, vscode.FoldingRangeKind.Comment); + }); + + test('single diff line returns no folding ranges', () => { + const provider = new GitCommitFoldingProvider(); + const doc = createMockDocument('diff --git a/file.txt b/file.txt'); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken); + + assert.strictEqual(Array.isArray(ranges) ? ranges.length : 0, 0); + }); + + test('diff block with content creates folding range', () => { + const provider = new GitCommitFoldingProvider(); + const content = 'diff --git a/file.txt b/file.txt\nindex 1234..5678\n--- a/file.txt\n+++ b/file.txt'; + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 1); + assert.strictEqual(ranges[0].start, 0); + assert.strictEqual(ranges[0].end, 3); + assert.strictEqual(ranges[0].kind, undefined); // Diff blocks don't have a specific kind + }); + + test('multiple diff blocks', () => { + const provider = new GitCommitFoldingProvider(); + const content = [ + 'diff --git a/file1.txt b/file1.txt', + '--- a/file1.txt', + '+++ b/file1.txt', + 'diff --git a/file2.txt b/file2.txt', + '--- a/file2.txt', + '+++ b/file2.txt' + ].join('\n'); + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 2); + assert.strictEqual(ranges[0].start, 0); + assert.strictEqual(ranges[0].end, 2); + assert.strictEqual(ranges[1].start, 3); + assert.strictEqual(ranges[1].end, 5); + }); + + test('diff block at end of document', () => { + const provider = new GitCommitFoldingProvider(); + const content = [ + 'Commit message', + '', + 'diff --git a/file.txt b/file.txt', + '--- a/file.txt', + '+++ b/file.txt' + ].join('\n'); + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 1); + assert.strictEqual(ranges[0].start, 2); + assert.strictEqual(ranges[0].end, 4); + }); + + test('realistic git commit message with comments and verbose diff', () => { + const provider = new GitCommitFoldingProvider(); + const content = [ + 'Add folding support for git commit messages', + '', + '# Please enter the commit message for your changes. Lines starting', + '# with \'#\' will be ignored, and an empty message aborts the commit.', + '#', + '# On branch main', + '# Changes to be committed:', + '#\tmodified: extension.ts', + '#\tnew file: foldingProvider.ts', + '#', + '# ------------------------ >8 ------------------------', + '# Do not modify or remove the line above.', + '# Everything below it will be ignored.', + 'diff --git a/extensions/git-base/src/extension.ts b/extensions/git-base/src/extension.ts', + 'index 17ffb89..453d8f7 100644', + '--- a/extensions/git-base/src/extension.ts', + '+++ b/extensions/git-base/src/extension.ts', + '@@ -3,14 +3,20 @@', + ' * Licensed under the MIT License.', + '-import { ExtensionContext } from \'vscode\';', + '+import { ExtensionContext, languages } from \'vscode\';', + 'diff --git a/extensions/git-base/src/foldingProvider.ts b/extensions/git-base/src/foldingProvider.ts', + 'new file mode 100644', + 'index 0000000..2c4a9c3', + '--- /dev/null', + '+++ b/extensions/git-base/src/foldingProvider.ts' + ].join('\n'); + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + // Should have one comment block and two diff blocks + assert.strictEqual(ranges.length, 3); + + // Comment block (lines 2-12) + assert.strictEqual(ranges[0].start, 2); + assert.strictEqual(ranges[0].end, 12); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + + // First diff block (lines 13-20) + assert.strictEqual(ranges[1].start, 13); + assert.strictEqual(ranges[1].end, 20); + assert.strictEqual(ranges[1].kind, undefined); + + // Second diff block (lines 21-25) + assert.strictEqual(ranges[2].start, 21); + assert.strictEqual(ranges[2].end, 25); + assert.strictEqual(ranges[2].kind, undefined); + }); + + test('mixed comment and diff content', () => { + const provider = new GitCommitFoldingProvider(); + const content = [ + 'Fix bug in parser', + '', + '# Comment 1', + '# Comment 2', + '', + 'diff --git a/file.txt b/file.txt', + '--- a/file.txt', + '+++ b/file.txt', + '', + '# Comment 3', + '# Comment 4' + ].join('\n'); + const doc = createMockDocument(content); + const ranges = provider.provideFoldingRanges(doc, mockContext, mockToken) as vscode.FoldingRange[]; + + assert.strictEqual(ranges.length, 3); + + // First comment block + assert.strictEqual(ranges[0].start, 2); + assert.strictEqual(ranges[0].end, 3); + assert.strictEqual(ranges[0].kind, vscode.FoldingRangeKind.Comment); + + // Diff block + assert.strictEqual(ranges[1].start, 5); + assert.strictEqual(ranges[1].end, 8); + assert.strictEqual(ranges[1].kind, undefined); + + // Second comment block + assert.strictEqual(ranges[2].start, 9); + assert.strictEqual(ranges[2].end, 10); + assert.strictEqual(ranges[2].kind, vscode.FoldingRangeKind.Comment); + }); +}); diff --git a/extensions/git-base/tsconfig.json b/extensions/git-base/tsconfig.json index d7aed1836eeda..796a159a61c21 100644 --- a/extensions/git-base/tsconfig.json +++ b/extensions/git-base/tsconfig.json @@ -2,7 +2,6 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "experimentalDecorators": true, "typeRoots": [ "./node_modules/@types" ] diff --git a/extensions/git/extension.webpack.config.js b/extensions/git/extension.webpack.config.js index 3324b6c1d988b..15cf273015b76 100644 --- a/extensions/git/extension.webpack.config.js +++ b/extensions/git/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { main: './src/main.ts', ['askpass-main']: './src/askpass-main.ts', diff --git a/extensions/git/package-lock.json b/extensions/git/package-lock.json index 4f119e2c3f894..45e3989e801f0 100644 --- a/extensions/git/package-lock.json +++ b/extensions/git/package-lock.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@types/byline": "4.2.31", - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/picomatch": "2.3.0", "@types/which": "3.0.0" @@ -176,10 +176,11 @@ } }, "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.10", diff --git a/extensions/git/package.json b/extensions/git/package.json index 47d013278b971..b8b97f4f116a9 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -16,6 +16,8 @@ "contribMergeEditorMenus", "contribMultiDiffEditorMenus", "contribDiffEditorGutterToolBarMenus", + "contribSourceControlArtifactGroupMenu", + "contribSourceControlArtifactMenu", "contribSourceControlHistoryItemMenu", "contribSourceControlHistoryTitleMenu", "contribSourceControlInputBoxMenu", @@ -26,6 +28,7 @@ "quickInputButtonLocation", "quickPickSortByLabel", "scmActionButton", + "scmArtifactProvider", "scmHistoryProvider", "scmMultiDiffEditor", "scmProviderOptions", @@ -136,6 +139,11 @@ "icon": "$(refresh)", "enablement": "!operationInProgress" }, + { + "command": "git.compareWithWorkspace", + "title": "%command.compareWithWorkspace%", + "category": "Git" + }, { "command": "git.openChange", "title": "%command.openChange%", @@ -440,13 +448,12 @@ { "command": "git.commitMessageAccept", "title": "%command.commitMessageAccept%", - "icon": "$(check)", "category": "Git" }, { "command": "git.commitMessageDiscard", "title": "%command.commitMessageDiscard%", - "icon": "$(discard)", + "icon": "$(close)", "category": "Git" }, { @@ -542,6 +549,7 @@ { "command": "git.createTag", "title": "%command.createTag%", + "icon": "$(plus)", "category": "Git", "enablement": "!operationInProgress" }, @@ -551,6 +559,12 @@ "category": "Git", "enablement": "!operationInProgress" }, + { + "command": "git.migrateWorktreeChanges", + "title": "%command.migrateWorktreeChanges%", + "category": "Git", + "enablement": "!operationInProgress" + }, { "command": "git.createWorktree", "title": "%command.createWorktree%", @@ -564,8 +578,8 @@ "enablement": "!operationInProgress" }, { - "command": "git.deleteWorktreeFromPalette", - "title": "%command.deleteWorktreeFromPalette%", + "command": "git.deleteWorktree2", + "title": "%command.deleteWorktree2%", "category": "Git", "enablement": "!operationInProgress" }, @@ -987,6 +1001,142 @@ "command": "git.blame.toggleStatusBarItem", "title": "%command.blameToggleStatusBarItem%", "category": "Git" + }, + { + "command": "git.graph.compareRef", + "title": "%command.graphCompareRef%", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.graph.compareWithRemote", + "title": "%command.graphCompareWithRemote%", + "category": "Git", + "enablement": "!operationInProgress && scmCurrentHistoryItemRefHasRemote" + }, + { + "command": "git.graph.compareWithMergeBase", + "title": "%command.graphCompareWithMergeBase%", + "category": "Git", + "enablement": "!operationInProgress && scmCurrentHistoryItemRefHasBase" + }, + { + "command": "git.repositories.checkout", + "title": "%command.graphCheckout%", + "icon": "$(target)", + "category": "Git", + "enablement": "!operationInProgress && !scmArtifactIsHistoryItemRef" + }, + { + "command": "git.repositories.checkoutDetached", + "title": "%command.graphCheckoutDetached%", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.compareRef", + "title": "%command.graphCompareRef%", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.createBranch", + "title": "%command.branch%", + "icon": "$(plus)", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.createTag", + "title": "%command.createTag%", + "icon": "$(plus)", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.merge", + "title": "%command.merge2%", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.rebase", + "title": "%command.rebase2%", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.deleteBranch", + "title": "%command.deleteRef%", + "category": "Git", + "enablement": "!operationInProgress && !scmArtifactIsHistoryItemRef" + }, + { + "command": "git.repositories.deleteTag", + "title": "%command.deleteRef%", + "category": "Git", + "enablement": "!operationInProgress && !scmArtifactIsHistoryItemRef" + }, + { + "command": "git.repositories.createFrom", + "title": "%command.createFrom%", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.stashView", + "title": "%command.stashView2%", + "icon": "$(diff-multiple)", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.stashApply", + "title": "%command.stashApplyEditor%", + "icon": "$(git-stash-apply)", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.stashPop", + "title": "%command.stashPopEditor%", + "icon": "$(git-stash-pop)", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.stashDrop", + "title": "%command.stashDropEditor%", + "icon": "$(trash)", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.createWorktree", + "title": "%command.createWorktree%", + "icon": "$(plus)", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.openWorktree", + "title": "%command.openWorktree2%", + "icon": "$(folder-opened)", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.openWorktreeInNewWindow", + "title": "%command.openWorktreeInNewWindow2%", + "icon": "$(folder-opened)", + "category": "Git", + "enablement": "!operationInProgress" + }, + { + "command": "git.repositories.deleteWorktree", + "title": "%command.deleteRef%", + "category": "Git", + "enablement": "!operationInProgress" } ], "continueEditSession": [ @@ -1328,12 +1478,12 @@ "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { - "command": "git.createWorktree", + "command": "git.migrateWorktreeChanges", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { - "command": "git.deleteWorktree", - "when": "false" + "command": "git.createWorktree", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, { "command": "git.openWorktree", @@ -1344,9 +1494,13 @@ "when": "false" }, { - "command": "git.deleteWorktreeFromPalette", + "command": "git.deleteWorktree", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" }, + { + "command": "git.deleteWorktree2", + "when": "false" + }, { "command": "git.deleteRemoteTag", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -1521,19 +1675,19 @@ }, { "command": "git.stashView", - "when": "config.git.enabled && !git.missing && config.multiDiffEditor.experimental.enabled" + "when": "config.git.enabled && !git.missing" }, { "command": "git.viewChanges", - "when": "config.git.enabled && !git.missing && config.multiDiffEditor.experimental.enabled" + "when": "config.git.enabled && !git.missing" }, { "command": "git.viewStagedChanges", - "when": "config.git.enabled && !git.missing && config.multiDiffEditor.experimental.enabled" + "when": "config.git.enabled && !git.missing" }, { "command": "git.viewUntrackedChanges", - "when": "config.git.enabled && !git.missing && config.multiDiffEditor.experimental.enabled && config.git.untrackedChanges == separate" + "when": "config.git.enabled && !git.missing && config.git.untrackedChanges == separate" }, { "command": "git.viewCommit", @@ -1579,6 +1733,10 @@ "command": "git.graph.deleteBranch", "when": "false" }, + { + "command": "git.graph.compareRef", + "when": "false" + }, { "command": "git.graph.deleteTag", "when": "false" @@ -1587,6 +1745,14 @@ "command": "git.graph.cherryPick", "when": "false" }, + { + "command": "git.graph.compareWithMergeBase", + "when": "false" + }, + { + "command": "git.graph.compareWithRemote", + "when": "false" + }, { "command": "git.diff.stageHunk", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && diffEditorOriginalUri =~ /^git\\:.*%22ref%22%3A%22~%22%7D$/" @@ -1594,6 +1760,78 @@ { "command": "git.diff.stageSelection", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && diffEditorOriginalUri =~ /^git\\:.*%22ref%22%3A%22~%22%7D$/" + }, + { + "command": "git.repositories.checkout", + "when": "false" + }, + { + "command": "git.repositories.checkoutDetached", + "when": "false" + }, + { + "command": "git.repositories.compareRef", + "when": "false" + }, + { + "command": "git.repositories.createBranch", + "when": "false" + }, + { + "command": "git.repositories.createTag", + "when": "false" + }, + { + "command": "git.repositories.merge", + "when": "false" + }, + { + "command": "git.repositories.rebase", + "when": "false" + }, + { + "command": "git.repositories.deleteBranch", + "when": "false" + }, + { + "command": "git.repositories.deleteTag", + "when": "false" + }, + { + "command": "git.repositories.createFrom", + "when": "false" + }, + { + "command": "git.repositories.stashView", + "when": "false" + }, + { + "command": "git.repositories.stashApply", + "when": "false" + }, + { + "command": "git.repositories.stashPop", + "when": "false" + }, + { + "command": "git.repositories.stashDrop", + "when": "false" + }, + { + "command": "git.repositories.createWorktree", + "when": "false" + }, + { + "command": "git.repositories.openWorktree", + "when": "false" + }, + { + "command": "git.repositories.openWorktreeInNewWindow", + "when": "false" + }, + { + "command": "git.repositories.deleteWorktree", + "when": "false" } ], "scm/title": [ @@ -1678,13 +1916,85 @@ "when": "scmProvider == git" } ], - "scm/sourceControl/title": [ + "scm/repositories/title": [ { "command": "git.reopenClosedRepositories", "group": "navigation@1", "when": "git.closedRepositoryCount > 0" } ], + "scm/repository": [ + { + "command": "git.pull", + "group": "1_header@1", + "when": "scmProvider == git" + }, + { + "command": "git.push", + "group": "1_header@2", + "when": "scmProvider == git" + }, + { + "command": "git.clone", + "group": "1_header@3", + "when": "scmProvider == git" + }, + { + "command": "git.checkout", + "group": "1_header@4", + "when": "scmProvider == git" + }, + { + "command": "git.fetch", + "group": "1_header@5", + "when": "scmProvider == git" + }, + { + "submenu": "git.commit", + "group": "2_main@1", + "when": "scmProvider == git" + }, + { + "submenu": "git.changes", + "group": "2_main@2", + "when": "scmProvider == git" + }, + { + "submenu": "git.pullpush", + "group": "2_main@3", + "when": "scmProvider == git" + }, + { + "submenu": "git.branch", + "group": "2_main@4", + "when": "scmProvider == git" + }, + { + "submenu": "git.remotes", + "group": "2_main@5", + "when": "scmProvider == git" + }, + { + "submenu": "git.stash", + "group": "2_main@6", + "when": "scmProvider == git" + }, + { + "submenu": "git.tags", + "group": "2_main@7", + "when": "scmProvider == git" + }, + { + "submenu": "git.worktrees", + "group": "2_main@8", + "when": "scmProvider == git" + }, + { + "command": "git.showOutput", + "group": "3_footer", + "when": "scmProvider == git" + } + ], "scm/sourceControl": [ { "command": "git.close", @@ -1705,6 +2015,126 @@ "command": "git.openWorktreeInNewWindow", "group": "1_worktree@2", "when": "scmProvider == git && scmProviderContext == worktree" + }, + { + "command": "git.deleteWorktree2", + "group": "2_worktree@1", + "when": "scmProvider == git && scmProviderContext == worktree" + } + ], + "scm/artifactGroup/context": [ + { + "command": "git.repositories.createBranch", + "group": "inline@1", + "when": "scmProvider == git && scmArtifactGroup == branches" + }, + { + "command": "git.repositories.createTag", + "group": "inline@1", + "when": "scmProvider == git && scmArtifactGroup == tags" + }, + { + "submenu": "git.repositories.stash", + "group": "inline@1", + "when": "scmProvider == git && scmArtifactGroup == stashes" + }, + { + "command": "git.repositories.createWorktree", + "group": "inline@1", + "when": "scmProvider == git && scmArtifactGroup == worktrees" + } + ], + "scm/artifact/context": [ + { + "command": "git.repositories.checkout", + "group": "inline@1", + "when": "scmProvider == git && (scmArtifactGroupId == branches || scmArtifactGroupId == tags)" + }, + { + "command": "git.repositories.stashApply", + "alt": "git.repositories.stashPop", + "group": "inline@1", + "when": "scmProvider == git && scmArtifactGroupId == stashes" + }, + { + "command": "git.repositories.stashView", + "group": "1_view@1", + "when": "scmProvider == git && scmArtifactGroupId == stashes" + }, + { + "command": "git.repositories.stashApply", + "group": "2_apply@1", + "when": "scmProvider == git && scmArtifactGroupId == stashes" + }, + { + "command": "git.repositories.stashPop", + "group": "2_apply@2", + "when": "scmProvider == git && scmArtifactGroupId == stashes" + }, + { + "command": "git.repositories.stashDrop", + "group": "3_drop@3", + "when": "scmProvider == git && scmArtifactGroupId == stashes" + }, + { + "command": "git.repositories.checkout", + "group": "1_checkout@1", + "when": "scmProvider == git && (scmArtifactGroupId == branches || scmArtifactGroupId == tags)" + }, + { + "command": "git.repositories.checkoutDetached", + "group": "1_checkout@2", + "when": "scmProvider == git && (scmArtifactGroupId == branches || scmArtifactGroupId == tags)" + }, + { + "command": "git.repositories.merge", + "group": "2_modify@1", + "when": "scmProvider == git && scmArtifactGroupId == branches" + }, + { + "command": "git.repositories.rebase", + "group": "2_modify@2", + "when": "scmProvider == git && scmArtifactGroupId == branches" + }, + { + "command": "git.repositories.createFrom", + "group": "3_modify@1", + "when": "scmProvider == git && scmArtifactGroupId == branches" + }, + { + "command": "git.repositories.deleteBranch", + "group": "3_modify@2", + "when": "scmProvider == git && scmArtifactGroupId == branches" + }, + { + "command": "git.repositories.deleteTag", + "group": "3_modify@1", + "when": "scmProvider == git && scmArtifactGroupId == tags" + }, + { + "command": "git.repositories.compareRef", + "group": "4_compare@1", + "when": "scmProvider == git && (scmArtifactGroupId == branches || scmArtifactGroupId == tags)" + }, + { + "command": "git.repositories.openWorktreeInNewWindow", + "group": "inline@1", + "when": "scmProvider == git && scmArtifactGroupId == worktrees" + }, + { + "command": "git.repositories.openWorktree", + "group": "1_open@1", + "when": "scmProvider == git && scmArtifactGroupId == worktrees" + }, + { + "command": "git.repositories.openWorktreeInNewWindow", + "group": "1_open@2", + "when": "scmProvider == git && scmArtifactGroupId == worktrees" + }, + { + "command": "git.repositories.deleteWorktree", + "group": "2_modify@1", + "when": "scmProvider == git && scmArtifactGroupId == worktrees" } ], "scm/resourceGroup/context": [ @@ -1730,12 +2160,12 @@ }, { "command": "git.viewStagedChanges", - "when": "scmProvider == git && scmResourceGroup == index && config.multiDiffEditor.experimental.enabled", + "when": "scmProvider == git && scmResourceGroup == index", "group": "inline@1" }, { "command": "git.viewChanges", - "when": "scmProvider == git && scmResourceGroup == workingTree && config.multiDiffEditor.experimental.enabled", + "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "inline@1" }, { @@ -1790,7 +2220,7 @@ }, { "command": "git.viewUntrackedChanges", - "when": "scmProvider == git && scmResourceGroup == untracked && config.multiDiffEditor.experimental.enabled", + "when": "scmProvider == git && scmResourceGroup == untracked", "group": "inline@1" }, { @@ -1967,6 +2397,11 @@ "when": "scmProvider == git && scmResourceGroup == index", "group": "2_view@2" }, + { + "command": "git.compareWithWorkspace", + "when": "scmProvider == git && scmResourceGroup == index && scmResourceState == worktree", + "group": "worktree_diff" + }, { "command": "git.openFile2", "when": "scmProvider == git && scmResourceGroup == index && config.git.showInlineOpenFileAction && config.git.openDiffOnClick", @@ -2012,6 +2447,11 @@ "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "inline@2" }, + { + "command": "git.compareWithWorkspace", + "when": "scmProvider == git && scmResourceGroup == workingTree && scmResourceState == worktree", + "group": "worktree_diff" + }, { "command": "git.openFile2", "when": "scmProvider == git && scmResourceGroup == workingTree && config.git.showInlineOpenFileAction && config.git.openDiffOnClick", @@ -2141,6 +2581,21 @@ "when": "scmProvider == git", "group": "4_modify@1" }, + { + "command": "git.graph.compareWithRemote", + "when": "scmProvider == git", + "group": "5_compare@1" + }, + { + "command": "git.graph.compareWithMergeBase", + "when": "scmProvider == git", + "group": "5_compare@2" + }, + { + "command": "git.graph.compareRef", + "when": "scmProvider == git", + "group": "5_compare@3" + }, { "command": "git.copyCommitId", "when": "scmProvider == git && !listMultiSelection", @@ -2181,19 +2636,14 @@ "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInNotebookTextDiffEditor && resourceScheme =~ /^git$|^file$/" }, { - "command": "git.openChange", - "group": "navigation@2", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && !isMergeEditor && resourceScheme == file && scmActiveResourceHasChanges" - }, - { - "command": "git.commitMessageAccept", + "command": "git.openFile", "group": "navigation", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit" + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && !isInNotebookTextDiffEditor && resourceScheme == git" }, { - "command": "git.commitMessageDiscard", - "group": "navigation", - "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit" + "command": "git.openChange", + "group": "navigation@2", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && !isMergeEditor && resourceScheme == file && scmActiveResourceHasChanges" }, { "command": "git.stashApplyEditor", @@ -2267,7 +2717,17 @@ { "command": "git.openMergeEditor", "group": "navigation@-10", - "when": "config.git.enabled && !git.missing && !isInDiffEditor && !isMergeEditor && resource in git.mergeChanges" + "when": "config.git.enabled && !git.missing && !isInDiffEditor && !isMergeEditor && resource in git.mergeChanges && git.activeResourceHasMergeConflicts" + }, + { + "command": "git.commitMessageAccept", + "group": "navigation", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit" + }, + { + "command": "git.commitMessageDiscard", + "group": "secondary", + "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit" } ], "multiDiffEditor/resource/title": [ @@ -2319,7 +2779,7 @@ { "command": "git.timeline.viewCommit", "group": "inline", - "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/ && !listMultiSelection && config.multiDiffEditor.experimental.enabled" + "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/ && !listMultiSelection" }, { "command": "git.timeline.openDiff", @@ -2329,7 +2789,7 @@ { "command": "git.timeline.viewCommit", "group": "1_actions@2", - "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/ && !listMultiSelection && config.multiDiffEditor.experimental.enabled" + "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/ && !listMultiSelection" }, { "command": "git.timeline.compareWithSelected", @@ -2594,22 +3054,40 @@ }, { "command": "git.stashView", - "when": "config.multiDiffEditor.experimental.enabled", "group": "5_preview@1" } ], + "git.repositories.stash": [ + { + "command": "git.stash", + "group": "1_stash@1" + }, + { + "command": "git.stashStaged", + "when": "gitVersion2.35", + "group": "2_stash@1" + }, + { + "command": "git.stashIncludeUntracked", + "group": "2_stash@2" + } + ], "git.tags": [ { "command": "git.createTag", - "group": "tags@1" + "group": "1_tags@1" }, { "command": "git.deleteTag", - "group": "tags@2" + "group": "1_tags@2" }, { "command": "git.deleteRemoteTag", - "group": "tags@3" + "group": "1_tags@3" + }, + { + "command": "git.pushTags", + "group": "2_tags@1" } ], "git.worktrees": [ @@ -2630,7 +3108,7 @@ }, { "when": "scmProviderContext == worktree", - "command": "git.deleteWorktree", + "command": "git.deleteWorktree2", "group": "worktrees@2" } ] @@ -2667,6 +3145,11 @@ { "id": "git.worktrees", "label": "%submenu.worktrees%" + }, + { + "id": "git.repositories.stash", + "label": "%submenu.stash%", + "icon": "$(plus)" } ], "configuration": { @@ -3085,7 +3568,7 @@ "git.detectWorktreesLimit": { "type": "number", "scope": "resource", - "default": 10, + "default": 50, "description": "%config.detectWorktreesLimit%" }, "git.alwaysShowStagedChangesResourceGroup": { @@ -3406,6 +3889,11 @@ "default": "${subject}, ${authorName} (${authorDateAgo})", "markdownDescription": "%config.blameEditorDecoration.template%" }, + "git.blame.editorDecoration.disableHover": { + "type": "boolean", + "default": false, + "markdownDescription": "%config.blameEditorDecoration.disableHover%" + }, "git.blame.statusBarItem.enabled": { "type": "boolean", "default": true, @@ -3416,6 +3904,11 @@ "default": "${authorName} (${authorDateAgo})", "markdownDescription": "%config.blameStatusBarItem.template%" }, + "git.blame.ignoreWhitespace": { + "type": "boolean", + "default": false, + "markdownDescription": "%config.blameIgnoreWhitespace%" + }, "git.commitShortHashLength": { "type": "number", "default": 7, @@ -3702,7 +4195,7 @@ }, "devDependencies": { "@types/byline": "4.2.31", - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/picomatch": "2.3.0", "@types/which": "3.0.0" diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 07204a1dd3438..c8834151c6f89 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -11,8 +11,11 @@ "command.close": "Close Repository", "command.closeOtherRepositories": "Close Other Repositories", "command.openWorktree": "Open Worktree in Current Window", + "command.openWorktree2": "Open", "command.openWorktreeInNewWindow": "Open Worktree in New Window", + "command.openWorktreeInNewWindow2": "Open in New Window", "command.refresh": "Refresh", + "command.compareWithWorkspace": "Compare with Workspace", "command.openChange": "Open Changes", "command.openAllChanges": "Open All Changes", "command.openFile": "Open File", @@ -59,8 +62,8 @@ "command.commitAllNoVerify": "Commit All (No Verify)", "command.commitAllSignedNoVerify": "Commit All (Signed Off, No Verify)", "command.commitAllAmendNoVerify": "Commit All (Amend, No Verify)", - "command.commitMessageAccept": "Accept Commit Message", - "command.commitMessageDiscard": "Discard Commit Message", + "command.commitMessageAccept": "Commit", + "command.commitMessageDiscard": "Cancel", "command.restoreCommitTemplate": "Restore Commit Template", "command.undoCommit": "Undo Last Commit", "command.checkout": "Checkout to...", @@ -73,13 +76,17 @@ "command.cherryPick": "Cherry Pick...", "command.cherryPickAbort": "Abort Cherry Pick", "command.merge": "Merge...", + "command.merge2": "Merge", "command.mergeAbort": "Abort Merge", "command.rebase": "Rebase Branch...", + "command.rebase2": "Rebase", + "command.createFrom": "Create from...", "command.createTag": "Create Tag...", "command.deleteTag": "Delete Tag...", + "command.migrateWorktreeChanges": "Migrate Worktree Changes...", "command.createWorktree": "Create Worktree...", - "command.deleteWorktree": "Delete Worktree", - "command.deleteWorktreeFromPalette": "Delete Worktree...", + "command.deleteWorktree": "Delete Worktree...", + "command.deleteWorktree2": "Delete Worktree", "command.deleteRemoteTag": "Delete Remote Tag...", "command.fetch": "Fetch", "command.fetchPrune": "Fetch (Prune)", @@ -119,6 +126,7 @@ "command.stashDropAll": "Drop All Stashes...", "command.stashDropEditor": "Drop Stash", "command.stashView": "View Stash...", + "command.stashView2": "View Stash", "command.timelineOpenDiff": "Open Changes", "command.timelineCopyCommitId": "Copy Commit ID", "command.timelineCopyCommitMessage": "Copy Commit Message", @@ -135,6 +143,10 @@ "command.graphCherryPick": "Cherry Pick", "command.graphDeleteBranch": "Delete Branch", "command.graphDeleteTag": "Delete Tag", + "command.graphCompareRef": "Compare with...", + "command.graphCompareWithMergeBase": "Compare with Merge Base", + "command.graphCompareWithRemote": "Compare with Remote", + "command.deleteRef": "Delete", "command.blameToggleEditorDecoration": "Toggle Git Blame Editor Decoration", "command.blameToggleStatusBarItem": "Toggle Git Blame Status Bar Item", "command.api.getRepositories": "Get Repositories", @@ -291,8 +303,10 @@ "config.similarityThreshold": "Controls the threshold of the similarity index (the amount of additions/deletions compared to the file's size) for changes in a pair of added/deleted files to be considered a rename. **Note:** Requires Git version `2.18.0` or later.", "config.blameEditorDecoration.enabled": "Controls whether to show blame information in the editor using editor decorations.", "config.blameEditorDecoration.template": "Template for the blame information editor decoration. Supported variables:\n\n* `hash`: Commit hash\n\n* `hashShort`: First N characters of the commit hash according to `#git.commitShortHashLength#`\n\n* `subject`: First line of the commit message\n\n* `authorName`: Author name\n\n* `authorEmail`: Author email\n\n* `authorDate`: Author date\n\n* `authorDateAgo`: Time difference between now and the author date\n\n", + "config.blameEditorDecoration.disableHover": "Controls whether to disable the blame information editor decoration hover.", "config.blameStatusBarItem.enabled": "Controls whether to show blame information in the status bar.", "config.blameStatusBarItem.template": "Template for the blame information status bar item. Supported variables:\n\n* `hash`: Commit hash\n\n* `hashShort`: First N characters of the commit hash according to `#git.commitShortHashLength#`\n\n* `subject`: First line of the commit message\n\n* `authorName`: Author name\n\n* `authorEmail`: Author email\n\n* `authorDate`: Author date\n\n* `authorDateAgo`: Time difference between now and the author date\n\n", + "config.blameIgnoreWhitespace": "Controls whether to ignore whitespace changes when computing blame information.", "config.commitShortHashLength": "Controls the length of the commit short hash.", "config.diagnosticsCommitHook.enabled": "Controls whether to check for unresolved diagnostics before committing.", "config.diagnosticsCommitHook.sources": "Controls the list of sources (**Item**) and the minimum severity (**Value**) to be considered before committing. **Note:** To ignore diagnostics from a particular source, add the source to the list and set the minimum severity to `none`.", diff --git a/extensions/git/src/api/api1.ts b/extensions/git/src/api/api1.ts index 2be6cec8dea4a..8e083199ac132 100644 --- a/extensions/git/src/api/api1.ts +++ b/extensions/git/src/api/api1.ts @@ -7,7 +7,7 @@ import { Model } from '../model'; import { Repository as BaseRepository, Resource } from '../repository'; -import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailsProvider, GitErrorCodes } from './git'; +import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, PostCommitCommandsProvider, RefQuery, BranchProtectionProvider, InitOptions, SourceControlHistoryItemDetailsProvider, GitErrorCodes, CloneOptions, CommitShortStat, DiffChange, Worktree } from './git'; import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode'; import { combinedDisposable, filterEvent, mapEvent } from '../util'; import { toGitUri } from '../uri'; @@ -15,6 +15,7 @@ import { GitExtensionImpl } from './extension'; import { GitBaseApi } from '../git-base'; import { PickRemoteSourceOptions } from '../typings/git-base'; import { OperationKind, OperationResult } from '../operation'; +import { CloneManager } from '../cloneManager'; class ApiInputBox implements InputBox { #inputBox: SourceControlInputBox; @@ -51,6 +52,7 @@ export class ApiRepositoryState implements RepositoryState { get refs(): Ref[] { console.warn('Deprecated. Use ApiRepository.getRefs() instead.'); return []; } get remotes(): Remote[] { return [...this.#repository.remotes]; } get submodules(): Submodule[] { return [...this.#repository.submodules]; } + get worktrees(): Worktree[] { return this.#repository.worktrees; } get rebaseCommit(): Commit | undefined { return this.#repository.rebaseCommit; } get mergeChanges(): Change[] { return this.#repository.mergeGroup.resourceStates.map(r => new ApiChange(r)); } @@ -162,6 +164,10 @@ export class ApiRepository implements Repository { return this.#repository.diffWithHEAD(path); } + diffWithHEADShortStats(path?: string): Promise { + return this.#repository.diffWithHEADShortStats(path); + } + diffWith(ref: string): Promise; diffWith(ref: string, path: string): Promise; diffWith(ref: string, path?: string): Promise { @@ -174,6 +180,10 @@ export class ApiRepository implements Repository { return this.#repository.diffIndexWithHEAD(path); } + diffIndexWithHEADShortStats(path?: string): Promise { + return this.#repository.diffIndexWithHEADShortStats(path); + } + diffIndexWith(ref: string): Promise; diffIndexWith(ref: string, path: string): Promise; diffIndexWith(ref: string, path?: string): Promise { @@ -190,6 +200,10 @@ export class ApiRepository implements Repository { return this.#repository.diffBetween(ref1, ref2, path); } + diffBetweenWithStats(ref1: string, ref2: string, path?: string): Promise { + return this.#repository.diffBetweenWithStats(ref1, ref2, path); + } + hashObject(data: string): Promise { return this.#repository.hashObject(data); } @@ -298,6 +312,10 @@ export class ApiRepository implements Repository { return this.#repository.mergeAbort(); } + createStash(options?: { message?: string; includeUntracked?: boolean; staged?: boolean }): Promise { + return this.#repository.createStash(options?.message, options?.includeUntracked, options?.staged); + } + applyStash(index?: number): Promise { return this.#repository.applyStash(index); } @@ -309,6 +327,18 @@ export class ApiRepository implements Repository { dropStash(index?: number): Promise { return this.#repository.dropStash(index); } + + createWorktree(options?: { path?: string; commitish?: string; branch?: string }): Promise { + return this.#repository.createWorktree(options); + } + + deleteWorktree(path: string, options?: { force?: boolean }): Promise { + return this.#repository.deleteWorktree(path, options); + } + + migrateChanges(sourceRepositoryPath: string, options?: { confirmation?: boolean; deleteFromSource?: boolean; untracked?: boolean }): Promise { + return this.#repository.migrateChanges(sourceRepositoryPath, options); + } } export class ApiGit implements Git { @@ -331,10 +361,12 @@ export class ApiGit implements Git { export class ApiImpl implements API { #model: Model; + #cloneManager: CloneManager; readonly git: ApiGit; - constructor(model: Model) { - this.#model = model; + constructor(privates: { model: Model; cloneManager: CloneManager }) { + this.#model = privates.model; + this.#cloneManager = privates.cloneManager; this.git = new ApiGit(this.#model); } @@ -392,6 +424,11 @@ export class ApiImpl implements API { } } + async getRepositoryWorkspace(uri: Uri): Promise { + const workspaces = this.#model.repositoryCache.get(uri.toString()); + return workspaces ? workspaces.map(r => Uri.file(r.workspacePath)) : null; + } + async init(root: Uri, options?: InitOptions): Promise { const path = root.fsPath; await this.#model.git.init(path, options); @@ -399,6 +436,12 @@ export class ApiImpl implements API { return this.getRepository(root) || null; } + async clone(uri: Uri, options?: CloneOptions): Promise { + const parentPath = options?.parentPath?.fsPath; + const result = await this.#cloneManager.clone(uri.toString(), { parentPath, recursive: options?.recursive, ref: options?.ref, postCloneAction: options?.postCloneAction }); + return result ? Uri.file(result) : null; + } + async openRepository(root: Uri): Promise { if (root.scheme !== 'file') { return null; @@ -511,6 +554,7 @@ export function registerAPICommands(extension: GitExtensionImpl): Disposable { refs: state.refs.map(ref), remotes: state.remotes, submodules: state.submodules, + worktrees: state.worktrees, rebaseCommit: state.rebaseCommit, mergeChanges: state.mergeChanges.map(change), indexChanges: state.indexChanges.map(change), diff --git a/extensions/git/src/api/extension.ts b/extensions/git/src/api/extension.ts index bfedc0cc9093b..a716fa00dae28 100644 --- a/extensions/git/src/api/extension.ts +++ b/extensions/git/src/api/extension.ts @@ -7,16 +7,17 @@ import { Model } from '../model'; import { GitExtension, Repository, API } from './git'; import { ApiRepository, ApiImpl } from './api1'; import { Event, EventEmitter } from 'vscode'; +import { CloneManager } from '../cloneManager'; -export function deprecated(_target: any, key: string, descriptor: any): void { - if (typeof descriptor.value !== 'function') { +function deprecated(original: unknown, context: ClassMemberDecoratorContext) { + if (typeof original !== 'function' || context.kind !== 'method') { throw new Error('not supported'); } - const fn = descriptor.value; - descriptor.value = function () { + const key = context.name.toString(); + return function (this: unknown, ...args: unknown[]) { console.warn(`Git extension API method '${key}' is deprecated.`); - return fn.apply(this, arguments); + return original.apply(this, args); }; } @@ -28,6 +29,7 @@ export class GitExtensionImpl implements GitExtension { readonly onDidChangeEnablement: Event = this._onDidChangeEnablement.event; private _model: Model | undefined = undefined; + private _cloneManager: CloneManager | undefined = undefined; set model(model: Model | undefined) { this._model = model; @@ -46,10 +48,15 @@ export class GitExtensionImpl implements GitExtension { return this._model; } - constructor(model?: Model) { - if (model) { + set cloneManager(cloneManager: CloneManager | undefined) { + this._cloneManager = cloneManager; + } + + constructor(privates?: { model: Model; cloneManager: CloneManager }) { + if (privates) { this.enabled = true; - this._model = model; + this._model = privates.model; + this._cloneManager = privates.cloneManager; } } @@ -72,7 +79,7 @@ export class GitExtensionImpl implements GitExtension { } getAPI(version: number): API { - if (!this._model) { + if (!this._model || !this._cloneManager) { throw new Error('Git model not found'); } @@ -80,6 +87,6 @@ export class GitExtensionImpl implements GitExtension { throw new Error(`No API version ${version} found.`); } - return new ApiImpl(this._model); + return new ApiImpl({ model: this._model, cloneManager: this._cloneManager }); } } diff --git a/extensions/git/src/api/git.d.ts b/extensions/git/src/api/git.d.ts index fdfb8b397bca0..dd42714ab094d 100644 --- a/extensions/git/src/api/git.d.ts +++ b/extensions/git/src/api/git.d.ts @@ -76,6 +76,13 @@ export interface Remote { readonly isReadOnly: boolean; } +export interface Worktree { + readonly name: string; + readonly path: string; + readonly ref: string; + readonly detached: boolean; +} + export const enum Status { INDEX_MODIFIED, INDEX_ADDED, @@ -113,11 +120,17 @@ export interface Change { readonly status: Status; } +export interface DiffChange extends Change { + readonly insertions: number; + readonly deletions: number; +} + export interface RepositoryState { readonly HEAD: Branch | undefined; readonly refs: Ref[]; readonly remotes: Remote[]; readonly submodules: Submodule[]; + readonly worktrees: Worktree[]; readonly rebaseCommit: Commit | undefined; readonly mergeChanges: Change[]; @@ -183,11 +196,24 @@ export interface InitOptions { defaultBranch?: string; } +export interface CloneOptions { + parentPath?: Uri; + /** + * ref is only used if the repository cache is missed. + */ + ref?: string; + recursive?: boolean; + /** + * If no postCloneAction is provided, then the users setting for git.openAfterClone is used. + */ + postCloneAction?: 'none'; +} + export interface RefQuery { readonly contains?: string; readonly count?: number; readonly pattern?: string | string[]; - readonly sort?: 'alphabetically' | 'committerdate'; + readonly sort?: 'alphabetically' | 'committerdate' | 'creatordate'; } export interface BranchQuery extends RefQuery { @@ -224,15 +250,18 @@ export interface Repository { diff(cached?: boolean): Promise; diffWithHEAD(): Promise; diffWithHEAD(path: string): Promise; + diffWithHEADShortStats(path?: string): Promise; diffWith(ref: string): Promise; diffWith(ref: string, path: string): Promise; diffIndexWithHEAD(): Promise; diffIndexWithHEAD(path: string): Promise; + diffIndexWithHEADShortStats(path?: string): Promise; diffIndexWith(ref: string): Promise; diffIndexWith(ref: string, path: string): Promise; diffBlobs(object1: string, object2: string): Promise; diffBetween(ref1: string, ref2: string): Promise; diffBetween(ref1: string, ref2: string, path: string): Promise; + diffBetweenWithStats(ref1: string, ref2: string, path?: string): Promise; hashObject(data: string): Promise; @@ -249,7 +278,7 @@ export interface Repository { getMergeBase(ref1: string, ref2: string): Promise; - tag(name: string, upstream: string): Promise; + tag(name: string, message: string, ref?: string | undefined): Promise; deleteTag(name: string): Promise; status(): Promise; @@ -271,9 +300,15 @@ export interface Repository { merge(ref: string): Promise; mergeAbort(): Promise; + createStash(options?: { message?: string; includeUntracked?: boolean; staged?: boolean }): Promise; applyStash(index?: number): Promise; popStash(index?: number): Promise; dropStash(index?: number): Promise; + + createWorktree(options?: { path?: string; commitish?: string; branch?: string }): Promise; + deleteWorktree(path: string, options?: { force?: boolean }): Promise; + + migrateChanges(sourceRepositoryPath: string, options?: { confirmation?: boolean; deleteFromSource?: boolean; untracked?: boolean }): Promise; } export interface RemoteSource { @@ -365,8 +400,15 @@ export interface API { toGitUri(uri: Uri, ref: string): Uri; getRepository(uri: Uri): Repository | null; getRepositoryRoot(uri: Uri): Promise; + getRepositoryWorkspace(uri: Uri): Promise; init(root: Uri, options?: InitOptions): Promise; - openRepository(root: Uri): Promise + /** + * Checks the cache of known cloned repositories, and clones if the repository is not found. + * Make sure to pass `postCloneAction` 'none' if you want to have the uri where you can find the repository returned. + * @returns The URI of a folder or workspace file which, when opened, will open the cloned repository. + */ + clone(uri: Uri, options?: CloneOptions): Promise; + openRepository(root: Uri): Promise; registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable; registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; diff --git a/extensions/git/src/artifactProvider.ts b/extensions/git/src/artifactProvider.ts new file mode 100644 index 0000000000000..f99e262b9c43f --- /dev/null +++ b/extensions/git/src/artifactProvider.ts @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LogOutputChannel, SourceControlArtifactProvider, SourceControlArtifactGroup, SourceControlArtifact, Event, EventEmitter, ThemeIcon, l10n, workspace, Uri, Disposable, Command } from 'vscode'; +import { coalesce, dispose, filterEvent, IDisposable, isCopilotWorktree } from './util'; +import { Repository } from './repository'; +import { Commit, Ref, RefType } from './api/git'; +import { OperationKind } from './operation'; + +/** + * Sorts refs like a directory tree: refs with more path segments (directories) appear first + * and are sorted alphabetically, while refs at the same level (files) maintain insertion order. + * Refs without '/' maintain their insertion order and appear after refs with '/'. + */ +function sortRefByName(refA: Ref, refB: Ref): number { + const nameA = refA.name ?? ''; + const nameB = refB.name ?? ''; + + const lastSlashA = nameA.lastIndexOf('/'); + const lastSlashB = nameB.lastIndexOf('/'); + + // Neither ref has a slash, maintain insertion order + if (lastSlashA === -1 && lastSlashB === -1) { + return 0; + } + + // Ref with a slash comes first + if (lastSlashA !== -1 && lastSlashB === -1) { + return -1; + } else if (lastSlashA === -1 && lastSlashB !== -1) { + return 1; + } + + // Both have slashes + // Get directory segments + const segmentsA = nameA.substring(0, lastSlashA).split('/'); + const segmentsB = nameB.substring(0, lastSlashB).split('/'); + + // Compare directory segments + for (let index = 0; index < Math.min(segmentsA.length, segmentsB.length); index++) { + const result = segmentsA[index].localeCompare(segmentsB[index]); + if (result !== 0) { + return result; + } + } + + // Directory with more segments comes first + if (segmentsA.length !== segmentsB.length) { + return segmentsB.length - segmentsA.length; + } + + // Insertion order + return 0; +} + +function sortByCommitDateDesc(a: { commitDetails?: Commit }, b: { commitDetails?: Commit }): number { + const aCommitDate = a.commitDetails?.commitDate?.getTime() ?? 0; + const bCommitDate = b.commitDetails?.commitDate?.getTime() ?? 0; + + return bCommitDate - aCommitDate; +} + +export class GitArtifactProvider implements SourceControlArtifactProvider, IDisposable { + private readonly _onDidChangeArtifacts = new EventEmitter(); + readonly onDidChangeArtifacts: Event = this._onDidChangeArtifacts.event; + + private readonly _groups: SourceControlArtifactGroup[]; + private readonly _disposables: Disposable[] = []; + + constructor( + private readonly repository: Repository, + private readonly logger: LogOutputChannel + ) { + this._groups = [ + { id: 'branches', name: l10n.t('Branches'), icon: new ThemeIcon('git-branch'), supportsFolders: true }, + { id: 'stashes', name: l10n.t('Stashes'), icon: new ThemeIcon('git-stash'), supportsFolders: false }, + { id: 'tags', name: l10n.t('Tags'), icon: new ThemeIcon('tag'), supportsFolders: true }, + { id: 'worktrees', name: l10n.t('Worktrees'), icon: new ThemeIcon('worktree'), supportsFolders: false } + ]; + + this._disposables.push(this._onDidChangeArtifacts); + this._disposables.push(repository.historyProvider.onDidChangeHistoryItemRefs(e => { + const groups = new Set(); + for (const ref of e.added.concat(e.modified).concat(e.removed)) { + if (ref.id.startsWith('refs/heads/')) { + groups.add('branches'); + } else if (ref.id.startsWith('refs/tags/')) { + groups.add('tags'); + } + } + + this._onDidChangeArtifacts.fire(Array.from(groups)); + })); + + const onDidRunWriteOperation = filterEvent( + repository.onDidRunOperation, e => !e.operation.readOnly); + + this._disposables.push(onDidRunWriteOperation(result => { + if (result.operation.kind === OperationKind.Stash) { + this._onDidChangeArtifacts.fire(['stashes']); + } else if (result.operation.kind === OperationKind.Worktree) { + this._onDidChangeArtifacts.fire(['worktrees']); + } + })); + } + + provideArtifactGroups(): SourceControlArtifactGroup[] { + return this._groups; + } + + async provideArtifacts(group: string): Promise { + const config = workspace.getConfiguration('git', Uri.file(this.repository.root)); + const shortCommitLength = config.get('commitShortHashLength', 7); + + try { + if (group === 'branches') { + const refs = await this.repository + .getRefs({ pattern: 'refs/heads', includeCommitDetails: true, sort: 'creatordate' }); + + return refs.sort(sortRefByName).map(r => ({ + id: `refs/heads/${r.name}`, + name: r.name ?? r.commit ?? '', + description: coalesce([ + r.commit?.substring(0, shortCommitLength), + r.commitDetails?.message.split('\n')[0] + ]).join(' \u2022 '), + icon: this.repository.HEAD?.type === RefType.Head && r.name === this.repository.HEAD?.name + ? new ThemeIcon('target') + : new ThemeIcon('git-branch'), + timestamp: r.commitDetails?.commitDate?.getTime() + })); + } else if (group === 'tags') { + const refs = await this.repository + .getRefs({ pattern: 'refs/tags', includeCommitDetails: true, sort: 'creatordate' }); + + return refs.sort(sortRefByName).map(r => ({ + id: `refs/tags/${r.name}`, + name: r.name ?? r.commit ?? '', + description: coalesce([ + r.commit?.substring(0, shortCommitLength), + r.commitDetails?.message.split('\n')[0] + ]).join(' \u2022 '), + icon: this.repository.HEAD?.type === RefType.Tag && r.name === this.repository.HEAD?.name + ? new ThemeIcon('target') + : new ThemeIcon('tag'), + timestamp: r.commitDetails?.commitDate?.getTime() + })); + } else if (group === 'stashes') { + const stashes = await this.repository.getStashes(); + + return stashes.map(s => ({ + id: `stash@{${s.index}}`, + name: s.description, + description: s.branchName, + icon: new ThemeIcon('git-stash'), + timestamp: s.commitDate?.getTime(), + command: { + title: l10n.t('View Stash'), + command: 'git.repositories.stashView' + } satisfies Command + })); + } else if (group === 'worktrees') { + const worktrees = await this.repository.getWorktreeDetails(); + + return worktrees.sort(sortByCommitDateDesc).map(w => ({ + id: w.path, + name: w.name, + description: coalesce([ + w.detached ? l10n.t('detached') : w.ref.substring(11), + w.commitDetails?.hash.substring(0, shortCommitLength), + w.commitDetails?.message.split('\n')[0] + ]).join(' \u2022 '), + icon: isCopilotWorktree(w.path) + ? new ThemeIcon('chat-sparkle') + : new ThemeIcon('worktree'), + timestamp: w.commitDetails?.commitDate?.getTime(), + })); + } + } catch (err) { + this.logger.error(`[GitArtifactProvider][provideArtifacts] Error while providing artifacts for group '${group}': `, err); + return []; + } + + return []; + } + + dispose(): void { + dispose(this._disposables); + } +} diff --git a/extensions/git/src/askpass-main.ts b/extensions/git/src/askpass-main.ts index cb93adf2821d3..21402fbaf344d 100644 --- a/extensions/git/src/askpass-main.ts +++ b/extensions/git/src/askpass-main.ts @@ -6,7 +6,7 @@ import * as fs from 'fs'; import { IPCClient } from './ipc/ipcClient'; -function fatal(err: any): void { +function fatal(err: unknown): void { console.error('Missing or invalid credentials.'); console.error(err); process.exit(1); diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts index 317d9cd4563ff..d9c852031e384 100644 --- a/extensions/git/src/askpass.ts +++ b/extensions/git/src/askpass.ts @@ -107,8 +107,14 @@ export class Askpass implements IIPCHandler, ITerminalEnvironmentProvider { // passphrase if (/passphrase/i.test(request)) { // Commit signing - Enter passphrase: + // Commit signing - Enter passphrase for '/c/Users//.ssh/id_ed25519': // Git operation - Enter passphrase for key '/c/Users//.ssh/id_ed25519': - const file = extractFilePathFromArgs(argv, 6); + let file: string | undefined = undefined; + if (argv[5] && !/key/i.test(argv[5])) { + file = extractFilePathFromArgs(argv, 5); + } else if (argv[6]) { + file = extractFilePathFromArgs(argv, 6); + } this.logger.trace(`[Askpass][handleSSHAskpass] request: ${request}, file: ${file}`); diff --git a/extensions/git/src/blame.ts b/extensions/git/src/blame.ts index eb65a8ea0ab94..c6f121a01b3a4 100644 --- a/extensions/git/src/blame.ts +++ b/extensions/git/src/blame.ts @@ -15,8 +15,7 @@ import { getWorkingTreeAndIndexDiffInformation, getWorkingTreeDiffInformation } import { provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider'; import { AvatarQuery, AvatarQueryCommit } from './api/git'; import { LRUCache } from './cache'; - -const AVATAR_SIZE = 20; +import { AVATAR_SIZE, getCommitHover, getHoverCommitHashCommands, processHoverRemoteCommands } from './hover'; function lineRangesContainLine(changes: readonly TextEditorChange[], lineNumber: number): boolean { return changes.some(c => c.modified.startLineNumber <= lineNumber && lineNumber < c.modified.endLineNumberExclusive); @@ -127,6 +126,10 @@ interface LineBlameInformation { class GitBlameInformationCache { private readonly _cache = new Map>(); + clear(): void { + this._cache.clear(); + } + delete(repository: Repository): boolean { return this._cache.delete(repository); } @@ -197,7 +200,9 @@ export class GitBlameController { } satisfies BlameInformationTemplateTokens; return template.replace(/\$\{(.+?)\}/g, (_, token) => { - return token in templateTokens ? templateTokens[token as keyof BlameInformationTemplateTokens] : `\${${token}}`; + return templateTokens.hasOwnProperty(token) + ? templateTokens[token as keyof BlameInformationTemplateTokens] + : `\${${token}}`; }); } @@ -242,89 +247,43 @@ export class GitBlameController { this._model, repository, commitInformation?.message ?? blameInformation.subject ?? ''); } - const markdownString = new MarkdownString(); - markdownString.isTrusted = true; - markdownString.supportThemeIcons = true; - - // Author, date const hash = commitInformation?.hash ?? blameInformation.hash; const authorName = commitInformation?.authorName ?? blameInformation.authorName; const authorEmail = commitInformation?.authorEmail ?? blameInformation.authorEmail; const authorDate = commitInformation?.authorDate ?? blameInformation.authorDate; - const avatar = commitAvatar ? `![${authorName}](${commitAvatar}|width=${AVATAR_SIZE},height=${AVATAR_SIZE})` : '$(account)'; - - - if (authorName) { - if (authorEmail) { - const emailTitle = l10n.t('Email'); - markdownString.appendMarkdown(`${avatar} [**${authorName}**](mailto:${authorEmail} "${emailTitle} ${authorName}")`); - } else { - markdownString.appendMarkdown(`${avatar} **${authorName}**`); - } - - if (authorDate) { - const dateString = new Date(authorDate).toLocaleString(undefined, { - year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' - }); - markdownString.appendMarkdown(`, $(history) ${fromNow(authorDate, true, true)} (${dateString})`); - } - - markdownString.appendMarkdown('\n\n'); - } - - // Subject | Message const message = commitMessageWithLinks ?? commitInformation?.message ?? blameInformation.subject ?? ''; - markdownString.appendMarkdown(`${emojify(message.replace(/\r\n|\r|\n/g, '\n\n'))}\n\n`); - markdownString.appendMarkdown(`---\n\n`); - - // Short stats - if (commitInformation?.shortStat) { - markdownString.appendMarkdown(`${commitInformation.shortStat.files === 1 ? - l10n.t('{0} file changed', commitInformation.shortStat.files) : - l10n.t('{0} files changed', commitInformation.shortStat.files)}`); - - if (commitInformation.shortStat.insertions) { - markdownString.appendMarkdown(`, ${commitInformation.shortStat.insertions === 1 ? - l10n.t('{0} insertion{1}', commitInformation.shortStat.insertions, '(+)') : - l10n.t('{0} insertions{1}', commitInformation.shortStat.insertions, '(+)')}`); - } - - if (commitInformation.shortStat.deletions) { - markdownString.appendMarkdown(`, ${commitInformation.shortStat.deletions === 1 ? - l10n.t('{0} deletion{1}', commitInformation.shortStat.deletions, '(-)') : - l10n.t('{0} deletions{1}', commitInformation.shortStat.deletions, '(-)')}`); - } - - markdownString.appendMarkdown(`\n\n---\n\n`); - } // Commands - markdownString.appendMarkdown(`[\`$(git-commit) ${getCommitShortHash(documentUri, hash)} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([documentUri, hash]))} "${l10n.t('Open Commit')}")`); - markdownString.appendMarkdown(' '); - markdownString.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`); - - // Remote hover commands - if (remoteHoverCommands.length > 0) { - markdownString.appendMarkdown('  |  '); + const commands: Command[][] = [ + getHoverCommitHashCommands(documentUri, hash), + processHoverRemoteCommands(remoteHoverCommands, hash) + ]; - const remoteCommandsMarkdown = remoteHoverCommands - .map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify([...command.arguments ?? [], hash]))} "${command.tooltip}")`); - markdownString.appendMarkdown(remoteCommandsMarkdown.join(' ')); - } + commands.push([{ + title: `$(gear)`, + tooltip: l10n.t('Open Settings'), + command: 'workbench.action.openSettings', + arguments: ['git.blame'] + }] satisfies Command[]); - markdownString.appendMarkdown('  |  '); - markdownString.appendMarkdown(`[$(gear)](command:workbench.action.openSettings?%5B%22git.blame%22%5D "${l10n.t('Open Settings')}")`); - - return markdownString; + return getCommitHover(commitAvatar, authorName, authorEmail, authorDate, message, commitInformation?.shortStat, commands); } private _onDidChangeConfiguration(e?: ConfigurationChangeEvent): void { if (e && + !e.affectsConfiguration('git.blame.ignoreWhitespace') && !e.affectsConfiguration('git.blame.editorDecoration.enabled') && !e.affectsConfiguration('git.blame.statusBarItem.enabled')) { return; } + // Clear cache when ignoreWhitespace setting changes + if (e && e.affectsConfiguration('git.blame.ignoreWhitespace')) { + this._repositoryBlameCache.clear(); + this._updateTextEditorBlameInformation(window.activeTextEditor); + return; + } + const config = workspace.getConfiguration('git'); const editorDecorationEnabled = config.get('blame.editorDecoration.enabled') === true; const statusBarItemEnabled = config.get('blame.statusBarItem.enabled') === true; @@ -631,7 +590,8 @@ class GitBlameEditorDecoration implements HoverProvider { private _onDidChangeConfiguration(e?: ConfigurationChangeEvent): void { if (e && !e.affectsConfiguration('git.commitShortHashLength') && - !e.affectsConfiguration('git.blame.editorDecoration.template')) { + !e.affectsConfiguration('git.blame.editorDecoration.template') && + !e.affectsConfiguration('git.blame.editorDecoration.disableHover')) { return; } @@ -695,7 +655,9 @@ class GitBlameEditorDecoration implements HoverProvider { private _registerHoverProvider(): void { this._hoverDisposable?.dispose(); - if (window.activeTextEditor && isResourceSchemeSupported(window.activeTextEditor.document.uri)) { + const config = workspace.getConfiguration('git'); + const disableHover = config.get('blame.editorDecoration.disableHover', false); + if (!disableHover && window.activeTextEditor && isResourceSchemeSupported(window.activeTextEditor.document.uri)) { this._hoverDisposable = languages.registerHoverProvider({ pattern: window.activeTextEditor.document.uri.fsPath }, this); @@ -765,10 +727,13 @@ class GitBlameStatusBarItem { blameInformation[0].blameInformation as BlameInformation, cancellationToken); }; + const uri = window.activeTextEditor.document.uri; + const hash = blameInformation[0].blameInformation.hash; + this._statusBarItem.command = { title: l10n.t('Open Commit'), command: 'git.viewCommit', - arguments: [window.activeTextEditor.document.uri, blameInformation[0].blameInformation.hash] + arguments: [uri, hash, uri] } satisfies Command; } diff --git a/extensions/git/src/cache.ts b/extensions/git/src/cache.ts index df0c0df5561ae..ad2db75edc893 100644 --- a/extensions/git/src/cache.ts +++ b/extensions/git/src/cache.ts @@ -132,7 +132,7 @@ class LinkedMap implements Map { return item.value; } - forEach(callbackfn: (value: V, key: K, map: LinkedMap) => void, thisArg?: any): void { + forEach(callbackfn: (value: V, key: K, map: LinkedMap) => void, thisArg?: unknown): void { const state = this._state; let current = this._head; while (current) { diff --git a/extensions/git/src/cloneManager.ts b/extensions/git/src/cloneManager.ts new file mode 100644 index 0000000000000..49d57d8763c63 --- /dev/null +++ b/extensions/git/src/cloneManager.ts @@ -0,0 +1,243 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import { pickRemoteSource } from './remoteSource'; +import { l10n, workspace, window, Uri, ProgressLocation, commands } from 'vscode'; +import { RepositoryCache, RepositoryCacheInfo } from './repositoryCache'; +import TelemetryReporter from '@vscode/extension-telemetry'; +import { Model } from './model'; + +type ApiPostCloneAction = 'none'; +enum PostCloneAction { Open, OpenNewWindow, AddToWorkspace, None } + +export interface CloneOptions { + parentPath?: string; + ref?: string; + recursive?: boolean; + postCloneAction?: ApiPostCloneAction; +} + +export class CloneManager { + constructor(private readonly model: Model, + private readonly telemetryReporter: TelemetryReporter, + private readonly repositoryCache: RepositoryCache) { } + + async clone(url?: string, options: CloneOptions = {}) { + if (!url || typeof url !== 'string') { + url = await pickRemoteSource({ + providerLabel: provider => l10n.t('Clone from {0}', provider.name), + urlLabel: l10n.t('Clone from URL') + }); + } + + if (!url) { + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' }); + return; + } + + url = url.trim().replace(/^git\s+clone\s+/, ''); + + const cachedRepository = this.repositoryCache.get(url); + if (cachedRepository && (cachedRepository.length > 0)) { + return this.tryOpenExistingRepository(cachedRepository, url, options.postCloneAction, options.parentPath, options.ref); + } + return this.cloneRepository(url, options.parentPath, options); + } + + private async cloneRepository(url: string, parentPath?: string, options: { recursive?: boolean; ref?: string; postCloneAction?: ApiPostCloneAction } = {}): Promise { + if (!parentPath) { + const config = workspace.getConfiguration('git'); + let defaultCloneDirectory = config.get('defaultCloneDirectory') || os.homedir(); + defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir()); + + const uris = await window.showOpenDialog({ + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + defaultUri: Uri.file(defaultCloneDirectory), + title: l10n.t('Choose a folder to clone {0} into', url), + openLabel: l10n.t('Select as Repository Destination') + }); + + if (!uris || uris.length === 0) { + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); + return; + } + + const uri = uris[0]; + parentPath = uri.fsPath; + } + + try { + const opts = { + location: ProgressLocation.Notification, + title: l10n.t('Cloning git repository "{0}"...', url), + cancellable: true + }; + + const repositoryPath = await window.withProgress( + opts, + (progress, token) => this.model.git.clone(url!, { parentPath: parentPath!, progress, recursive: options.recursive, ref: options.ref }, token) + ); + + await this.doPostCloneAction(repositoryPath, options.postCloneAction); + + return repositoryPath; + } catch (err) { + if (/already exists and is not an empty directory/.test(err && err.stderr || '')) { + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' }); + } else if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) { + return; + } else { + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' }); + } + + throw err; + } + } + + private async doPostCloneAction(target: string, postCloneAction?: ApiPostCloneAction): Promise { + const config = workspace.getConfiguration('git'); + const openAfterClone = config.get<'always' | 'alwaysNewWindow' | 'whenNoFolderOpen' | 'prompt'>('openAfterClone'); + + let action: PostCloneAction | undefined = undefined; + + if (postCloneAction && postCloneAction === 'none') { + action = PostCloneAction.None; + } else { + if (openAfterClone === 'always') { + action = PostCloneAction.Open; + } else if (openAfterClone === 'alwaysNewWindow') { + action = PostCloneAction.OpenNewWindow; + } else if (openAfterClone === 'whenNoFolderOpen' && !workspace.workspaceFolders) { + action = PostCloneAction.Open; + } + } + + if (action === undefined) { + let message = l10n.t('Would you like to open the repository?'); + const open = l10n.t('Open'); + const openNewWindow = l10n.t('Open in New Window'); + const choices = [open, openNewWindow]; + + const addToWorkspace = l10n.t('Add to Workspace'); + if (workspace.workspaceFolders) { + message = l10n.t('Would you like to open the repository, or add it to the current workspace?'); + choices.push(addToWorkspace); + } + + const result = await window.showInformationMessage(message, { modal: true }, ...choices); + + action = result === open ? PostCloneAction.Open + : result === openNewWindow ? PostCloneAction.OpenNewWindow + : result === addToWorkspace ? PostCloneAction.AddToWorkspace : undefined; + } + + /* __GDPR__ + "clone" : { + "owner": "lszomoru", + "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, + "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } + } + */ + this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); + + const uri = Uri.file(target); + + if (action === PostCloneAction.Open) { + commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true }); + } else if (action === PostCloneAction.AddToWorkspace) { + workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); + } else if (action === PostCloneAction.OpenNewWindow) { + commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); + } + } + + private async chooseExistingRepository(url: string, existingCachedRepositories: RepositoryCacheInfo[], ref: string | undefined, parentPath?: string, postCloneAction?: ApiPostCloneAction): Promise { + try { + const items: { label: string; description?: string; item?: RepositoryCacheInfo }[] = existingCachedRepositories.map(knownFolder => { + const isWorkspace = knownFolder.workspacePath.endsWith('.code-workspace'); + const label = isWorkspace ? l10n.t('Workspace: {0}', path.basename(knownFolder.workspacePath, '.code-workspace')) : path.basename(knownFolder.workspacePath); + return { label, description: knownFolder.workspacePath, item: knownFolder }; + }); + const cloneAgain = { label: l10n.t('Clone again') }; + items.push(cloneAgain); + const placeHolder = l10n.t('Open Existing Repository Clone'); + const pick = await window.showQuickPick(items, { placeHolder, canPickMany: false }); + if (pick === cloneAgain) { + return (await this.cloneRepository(url, parentPath, { ref, postCloneAction })) ?? undefined; + } + if (!pick?.item) { + return undefined; + } + return pick.item.workspacePath; + } catch { + return undefined; + } + } + + private async tryOpenExistingRepository(cachedRepository: RepositoryCacheInfo[], url: string, postCloneAction?: ApiPostCloneAction, parentPath?: string, ref?: string): Promise { + // Gather existing folders/workspace files (ignore ones that no longer exist) + const existingCachedRepositories: RepositoryCacheInfo[] = (await Promise.all(cachedRepository.map(async folder => { + const stat = await fs.promises.stat(folder.workspacePath).catch(() => undefined); + if (stat) { + return folder; + } + return undefined; + } + ))).filter((folder): folder is RepositoryCacheInfo => folder !== undefined); + + if (!existingCachedRepositories.length) { + // fallback to clone + return (await this.cloneRepository(url, parentPath, { ref, postCloneAction }) ?? undefined); + } + + // First, find the cached repo that exists in the current workspace + const matchingInCurrentWorkspace = existingCachedRepositories?.find(cachedRepo => { + return workspace.workspaceFolders?.some(workspaceFolder => workspaceFolder.uri.fsPath === cachedRepo.workspacePath); + }); + + if (matchingInCurrentWorkspace) { + return matchingInCurrentWorkspace.workspacePath; + } + + let repoForWorkspace: string | undefined = (existingCachedRepositories.length === 1 ? existingCachedRepositories[0].workspacePath : undefined); + if (!repoForWorkspace) { + repoForWorkspace = await this.chooseExistingRepository(url, existingCachedRepositories, ref, parentPath, postCloneAction); + } + if (repoForWorkspace) { + await this.doPostCloneAction(repoForWorkspace, postCloneAction); + return repoForWorkspace; + } + return; + } +} diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 55b0a8ec016bb..671d18341bd6d 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -5,20 +5,21 @@ import * as os from 'os'; import * as path from 'path'; -import { Command, commands, Disposable, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages } from 'vscode'; +import { Command, commands, Disposable, MessageOptions, Position, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages, SourceControlArtifact } from 'vscode'; import TelemetryReporter from '@vscode/extension-telemetry'; import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator'; import { ForcePushMode, GitErrorCodes, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote, Branch, Ref } from './api/git'; -import { Git, Stash, Worktree } from './git'; +import { Git, GitError, Stash, Worktree } from './git'; import { Model } from './model'; import { GitResourceGroup, Repository, Resource, ResourceGroupType } from './repository'; import { DiffEditorSelectionHunkToolbarContext, LineChange, applyLineChanges, getIndexDiffInformation, getModifiedRange, getWorkingTreeDiffInformation, intersectDiffWithRange, invertLineChange, toLineChanges, toLineRanges, compareLineChanges } from './staging'; import { fromGitUri, toGitUri, isGitUri, toMergeUris, toMultiFileDiffEditorUris } from './uri'; -import { DiagnosticSeverityConfig, dispose, fromNow, grep, isDefined, isDescendant, isLinuxSnap, isRemote, isWindows, pathEquals, relativePath, toDiagnosticSeverity, truncate } from './util'; +import { coalesce, DiagnosticSeverityConfig, dispose, fromNow, getHistoryItemDisplayName, getStashDescription, grep, isDefined, isDescendant, isLinuxSnap, isRemote, isWindows, pathEquals, relativePath, subject, toDiagnosticSeverity, truncate } from './util'; import { GitTimelineItem } from './timelineProvider'; import { ApiRepository } from './api/api1'; import { getRemoteSourceActions, pickRemoteSource } from './remoteSource'; import { RemoteSourceAction } from './typings/git-base'; +import { CloneManager } from './cloneManager'; abstract class CheckoutCommandItem implements QuickPickItem { abstract get label(): string; @@ -57,19 +58,6 @@ class RefItemSeparator implements QuickPickItem { constructor(private readonly refType: RefType) { } } -class WorktreeItem implements QuickPickItem { - - get label(): string { - return `$(list-tree) ${this.worktree.name}`; - } - - get description(): string { - return this.worktree.path; - } - - constructor(readonly worktree: Worktree) { } -} - class RefItem implements QuickPickItem { get label(): string { @@ -115,7 +103,7 @@ class RefItem implements QuickPickItem { case RefType.Head: return `refs/heads/${this.ref.name}`; case RefType.RemoteHead: - return `refs/remotes/${this.ref.remote}/${this.ref.name}`; + return `refs/remotes/${this.ref.name}`; case RefType.Tag: return `refs/tags/${this.ref.name}`; } @@ -123,6 +111,7 @@ class RefItem implements QuickPickItem { get refName(): string | undefined { return this.ref.name; } get refRemote(): string | undefined { return this.ref.remote; } get shortCommit(): string { return (this.ref.commit || '').substring(0, this.shortCommitLength); } + get commitMessage(): string | undefined { return this.ref.commitDetails?.message; } private _buttons?: QuickInputButton[]; get buttons(): QuickInputButton[] | undefined { return this._buttons; } @@ -238,25 +227,46 @@ class RemoteTagDeleteItem extends RefItem { } } +class WorktreeItem implements QuickPickItem { + + get label(): string { + return `$(list-tree) ${this.worktree.name}`; + } + + get description(): string | undefined { + return this.worktree.path; + } + + constructor(readonly worktree: Worktree) { } +} + class WorktreeDeleteItem extends WorktreeItem { + override get description(): string | undefined { + if (!this.worktree.commitDetails) { + return undefined; + } + + return coalesce([ + this.worktree.detached ? l10n.t('detached') : this.worktree.ref.substring(11), + this.worktree.commitDetails.hash.substring(0, this.shortCommitLength), + this.worktree.commitDetails.message.split('\n')[0] + ]).join(' \u2022 '); + } + + get detail(): string { + return this.worktree.path; + } + + constructor(worktree: Worktree, private readonly shortCommitLength: number) { + super(worktree); + } + async run(mainRepository: Repository): Promise { if (!this.worktree.path) { return; } - try { - await mainRepository.deleteWorktree(this.worktree.path); - } catch (err) { - if (err.gitErrorCode === GitErrorCodes.WorktreeContainsChanges) { - const forceDelete = l10n.t('Force Delete'); - const message = l10n.t('The worktree contains modified or untracked files. Do you want to force delete?'); - const choice = await window.showWarningMessage(message, { modal: true }, forceDelete); - - if (choice === forceDelete) { - await mainRepository.deleteWorktree(this.worktree.path, { force: true }); - } - } - } + await mainRepository.deleteWorktree(this.worktree.path); } } @@ -343,7 +353,7 @@ class RepositoryItem implements QuickPickItem { class StashItem implements QuickPickItem { get label(): string { return `#${this.stash.index}: ${this.stash.description}`; } - get description(): string | undefined { return this.stash.branchName; } + get description(): string | undefined { return getStashDescription(this.stash); } constructor(readonly stash: Stash) { } } @@ -363,12 +373,12 @@ interface ScmCommand { const Commands: ScmCommand[] = []; function command(commandId: string, options: ScmCommandOptions = {}): Function { - return (_target: any, key: string, descriptor: any) => { - if (!(typeof descriptor.value === 'function')) { + return (value: unknown, context: ClassMethodDecoratorContext) => { + if (typeof value !== 'function' || context.kind !== 'method') { throw new Error('not supported'); } - - Commands.push({ commandId, key, method: descriptor.value, options }); + const key = context.name.toString(); + Commands.push({ commandId, key, method: value, options }); }; } @@ -681,10 +691,11 @@ class CommandErrorOutputTextDocumentContentProvider implements TextDocumentConte } } -async function evaluateDiagnosticsCommitHook(repository: Repository, options: CommitOptions): Promise { +async function evaluateDiagnosticsCommitHook(repository: Repository, options: CommitOptions, logger: LogOutputChannel): Promise { const config = workspace.getConfiguration('git', Uri.file(repository.root)); const enabled = config.get('diagnosticsCommitHook.enabled', false) === true; const sourceSeverity = config.get>('diagnosticsCommitHook.sources', { '*': 'error' }); + logger.trace(`[CommandCenter][evaluateDiagnosticsCommitHook] Diagnostics Commit Hook: enabled=${enabled}, sources=${JSON.stringify(sourceSeverity)}`); if (!enabled) { return true; @@ -710,23 +721,27 @@ async function evaluateDiagnosticsCommitHook(repository: Repository, options: Co for (const resource of resources) { const unresolvedDiagnostics = languages.getDiagnostics(resource) .filter(d => { + logger.trace(`[CommandCenter][evaluateDiagnosticsCommitHook] Evaluating diagnostic for ${resource.fsPath}: source='${d.source}', severity='${d.severity}'`); + // No source or ignored source if (!d.source || (Object.keys(sourceSeverity).includes(d.source) && sourceSeverity[d.source] === 'none')) { + logger.trace(`[CommandCenter][evaluateDiagnosticsCommitHook] Ignoring diagnostic for ${resource.fsPath}: source='${d.source}', severity='${d.severity}'`); return false; } // Source severity - if (Object.keys(sourceSeverity).includes(d.source) && - d.severity <= toDiagnosticSeverity(sourceSeverity[d.source])) { + if (Object.keys(sourceSeverity).includes(d.source) && d.severity <= toDiagnosticSeverity(sourceSeverity[d.source])) { + logger.trace(`[CommandCenter][evaluateDiagnosticsCommitHook] Found unresolved diagnostic for ${resource.fsPath}: source='${d.source}', severity='${d.severity}'`); return true; } // Wildcard severity - if (Object.keys(sourceSeverity).includes('*') && - d.severity <= toDiagnosticSeverity(sourceSeverity['*'])) { + if (Object.keys(sourceSeverity).includes('*') && d.severity <= toDiagnosticSeverity(sourceSeverity['*'])) { + logger.trace(`[CommandCenter][evaluateDiagnosticsCommitHook] Found unresolved diagnostic for ${resource.fsPath}: source='${d.source}', severity='${d.severity}'`); return true; } + logger.trace(`[CommandCenter][evaluateDiagnosticsCommitHook] Ignoring diagnostic for ${resource.fsPath}: source='${d.source}', severity='${d.severity}'`); return false; }); @@ -767,14 +782,13 @@ export class CommandCenter { private disposables: Disposable[]; private commandErrors = new CommandErrorOutputTextDocumentContentProvider(); - private static readonly WORKTREE_ROOT_KEY = 'worktreeRoot'; - constructor( private git: Git, private model: Model, private globalState: Memento, private logger: LogOutputChannel, - private telemetryReporter: TelemetryReporter + private telemetryReporter: TelemetryReporter, + private cloneManager: CloneManager ) { this.disposables = Commands.map(({ commandId, key, method, options }) => { const command = this.createCommand(commandId, key, method, options); @@ -862,23 +876,32 @@ export class CommandCenter { } try { - const [head, rebaseOrMergeHead, diffBetween] = await Promise.all([ + const [head, rebaseOrMergeHead, oursDiff, theirsDiff] = await Promise.all([ repo.getCommit('HEAD'), isRebasing ? repo.getCommit('REBASE_HEAD') : repo.getCommit('MERGE_HEAD'), - await repo.diffBetween(isRebasing ? 'REBASE_HEAD' : 'MERGE_HEAD', 'HEAD') + await repo.diffBetween(isRebasing ? 'REBASE_HEAD' : 'MERGE_HEAD', 'HEAD'), + await repo.diffBetween('HEAD', isRebasing ? 'REBASE_HEAD' : 'MERGE_HEAD') ]); - const diffFile = diffBetween?.find(diff => diff.uri.fsPath === uri.fsPath); + + const oursDiffFile = oursDiff?.find(diff => diff.uri.fsPath === uri.fsPath); + const theirsDiffFile = theirsDiff?.find(diff => diff.uri.fsPath === uri.fsPath); // ours (current branch and commit) current.detail = head.refNames.map(s => s.replace(/^HEAD ->/, '')).join(', '); current.description = '$(git-commit) ' + head.hash.substring(0, 7); - current.uri = toGitUri(uri, head.hash); + if (theirsDiffFile) { + // use the original uri in case the file was renamed by theirs + current.uri = toGitUri(theirsDiffFile.originalUri, head.hash); + } else { + current.uri = toGitUri(uri, head.hash); + } // theirs incoming.detail = rebaseOrMergeHead.refNames.join(', '); incoming.description = '$(git-commit) ' + rebaseOrMergeHead.hash.substring(0, 7); - if (diffFile) { - incoming.uri = toGitUri(diffFile.originalUri, rebaseOrMergeHead.hash); + if (oursDiffFile) { + // use the original uri in case the file was renamed by ours + incoming.uri = toGitUri(oursDiffFile.originalUri, rebaseOrMergeHead.hash); } else { incoming.uri = toGitUri(uri, rebaseOrMergeHead.hash); } @@ -935,144 +958,6 @@ export class CommandCenter { } } - async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean; ref?: string } = {}): Promise { - if (!url || typeof url !== 'string') { - url = await pickRemoteSource({ - providerLabel: provider => l10n.t('Clone from {0}', provider.name), - urlLabel: l10n.t('Clone from URL') - }); - } - - if (!url) { - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' }); - return; - } - - url = url.trim().replace(/^git\s+clone\s+/, ''); - - if (!parentPath) { - const config = workspace.getConfiguration('git'); - let defaultCloneDirectory = config.get('defaultCloneDirectory') || os.homedir(); - defaultCloneDirectory = defaultCloneDirectory.replace(/^~/, os.homedir()); - - const uris = await window.showOpenDialog({ - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - defaultUri: Uri.file(defaultCloneDirectory), - title: l10n.t('Choose a folder to clone {0} into', url), - openLabel: l10n.t('Select as Repository Destination') - }); - - if (!uris || uris.length === 0) { - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' }); - return; - } - - const uri = uris[0]; - parentPath = uri.fsPath; - } - - try { - const opts = { - location: ProgressLocation.Notification, - title: l10n.t('Cloning git repository "{0}"...', url), - cancellable: true - }; - - const repositoryPath = await window.withProgress( - opts, - (progress, token) => this.git.clone(url!, { parentPath: parentPath!, progress, recursive: options.recursive, ref: options.ref }, token) - ); - - const config = workspace.getConfiguration('git'); - const openAfterClone = config.get<'always' | 'alwaysNewWindow' | 'whenNoFolderOpen' | 'prompt'>('openAfterClone'); - - enum PostCloneAction { Open, OpenNewWindow, AddToWorkspace } - let action: PostCloneAction | undefined = undefined; - - if (openAfterClone === 'always') { - action = PostCloneAction.Open; - } else if (openAfterClone === 'alwaysNewWindow') { - action = PostCloneAction.OpenNewWindow; - } else if (openAfterClone === 'whenNoFolderOpen' && !workspace.workspaceFolders) { - action = PostCloneAction.Open; - } - - if (action === undefined) { - let message = l10n.t('Would you like to open the cloned repository?'); - const open = l10n.t('Open'); - const openNewWindow = l10n.t('Open in New Window'); - const choices = [open, openNewWindow]; - - const addToWorkspace = l10n.t('Add to Workspace'); - if (workspace.workspaceFolders) { - message = l10n.t('Would you like to open the cloned repository, or add it to the current workspace?'); - choices.push(addToWorkspace); - } - - const result = await window.showInformationMessage(message, { modal: true }, ...choices); - - action = result === open ? PostCloneAction.Open - : result === openNewWindow ? PostCloneAction.OpenNewWindow - : result === addToWorkspace ? PostCloneAction.AddToWorkspace : undefined; - } - - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }, - "openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 }); - - const uri = Uri.file(repositoryPath); - - if (action === PostCloneAction.Open) { - commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true }); - } else if (action === PostCloneAction.AddToWorkspace) { - workspace.updateWorkspaceFolders(workspace.workspaceFolders!.length, 0, { uri }); - } else if (action === PostCloneAction.OpenNewWindow) { - commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); - } - } catch (err) { - if (/already exists and is not an empty directory/.test(err && err.stderr || '')) { - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' }); - } else if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) { - return; - } else { - /* __GDPR__ - "clone" : { - "owner": "lszomoru", - "outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" } - } - */ - this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' }); - } - - throw err; - } - } - private getRepositoriesWithRemote(repositories: Repository[]) { return repositories.reduce<(QuickPickItem & { repository: Repository })[]>((items, repository) => { const remote = repository.remotes.find((r) => r.name === repository.HEAD?.upstream?.remote); @@ -1145,12 +1030,12 @@ export class CommandCenter { @command('git.clone') async clone(url?: string, parentPath?: string, options?: { ref?: string }): Promise { - await this.cloneRepository(url, parentPath, options); + await this.cloneManager.clone(url, { parentPath, ...options }); } @command('git.cloneRecursive') async cloneRecursive(url?: string, parentPath?: string): Promise { - await this.cloneRepository(url, parentPath, { recursive: true }); + await this.cloneManager.clone(url, { parentPath, recursive: true }); } @command('git.init') @@ -1484,6 +1369,15 @@ export class CommandCenter { } } + @command('git.compareWithWorkspace') + async compareWithWorkspace(resource?: Resource): Promise { + if (!resource) { + return; + } + + await resource.compareWithWorkspace(); + } + @command('git.rename', { repository: true }) async rename(repository: Repository, fromUri: Uri | undefined): Promise { fromUri = fromUri ?? window.activeTextEditor?.document.uri; @@ -1816,7 +1710,7 @@ export class CommandCenter { const resources = [ ...repository.workingTreeGroup.resourceStates, ...repository.untrackedGroup.resourceStates] - .filter(r => r.multiFileDiffEditorModifiedUri?.toString() === uri.toString()) + .filter(r => r.multiFileDiffEditorModifiedUri?.toString() === uri.toString() || r.multiDiffEditorOriginalUri?.toString() === uri.toString()) .map(r => r.resourceUri); if (resources.length === 0) { @@ -2127,7 +2021,7 @@ export class CommandCenter { } const resources = repository.indexGroup.resourceStates - .filter(r => r.multiFileDiffEditorModifiedUri?.toString() === uri.toString()) + .filter(r => r.multiFileDiffEditorModifiedUri?.toString() === uri.toString() || r.multiDiffEditorOriginalUri?.toString() === uri.toString()) .map(r => r.resourceUri); if (resources.length === 0) { @@ -2372,10 +2266,8 @@ export class CommandCenter { let promptToSaveFilesBeforeCommit = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeCommit'); // migration - if (promptToSaveFilesBeforeCommit as any === true) { - promptToSaveFilesBeforeCommit = 'always'; - } else if (promptToSaveFilesBeforeCommit as any === false) { - promptToSaveFilesBeforeCommit = 'never'; + if (typeof promptToSaveFilesBeforeCommit === 'boolean') { + promptToSaveFilesBeforeCommit = promptToSaveFilesBeforeCommit ? 'always' : 'never'; } let enableSmartCommit = config.get('enableSmartCommit') === true; @@ -2532,7 +2424,7 @@ export class CommandCenter { } // Diagnostics commit hook - const diagnosticsResult = await evaluateDiagnosticsCommitHook(repository, opts); + const diagnosticsResult = await evaluateDiagnosticsCommitHook(repository, opts, this.logger); if (!diagnosticsResult) { return; } @@ -2545,7 +2437,7 @@ export class CommandCenter { let pick: string | undefined = commitToNewBranch; if (branchProtectionPrompt === 'alwaysPrompt') { - const message = l10n.t('You are trying to commit to a protected branch and you might not have permission to push your commits to the remote.\n\nHow would you like to proceed?'); + const message = l10n.t('You are trying to commit to a protected branch. How would you like to proceed?'); const commit = l10n.t('Commit Anyway'); pick = await window.showWarningMessage(message, { modal: true }, commitToNewBranch, commit); @@ -2938,7 +2830,25 @@ export class CommandCenter { } if (err.gitErrorCode === GitErrorCodes.WorktreeBranchAlreadyUsed) { - this.handleWorktreeBranchAlreadyUsed(err); + // Not checking out in a worktree (use standard error handling) + if (!repository.dotGit.commonPath) { + await this.handleWorktreeBranchAlreadyUsed(err); + return false; + } + + // Check out in a worktree (check if worktree's main repository is open in workspace and if branch is already checked out in main repository) + const commonPath = path.dirname(repository.dotGit.commonPath); + if (workspace.workspaceFolders && workspace.workspaceFolders.some(folder => pathEquals(folder.uri.fsPath, commonPath))) { + const mainRepository = this.model.getRepository(commonPath); + if (mainRepository && item.refName && item.refName.replace(`${item.refRemote}/`, '') === mainRepository.HEAD?.name) { + const message = l10n.t('Branch "{0}" is already checked out in the current window.', item.refName); + await window.showErrorMessage(message, { modal: true }); + return false; + } + } + + // Check out in a worktree, (branch is already checked out in existing worktree) + await this.handleWorktreeBranchAlreadyUsed(err); return false; } @@ -3206,6 +3116,108 @@ export class CommandCenter { await this._deleteBranch(repository, remoteName, refName, { remote: true }); } + @command('git.graph.compareWithRemote', { repository: true }) + async compareWithRemote(repository: Repository, historyItem?: SourceControlHistoryItem): Promise { + if (!historyItem || !repository.historyProvider.currentHistoryItemRemoteRef) { + return; + } + + await this._openChangesBetweenRefs( + repository, + { + id: repository.historyProvider.currentHistoryItemRemoteRef.revision, + displayId: repository.historyProvider.currentHistoryItemRemoteRef.name + }, + { + id: historyItem.id, + displayId: getHistoryItemDisplayName(historyItem) + }); + } + + @command('git.graph.compareWithMergeBase', { repository: true }) + async compareWithMergeBase(repository: Repository, historyItem?: SourceControlHistoryItem): Promise { + if (!historyItem || !repository.historyProvider.currentHistoryItemBaseRef) { + return; + } + + await this._openChangesBetweenRefs( + repository, + { + id: repository.historyProvider.currentHistoryItemBaseRef.revision, + displayId: repository.historyProvider.currentHistoryItemBaseRef.name + }, + { + id: historyItem.id, + displayId: getHistoryItemDisplayName(historyItem) + }); + } + + @command('git.graph.compareRef', { repository: true }) + async compareRef(repository: Repository, historyItem?: SourceControlHistoryItem): Promise { + if (!repository || !historyItem) { + return; + } + + const config = workspace.getConfiguration('git'); + const showRefDetails = config.get('showReferenceDetails') === true; + + const getRefPicks = async () => { + const refs = await repository.getRefs({ includeCommitDetails: showRefDetails }); + const processors = [ + new RefProcessor(RefType.Head, BranchItem), + new RefProcessor(RefType.RemoteHead, BranchItem), + new RefProcessor(RefType.Tag, BranchItem) + ]; + + const itemsProcessor = new RefItemsProcessor(repository, processors); + return itemsProcessor.processRefs(refs); + }; + + const placeHolder = l10n.t('Select a reference to compare with'); + const sourceRef = await this.pickRef(getRefPicks(), placeHolder); + + if (!(sourceRef instanceof BranchItem) || !sourceRef.ref.commit) { + return; + } + + await this._openChangesBetweenRefs( + repository, + { + id: sourceRef.ref.commit, + displayId: sourceRef.ref.name + }, + { + id: historyItem.id, + displayId: getHistoryItemDisplayName(historyItem) + }); + } + + private async _openChangesBetweenRefs(repository: Repository, ref1: { id: string | undefined; displayId: string | undefined }, ref2: { id: string | undefined; displayId: string | undefined }): Promise { + if (!repository || !ref1.id || !ref2.id) { + return; + } + + try { + const changes = await repository.diffBetweenWithStats(ref1.id, ref2.id); + + if (changes.length === 0) { + window.showInformationMessage(l10n.t('There are no changes between "{0}" and "{1}".', ref1.displayId ?? ref1.id, ref2.displayId ?? ref2.id)); + return; + } + + const multiDiffSourceUri = Uri.from({ scheme: 'git-ref-compare', path: `${repository.root}/${ref1.id}..${ref2.id}` }); + const resources = changes.map(change => toMultiFileDiffEditorUris(change, ref1.id!, ref2.id!)); + + await commands.executeCommand('_workbench.openMultiDiffEditor', { + multiDiffSourceUri, + title: `${ref1.displayId ?? ref1.id} \u2194 ${ref2.displayId ?? ref2.id}`, + resources + }); + } catch (err) { + window.showErrorMessage(l10n.t('Failed to open changes between "{0}" and "{1}": {2}', ref1.displayId ?? ref1.id, ref2.displayId ?? ref2.id, err.message)); + } + } + @command('git.deleteRemoteBranch', { repository: true }) async deleteRemoteBranch(repository: Repository): Promise { await this._deleteBranch(repository, undefined, undefined, { remote: true }); @@ -3366,24 +3378,7 @@ export class CommandCenter { @command('git.createTag', { repository: true }) async createTag(repository: Repository, historyItem?: SourceControlHistoryItem): Promise { - const inputTagName = await window.showInputBox({ - placeHolder: l10n.t('Tag name'), - prompt: l10n.t('Please provide a tag name'), - ignoreFocusOut: true - }); - - if (!inputTagName) { - return; - } - - const inputMessage = await window.showInputBox({ - placeHolder: l10n.t('Message'), - prompt: l10n.t('Please provide a message to annotate the tag'), - ignoreFocusOut: true - }); - - const name = inputTagName.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-'); - await repository.tag({ name, message: inputMessage, ref: historyItem?.id }); + await this._createTag(repository, historyItem?.id); } @command('git.deleteTag', { repository: true }) @@ -3407,32 +3402,57 @@ export class CommandCenter { } } - @command('git.createWorktree') - async createWorktree(repository: any): Promise { - repository = this.model.getRepository(repository); + @command('git.migrateWorktreeChanges', { repository: true, repositoryFilter: ['repository', 'submodule'] }) + async migrateWorktreeChanges(repository: Repository): Promise { + let worktreeRepository: Repository | undefined; - if (!repository) { - // Single repository/submodule/worktree - if (this.model.repositories.length === 1) { - repository = this.model.repositories[0]; - } - } + const worktrees = await repository.getWorktrees(); + if (worktrees.length === 1) { + worktreeRepository = this.model.getRepository(worktrees[0].path); + } else { + const worktreePicks = async (): Promise => { + return worktrees.length === 0 + ? [{ label: l10n.t('$(info) This repository has no worktrees.') }] + : worktrees.map(worktree => new WorktreeItem(worktree)); + }; - if (!repository) { - // Single repository/submodule - const repositories = this.model.repositories - .filter(r => r.kind === 'repository' || r.kind === 'submodule'); + const placeHolder = l10n.t('Select a worktree to migrate changes from'); + const choice = await this.pickRef(worktreePicks(), placeHolder); - if (repositories.length === 1) { - repository = repositories[0]; + if (!choice || !(choice instanceof WorktreeItem)) { + return; } + + worktreeRepository = this.model.getRepository(choice.worktree.path); } - if (!repository) { - // Multiple repositories/submodules - repository = await this.model.pickRepository(['repository', 'submodule']); + if (!worktreeRepository || worktreeRepository.kind !== 'worktree') { + return; } + await repository.migrateChanges(worktreeRepository.root, { + confirmation: true, deleteFromSource: true, untracked: true + }); + } + + @command('git.openWorktreeMergeEditor') + async openWorktreeMergeEditor(uri: Uri): Promise { + type InputData = { uri: Uri; title: string }; + const mergeUris = toMergeUris(uri); + + const current: InputData = { uri: mergeUris.ours, title: l10n.t('Workspace') }; + const incoming: InputData = { uri: mergeUris.theirs, title: l10n.t('Worktree') }; + + await commands.executeCommand('_open.mergeEditor', { + base: mergeUris.base, + input1: current, + input2: incoming, + output: uri + }); + } + + @command('git.createWorktree', { repository: true, repositoryFilter: ['repository', 'submodule'] }) + async createWorktree(repository?: Repository): Promise { if (!repository) { return; } @@ -3440,9 +3460,42 @@ export class CommandCenter { await this._createWorktree(repository); } - private async _createWorktree(repository: Repository): Promise { + async _createWorktree(repository: Repository): Promise { const config = workspace.getConfiguration('git'); const branchPrefix = config.get('branchPrefix')!; + + // Get commitish and branch for the new worktree + const worktreeDetails = await this.getWorktreeCommitishAndBranch(repository); + if (!worktreeDetails) { + return; + } + + const { commitish, branch } = worktreeDetails; + const worktreeName = ((branch ?? commitish).startsWith(branchPrefix) + ? (branch ?? commitish).substring(branchPrefix.length).replace(/\//g, '-') + : (branch ?? commitish).replace(/\//g, '-')); + + // Get path for the new worktree + const worktreePath = await this.getWorktreePath(repository, worktreeName); + if (!worktreePath) { + return; + } + + try { + await repository.createWorktree({ path: worktreePath, branch, commitish: commitish }); + } catch (err) { + if (err instanceof GitError && err.gitErrorCode === GitErrorCodes.WorktreeAlreadyExists) { + await this.handleWorktreeAlreadyExists(err); + } else if (err instanceof GitError && err.gitErrorCode === GitErrorCodes.WorktreeBranchAlreadyUsed) { + await this.handleWorktreeBranchAlreadyUsed(err); + } else { + throw err; + } + } + } + + private async getWorktreeCommitishAndBranch(repository: Repository): Promise<{ commitish: string; branch: string | undefined } | undefined> { + const config = workspace.getConfiguration('git', Uri.file(repository.root)); const showRefDetails = config.get('showReferenceDetails') === true; const createBranch = new CreateBranchItem(); @@ -3457,27 +3510,25 @@ export class CommandCenter { return [createBranch, { label: '', kind: QuickPickItemKind.Separator }, ...branchItems]; }; - const placeHolder = l10n.t('Select a branch to create the new worktree from'); + const placeHolder = l10n.t('Select a branch or tag to create the new worktree from'); const choice = await this.pickRef(getBranchPicks(), placeHolder); if (!choice) { - return; + return undefined; } - let branch: string | undefined = undefined; - let commitish: string; - if (choice === createBranch) { - branch = await this.promptForBranchName(repository); - + // Create new branch + const branch = await this.promptForBranchName(repository); if (!branch) { - return; + return undefined; } - commitish = 'HEAD'; + return { commitish: 'HEAD', branch }; } else { + // Existing reference if (!(choice instanceof RefItem) || !choice.refName) { - return; + return undefined; } if (choice.refName === repository.HEAD?.name) { @@ -3486,15 +3537,14 @@ export class CommandCenter { const pick = await window.showWarningMessage(message, { modal: true }, createBranch); if (pick === createBranch) { - branch = await this.promptForBranchName(repository); - + const branch = await this.promptForBranchName(repository); if (!branch) { - return; + return undefined; } - commitish = 'HEAD'; + return { commitish: 'HEAD', branch }; } else { - return; + return undefined; } } else { // Check whether the selected branch is checked out in an existing worktree @@ -3504,17 +3554,14 @@ export class CommandCenter { await this.handleWorktreeConflict(worktree.path, message); return; } - commitish = choice.refName; + return { commitish: choice.refName, branch: undefined }; } } + } - const worktreeName = ((branch ?? commitish).startsWith(branchPrefix) - ? (branch ?? commitish).substring(branchPrefix.length).replace(/\//g, '-') - : (branch ?? commitish).replace(/\//g, '-')); - - // If user selects folder button, they manually select the worktree path through folder picker + private async getWorktreePath(repository: Repository, worktreeName: string): Promise { const getWorktreePath = async (): Promise => { - const worktreeRoot = this.globalState.get(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`); + const worktreeRoot = this.globalState.get(`${Repository.WORKTREE_ROOT_STORAGE_KEY}:${repository.root}`); const defaultUri = worktreeRoot ? Uri.file(worktreeRoot) : Uri.file(path.dirname(repository.root)); const uris = await window.showOpenDialog({ @@ -3550,7 +3597,7 @@ export class CommandCenter { }; // Default worktree path is based on the last worktree location or a worktree folder for the repository - const defaultWorktreeRoot = this.globalState.get(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`); + const defaultWorktreeRoot = this.globalState.get(`${Repository.WORKTREE_ROOT_STORAGE_KEY}:${repository.root}`); const defaultWorktreePath = defaultWorktreeRoot ? path.join(defaultWorktreeRoot, worktreeName) : path.join(path.dirname(repository.root), `${path.basename(repository.root)}.worktrees`, worktreeName); @@ -3589,35 +3636,13 @@ export class CommandCenter { dispose(disposables); - if (!worktreePath) { - return; - } - - try { - await repository.addWorktree({ path: worktreePath, branch, commitish: commitish }); - - // Update worktree root in global state - const worktreeRoot = path.dirname(worktreePath); - if (worktreeRoot !== defaultWorktreeRoot) { - this.globalState.update(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`, worktreeRoot); - } - } catch (err) { - if (err.gitErrorCode === GitErrorCodes.WorktreeAlreadyExists) { - await this.handleWorktreeAlreadyExists(err); - } else if (err.gitErrorCode === GitErrorCodes.WorktreeBranchAlreadyUsed) { - await this.handleWorktreeBranchAlreadyUsed(err); - } else { - throw err; - } - - return; - } - } - - private async handleWorktreeBranchAlreadyUsed(err: any): Promise { - const match = err.stderr.match(/fatal: '([^']+)' is already used by worktree at '([^']+)'/); - - if (!match) { + return worktreePath; + } + + private async handleWorktreeBranchAlreadyUsed(err: GitError): Promise { + const match = err.stderr?.match(/fatal: '([^']+)' is already used by worktree at '([^']+)'/); + + if (!match) { return; } @@ -3626,8 +3651,8 @@ export class CommandCenter { await this.handleWorktreeConflict(path, message); } - private async handleWorktreeAlreadyExists(err: any): Promise { - const match = err.stderr.match(/fatal: '([^']+)'/); + private async handleWorktreeAlreadyExists(err: GitError): Promise { + const match = err.stderr?.match(/fatal: '([^']+)'/); if (!match) { return; @@ -3659,49 +3684,16 @@ export class CommandCenter { return; } - - @command('git.deleteWorktree', { repository: true, repositoryFilter: ['worktree'] }) - async deleteWorktree(repository: Repository): Promise { - if (!repository.dotGit.commonPath) { - return; - } - - const mainRepository = this.model.getRepository(path.dirname(repository.dotGit.commonPath)); - if (!mainRepository) { - await window.showErrorMessage(l10n.t('You cannot delete the worktree you are currently in. Please switch to the main repository first.'), { modal: true }); - return; - } - - // Dispose worktree repository - this.model.disposeRepository(repository); - - try { - await mainRepository.deleteWorktree(repository.root); - } catch (err) { - if (err.gitErrorCode === GitErrorCodes.WorktreeContainsChanges) { - const forceDelete = l10n.t('Force Delete'); - const message = l10n.t('The worktree contains modified or untracked files. Do you want to force delete?'); - const choice = await window.showWarningMessage(message, { modal: true }, forceDelete); - if (choice === forceDelete) { - await mainRepository.deleteWorktree(repository.root, { force: true }); - } else { - await this.model.openRepository(repository.root); - } - - return; - } - - throw err; - } - } - - @command('git.deleteWorktreeFromPalette', { repository: true, repositoryFilter: ['repository', 'submodule'] }) + @command('git.deleteWorktree', { repository: true, repositoryFilter: ['repository', 'submodule'] }) async deleteWorktreeFromPalette(repository: Repository): Promise { + const config = workspace.getConfiguration('git', Uri.file(repository.root)); + const commitShortHashLength = config.get('commitShortHashLength') ?? 7; + const worktreePicks = async (): Promise => { - const worktrees = await repository.getWorktrees(); + const worktrees = await repository.getWorktreeDetails(); return worktrees.length === 0 ? [{ label: l10n.t('$(info) This repository has no worktrees.') }] - : worktrees.map(worktree => new WorktreeDeleteItem(worktree)); + : worktrees.map(worktree => new WorktreeDeleteItem(worktree, commitShortHashLength)); }; const placeHolder = l10n.t('Select a worktree to delete'); @@ -3712,6 +3704,21 @@ export class CommandCenter { } } + @command('git.deleteWorktree2', { repository: true, repositoryFilter: ['worktree'] }) + async deleteWorktree(repository: Repository): Promise { + if (!repository.dotGit.commonPath) { + return; + } + + const mainRepository = this.model.getRepository(path.dirname(repository.dotGit.commonPath)); + if (!mainRepository) { + await window.showErrorMessage(l10n.t('You cannot delete the worktree you are currently in. Please switch to the main repository first.'), { modal: true }); + return; + } + + await mainRepository.deleteWorktree(repository.root); + } + @command('git.openWorktree', { repository: true }) async openWorktreeInCurrentWindow(repository: Repository): Promise { if (!repository) { @@ -4564,7 +4571,7 @@ export class CommandCenter { return; } - await this._stashDrop(repository, stash); + await this._stashDrop(repository, stash.index, stash.description); } @command('git.stashDropAll', { repository: true }) @@ -4597,15 +4604,15 @@ export class CommandCenter { return; } - if (await this._stashDrop(result.repository, result.stash)) { + if (await this._stashDrop(result.repository, result.stash.index, result.stash.description)) { await commands.executeCommand('workbench.action.closeActiveEditor'); } } - async _stashDrop(repository: Repository, stash: Stash): Promise { + async _stashDrop(repository: Repository, index: number, description: string): Promise { const yes = l10n.t('Yes'); const result = await window.showWarningMessage( - l10n.t('Are you sure you want to drop the stash: {0}?', stash.description), + l10n.t('Are you sure you want to drop the stash: {0}?', description), { modal: true }, yes ); @@ -4613,7 +4620,7 @@ export class CommandCenter { return false; } - await repository.dropStash(stash.index); + await repository.dropStash(index); return true; } @@ -4626,36 +4633,7 @@ export class CommandCenter { return; } - const stashChanges = await repository.showStash(stash.index); - if (!stashChanges || stashChanges.length === 0) { - return; - } - - // A stash commit can have up to 3 parents: - // 1. The first parent is the commit that was HEAD when the stash was created. - // 2. The second parent is the commit that represents the index when the stash was created. - // 3. The third parent (when present) represents the untracked files when the stash was created. - const stashFirstParentCommit = stash.parents.length > 0 ? stash.parents[0] : `${stash.hash}^`; - const stashUntrackedFilesParentCommit = stash.parents.length === 3 ? stash.parents[2] : undefined; - const stashUntrackedFiles: string[] = []; - - if (stashUntrackedFilesParentCommit) { - const untrackedFiles = await repository.getObjectFiles(stashUntrackedFilesParentCommit); - stashUntrackedFiles.push(...untrackedFiles.map(f => path.join(repository.root, f.file))); - } - - const title = `Git Stash #${stash.index}: ${stash.description}`; - const multiDiffSourceUri = toGitUri(Uri.file(repository.root), `stash@{${stash.index}}`, { scheme: 'git-stash' }); - - const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = []; - for (const change of stashChanges) { - const isChangeUntracked = !!stashUntrackedFiles.find(f => pathEquals(f, change.uri.fsPath)); - const modifiedUriRef = !isChangeUntracked ? stash.hash : stashUntrackedFilesParentCommit ?? stash.hash; - - resources.push(toMultiFileDiffEditorUris(change, stashFirstParentCommit, modifiedUriRef)); - } - - commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); + await this._viewStash(repository, stash); } private async pickStash(repository: Repository, placeHolder: string): Promise { @@ -4700,6 +4678,39 @@ export class CommandCenter { return { repository, stash }; } + private async _viewStash(repository: Repository, stash: Stash): Promise { + const stashChanges = await repository.showStash(stash.index); + if (!stashChanges || stashChanges.length === 0) { + return; + } + + // A stash commit can have up to 3 parents: + // 1. The first parent is the commit that was HEAD when the stash was created. + // 2. The second parent is the commit that represents the index when the stash was created. + // 3. The third parent (when present) represents the untracked files when the stash was created. + const stashFirstParentCommit = stash.parents.length > 0 ? stash.parents[0] : `${stash.hash}^`; + const stashUntrackedFilesParentCommit = stash.parents.length === 3 ? stash.parents[2] : undefined; + const stashUntrackedFiles: string[] = []; + + if (stashUntrackedFilesParentCommit) { + const untrackedFiles = await repository.getObjectFiles(stashUntrackedFilesParentCommit); + stashUntrackedFiles.push(...untrackedFiles.map(f => path.join(repository.root, f.file))); + } + + const title = `Git Stash #${stash.index}: ${stash.description}`; + const multiDiffSourceUri = toGitUri(Uri.file(repository.root), `stash@{${stash.index}}`, { scheme: 'git-stash' }); + + const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = []; + for (const change of stashChanges) { + const isChangeUntracked = !!stashUntrackedFiles.find(f => pathEquals(f, change.uri.fsPath)); + const modifiedUriRef = !isChangeUntracked ? stash.hash : stashUntrackedFilesParentCommit ?? stash.hash; + + resources.push(toMultiFileDiffEditorUris(change, stashFirstParentCommit, modifiedUriRef)); + } + + commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); + } + @command('git.timeline.openDiff', { repository: false }) async timelineOpenDiff(item: TimelineItem, uri: Uri | undefined, _source: string) { const cmd = this.resolveTimelineOpenDiffCommand( @@ -4731,7 +4742,7 @@ export class CommandCenter { else if (item.previousRef === 'HEAD' && item.ref === '~') { title = l10n.t('{0} (Index)', basename); } else { - title = l10n.t('{0} ({1}) ↔ {0} ({2})', basename, item.shortPreviousRef, item.shortRef); + title = l10n.t('{0} ({1}) \u2194 {0} ({2})', basename, item.shortPreviousRef, item.shortRef); } return { @@ -4774,16 +4785,17 @@ export class CommandCenter { const commit = await repository.getCommit(item.ref); const commitParentId = commit.parents.length > 0 ? commit.parents[0] : await repository.getEmptyTree(); - const changes = await repository.diffTrees(commitParentId, commit.hash); + const changes = await repository.diffBetweenWithStats(commitParentId, commit.hash); const resources = changes.map(c => toMultiFileDiffEditorUris(c, commitParentId, commit.hash)); - const title = `${item.shortRef} - ${truncate(commit.message)}`; + const title = `${item.shortRef} - ${subject(commit.message)}`; const multiDiffSourceUri = Uri.from({ scheme: 'scm-history-item', path: `${repository.root}/${commitParentId}..${commit.hash}` }); + const reveal = { modifiedUri: toGitUri(uri, commit.hash) }; return { command: '_workbench.openMultiDiffEditor', title: l10n.t('Open Commit'), - arguments: [{ multiDiffSourceUri, title, resources }, options] + arguments: [{ multiDiffSourceUri, title, resources, reveal }, options] }; } @@ -4847,7 +4859,7 @@ export class CommandCenter { } - const title = l10n.t('{0} ↔ {1}', leftTitle, rightTitle); + const title = l10n.t('{0} \u2194 {1}', leftTitle, rightTitle); await commands.executeCommand('vscode.diff', selected.ref === '' ? uri : toGitUri(uri, selected.ref), item.ref === '' ? uri : toGitUri(uri, item.ref), title); } @@ -5032,7 +5044,7 @@ export class CommandCenter { } @command('git.viewCommit', { repository: true }) - async viewCommit(repository: Repository, historyItemId: string): Promise { + async viewCommit(repository: Repository, historyItemId: string, revealUri?: Uri): Promise { if (!repository || !historyItemId) { return; } @@ -5042,15 +5054,16 @@ export class CommandCenter { const commitShortHashLength = config.get('commitShortHashLength', 7); const commit = await repository.getCommit(historyItemId); - const title = `${truncate(historyItemId, commitShortHashLength, false)} - ${truncate(commit.message)}`; + const title = `${truncate(historyItemId, commitShortHashLength, false)} - ${subject(commit.message)}`; const historyItemParentId = commit.parents.length > 0 ? commit.parents[0] : await repository.getEmptyTree(); const multiDiffSourceUri = Uri.from({ scheme: 'scm-history-item', path: `${repository.root}/${historyItemParentId}..${historyItemId}` }); - const changes = await repository.diffTrees(historyItemParentId, historyItemId); + const changes = await repository.diffBetweenWithStats(historyItemParentId, historyItemId); const resources = changes.map(c => toMultiFileDiffEditorUris(c, historyItemParentId, historyItemId)); + const reveal = revealUri ? { modifiedUri: toGitUri(revealUri, historyItemId) } : undefined; - await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources }); + await commands.executeCommand('_workbench.openMultiDiffEditor', { multiDiffSourceUri, title, resources, reveal }); } @command('git.copyContentToClipboard') @@ -5079,6 +5092,272 @@ export class CommandCenter { config.update(setting, !enabled, true); } + @command('git.repositories.createBranch', { repository: true }) + async artifactGroupCreateBranch(repository: Repository): Promise { + if (!repository) { + return; + } + + await this._branch(repository, undefined, false); + } + + @command('git.repositories.createTag', { repository: true }) + async artifactGroupCreateTag(repository: Repository): Promise { + if (!repository) { + return; + } + + await this._createTag(repository); + } + + @command('git.repositories.createWorktree', { repository: true }) + async artifactGroupCreateWorktree(repository: Repository): Promise { + if (!repository) { + return; + } + + await this._createWorktree(repository); + } + + @command('git.repositories.checkout', { repository: true }) + async artifactCheckout(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + await this._checkout(repository, { treeish: artifact.name }); + } + + @command('git.repositories.checkoutDetached', { repository: true }) + async artifactCheckoutDetached(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + await this._checkout(repository, { treeish: artifact.name, detached: true }); + } + + @command('git.repositories.merge', { repository: true }) + async artifactMerge(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + await repository.merge(artifact.id); + } + + @command('git.repositories.rebase', { repository: true }) + async artifactRebase(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + await repository.rebase(artifact.id); + } + + @command('git.repositories.createFrom', { repository: true }) + async artifactCreateFrom(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + await this._branch(repository, undefined, false, artifact.id); + } + + @command('git.repositories.compareRef', { repository: true }) + async artifactCompareWith(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + const config = workspace.getConfiguration('git'); + const showRefDetails = config.get('showReferenceDetails') === true; + + const getRefPicks = async () => { + const refs = await repository.getRefs({ includeCommitDetails: showRefDetails }); + const processors = [ + new RefProcessor(RefType.Head, BranchItem), + new RefProcessor(RefType.RemoteHead, BranchItem), + new RefProcessor(RefType.Tag, BranchItem) + ]; + + const itemsProcessor = new RefItemsProcessor(repository, processors); + return itemsProcessor.processRefs(refs); + }; + + const placeHolder = l10n.t('Select a reference to compare with'); + const sourceRef = await this.pickRef(getRefPicks(), placeHolder); + + if (!(sourceRef instanceof BranchItem) || !sourceRef.ref.commit) { + return; + } + + await this._openChangesBetweenRefs( + repository, + { + id: sourceRef.ref.commit, + displayId: sourceRef.ref.name + }, + { + id: artifact.id, + displayId: artifact.name + }); + } + + private async _createTag(repository: Repository, ref?: string): Promise { + const inputTagName = await window.showInputBox({ + placeHolder: l10n.t('Tag name'), + prompt: l10n.t('Please provide a tag name'), + ignoreFocusOut: true + }); + + if (!inputTagName) { + return; + } + + const inputMessage = await window.showInputBox({ + placeHolder: l10n.t('Message'), + prompt: l10n.t('Please provide a message to annotate the tag'), + ignoreFocusOut: true + }); + + const name = inputTagName.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-'); + await repository.tag({ name, message: inputMessage, ref }); + } + + @command('git.repositories.deleteBranch', { repository: true }) + async artifactDeleteBranch(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + const message = l10n.t('Are you sure you want to delete branch "{0}"? This action will permanently remove the branch reference from the repository.', artifact.name); + const yes = l10n.t('Delete Branch'); + const result = await window.showWarningMessage(message, { modal: true }, yes); + if (result !== yes) { + return; + } + + await this._deleteBranch(repository, undefined, artifact.name, { remote: false }); + } + + @command('git.repositories.deleteTag', { repository: true }) + async artifactDeleteTag(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + const message = l10n.t('Are you sure you want to delete tag "{0}"? This action will permanently remove the tag reference from the repository.', artifact.name); + const yes = l10n.t('Delete Tag'); + const result = await window.showWarningMessage(message, { modal: true }, yes); + if (result !== yes) { + return; + } + + await repository.deleteTag(artifact.name); + } + + @command('git.repositories.stashView', { repository: true }) + async artifactStashView(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + // Extract stash index from artifact id + const regex = /^stash@\{(\d+)\}$/; + const match = regex.exec(artifact.id); + if (!match) { + return; + } + + const stashes = await repository.getStashes(); + const stash = stashes.find(s => s.index === parseInt(match[1])); + if (!stash) { + return; + } + + await this._viewStash(repository, stash); + } + + @command('git.repositories.stashApply', { repository: true }) + async artifactStashApply(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + // Extract stash index from artifact id (format: "stash@{index}") + const regex = /^stash@\{(\d+)\}$/; + const match = regex.exec(artifact.id); + if (!match) { + return; + } + + const stashIndex = parseInt(match[1]); + await repository.applyStash(stashIndex); + } + + @command('git.repositories.stashPop', { repository: true }) + async artifactStashPop(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + // Extract stash index from artifact id (format: "stash@{index}") + const regex = /^stash@\{(\d+)\}$/; + const match = regex.exec(artifact.id); + if (!match) { + return; + } + + const stashIndex = parseInt(match[1]); + await repository.popStash(stashIndex); + } + + @command('git.repositories.stashDrop', { repository: true }) + async artifactStashDrop(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + // Extract stash index from artifact id + const regex = /^stash@\{(\d+)\}$/; + const match = regex.exec(artifact.id); + if (!match) { + return; + } + + await this._stashDrop(repository, parseInt(match[1]), artifact.name); + } + + @command('git.repositories.openWorktree', { repository: true }) + async artifactOpenWorktree(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + const uri = Uri.file(artifact.id); + await commands.executeCommand('vscode.openFolder', uri, { forceReuseWindow: true }); + } + + @command('git.repositories.openWorktreeInNewWindow', { repository: true }) + async artifactOpenWorktreeInNewWindow(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + const uri = Uri.file(artifact.id); + await commands.executeCommand('vscode.openFolder', uri, { forceNewWindow: true }); + } + + @command('git.repositories.deleteWorktree', { repository: true }) + async artifactDeleteWorktree(repository: Repository, artifact: SourceControlArtifact): Promise { + if (!repository || !artifact) { + return; + } + + await repository.deleteWorktree(artifact.id); + } + private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; @@ -5237,7 +5516,7 @@ export class CommandCenter { }; // patch this object, so people can call methods directly - (this as any)[key] = result; + (this as Record)[key] = result; return result; } diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 2e72a1e41144c..fb895d5aff2b3 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import { Repository, GitResourceGroup } from './repository'; import { Model } from './model'; import { debounce } from './decorators'; -import { filterEvent, dispose, anyEvent, fireEvent, PromiseSource, combinedDisposable, runAndSubscribeEvent } from './util'; +import { filterEvent, dispose, anyEvent, PromiseSource, combinedDisposable, runAndSubscribeEvent } from './util'; import { Change, GitErrorCodes, Status } from './api/git'; function equalSourceControlHistoryItemRefs(ref1?: SourceControlHistoryItemRef, ref2?: SourceControlHistoryItemRef): boolean { @@ -25,17 +25,19 @@ class GitIgnoreDecorationProvider implements FileDecorationProvider { private static Decoration: FileDecoration = { color: new ThemeColor('gitDecoration.ignoredResourceForeground') }; - readonly onDidChangeFileDecorations: Event; + private readonly _onDidChangeDecorations = new EventEmitter(); + readonly onDidChangeFileDecorations: Event = this._onDidChangeDecorations.event; + private queue = new Map> }>(); private disposables: Disposable[] = []; constructor(private model: Model) { - this.onDidChangeFileDecorations = fireEvent(anyEvent( + const onDidChangeRepository = anyEvent( filterEvent(workspace.onDidSaveTextDocument, e => /\.gitignore$|\.git\/info\/exclude$/.test(e.uri.path)), model.onDidOpenRepository, model.onDidCloseRepository - )); - + ); + this.disposables.push(onDidChangeRepository(() => this._onDidChangeDecorations.fire(undefined))); this.disposables.push(window.registerFileDecorationProvider(this)); } @@ -255,7 +257,7 @@ class GitIncomingChangesFileDecorationProvider implements FileDecorationProvider return []; } - const changes = await this.repository.diffBetween(ancestor, currentHistoryItemRemoteRef.id); + const changes = await this.repository.diffBetweenWithStats(ancestor, currentHistoryItemRemoteRef.id); return changes; } catch (err) { return []; diff --git a/extensions/git/src/decorators.ts b/extensions/git/src/decorators.ts index f89ff2327e95f..0e59a849ed2f9 100644 --- a/extensions/git/src/decorators.ts +++ b/extensions/git/src/decorators.ts @@ -6,23 +6,11 @@ import { done } from './util'; function decorate(decorator: (fn: Function, key: string) => Function): Function { - return (_target: any, key: string, descriptor: any) => { - let fnKey: string | null = null; - let fn: Function | null = null; - - if (typeof descriptor.value === 'function') { - fnKey = 'value'; - fn = descriptor.value; - } else if (typeof descriptor.get === 'function') { - fnKey = 'get'; - fn = descriptor.get; + return function (original: unknown, context: ClassMethodDecoratorContext) { + if (typeof original === 'function' && (context.kind === 'method' || context.kind === 'getter' || context.kind === 'setter')) { + return decorator(original, context.name.toString()); } - - if (!fn || !fnKey) { - throw new Error('not supported'); - } - - descriptor[fnKey] = decorator(fn, key); + throw new Error('not supported'); }; } diff --git a/extensions/git/src/emoji.ts b/extensions/git/src/emoji.ts index bd686b0160d6a..7c41ce6952e94 100644 --- a/extensions/git/src/emoji.ts +++ b/extensions/git/src/emoji.ts @@ -24,7 +24,7 @@ export async function ensureEmojis() { async function loadEmojiMap() { const context = getExtensionContext(); - const uri = (Uri as any).joinPath(context.extensionUri, 'resources', 'emojis.json'); + const uri = Uri.joinPath(context.extensionUri, 'resources', 'emojis.json'); emojiMap = JSON.parse(new TextDecoder('utf8').decode(await workspace.fs.readFile(uri))); } diff --git a/extensions/git/src/git-editor-main.ts b/extensions/git/src/git-editor-main.ts index eb4da4a40b55f..80615b56e5ab2 100644 --- a/extensions/git/src/git-editor-main.ts +++ b/extensions/git/src/git-editor-main.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IPCClient } from './ipc/ipcClient'; -function fatal(err: any): void { +function fatal(err: unknown): void { console.error(err); process.exit(1); } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index e04a1a754c7d5..3fb80e0ee98d7 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -11,9 +11,9 @@ import { fileURLToPath } from 'url'; import which from 'which'; import { EventEmitter } from 'events'; import * as filetype from 'file-type'; -import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals, isMacintosh, isDescendant, relativePathWithNoFallback } from './util'; +import { assign, groupBy, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent, splitInChunks, Limiter, Versions, isWindows, pathEquals, isMacintosh, isDescendant, relativePathWithNoFallback, Mutable } from './util'; import { CancellationError, CancellationToken, ConfigurationChangeEvent, LogOutputChannel, Progress, Uri, workspace } from 'vscode'; -import { Commit as ApiCommit, Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery as ApiRefQuery, InitOptions } from './api/git'; +import { Commit as ApiCommit, Ref, RefType, Branch, Remote, ForcePushMode, GitErrorCodes, LogOptions, Change, Status, CommitOptions, RefQuery as ApiRefQuery, InitOptions, DiffChange, Worktree as ApiWorktree } from './api/git'; import * as byline from 'byline'; import { StringDecoder } from 'string_decoder'; @@ -44,6 +44,8 @@ export interface Stash { readonly index: number; readonly description: string; readonly branchName?: string; + readonly authorDate?: Date; + readonly commitDate?: Date; } interface MutableRemote extends Remote { @@ -117,7 +119,7 @@ function findGitDarwin(onValidate: (path: string) => boolean): Promise { } // must check if XCode is installed - cp.exec('xcode-select -p', (err: any) => { + cp.exec('xcode-select -p', (err) => { if (err && err.code === 2) { // git is not installed, and launching /usr/bin/git // will prompt the user to install it @@ -307,8 +309,8 @@ export class GitError extends Error { stderr: this.stderr }, null, 2); - if (this.error) { - result += (this.error).stack; + if (this.error?.stack) { + result += this.error.stack; } return result; @@ -370,7 +372,7 @@ function sanitizeRelativePath(path: string): string { } const COMMIT_FORMAT = '%H%n%aN%n%aE%n%at%n%ct%n%P%n%D%n%B'; -const STASH_FORMAT = '%H%n%P%n%gd%n%gs'; +const STASH_FORMAT = '%H%n%P%n%gd%n%gs%n%at%n%ct'; export interface ICloneOptions { readonly parentPath: string; @@ -865,12 +867,6 @@ export class GitStatusParser { } } -export interface Worktree { - readonly name: string; - readonly path: string; - readonly ref: string; -} - export interface Submodule { name: string; path: string; @@ -999,12 +995,12 @@ export function parseLsFiles(raw: string): LsFilesElement[] { .map(([, mode, object, stage, file]) => ({ mode, object, stage, file })); } -const stashRegex = /([0-9a-f]{40})\n(.*)\nstash@{(\d+)}\n(WIP\s)*on([^:]+):(.*)(?:\x00)/gmi; +const stashRegex = /([0-9a-f]{40})\n(.*)\nstash@{(\d+)}\n(WIP\s)?on\s([^:]+):\s(.*)\n(\d+)\n(\d+)(?:\x00)/gmi; function parseGitStashes(raw: string): Stash[] { const result: Stash[] = []; - let match, hash, parents, index, wip, branchName, description; + let match, hash, parents, index, wip, branchName, description, authorDate, commitDate; do { match = stashRegex.exec(raw); @@ -1012,13 +1008,15 @@ function parseGitStashes(raw: string): Stash[] { break; } - [, hash, parents, index, wip, branchName, description] = match; + [, hash, parents, index, wip, branchName, description, authorDate, commitDate] = match; result.push({ hash, parents: parents.split(' '), index: parseInt(index), branchName: branchName.trim(), - description: wip ? `WIP (${description.trim()})` : description.trim() + description: wip ? `WIP (${description.trim()})` : description.trim(), + authorDate: authorDate ? new Date(Number(authorDate) * 1000) : undefined, + commitDate: commitDate ? new Date(Number(commitDate) * 1000) : undefined, }); } while (true); @@ -1086,6 +1084,79 @@ function parseGitChanges(repositoryRoot: string, raw: string): Change[] { return result; } +function parseGitChangesRaw(repositoryRoot: string, raw: string): DiffChange[] { + const changes: Change[] = []; + const numStats = new Map(); + + let index = 0; + const segments = raw.trim().split('\x00').filter(s => s); + + segmentsLoop: + while (index < segments.length) { + const segment = segments[index++]; + if (!segment) { + break; + } + + if (segment.startsWith(':')) { + // Parse --raw output + const [, , , , change] = segment.split(' '); + const filePath = segments[index++]; + const originalUri = Uri.file(path.isAbsolute(filePath) ? filePath : path.join(repositoryRoot, filePath)); + + let uri = originalUri; + let renameUri = originalUri; + let status = Status.UNTRACKED; + + switch (change[0]) { + case 'A': + status = Status.INDEX_ADDED; + break; + case 'M': + status = Status.MODIFIED; + break; + case 'D': + status = Status.DELETED; + break; + case 'R': { + if (index >= segments.length) { + break; + } + const newPath = segments[index++]; + if (!newPath) { + break; + } + + status = Status.INDEX_RENAMED; + uri = renameUri = Uri.file(path.isAbsolute(newPath) ? newPath : path.join(repositoryRoot, newPath)); + break; + } + default: + // Unknown status + break segmentsLoop; + } + + changes.push({ status, uri, originalUri, renameUri }); + } else { + // Parse --numstat output + const [insertions, deletions, filePath] = segment.split('\t'); + numStats.set( + path.isAbsolute(filePath) + ? filePath + : path.join(repositoryRoot, filePath), { + insertions: insertions === '-' ? 0 : parseInt(insertions), + deletions: deletions === '-' ? 0 : parseInt(deletions), + }); + } + } + + return changes.map(change => ({ + ...change, + insertions: numStats.get(change.uri.fsPath)?.insertions ?? 0, + deletions: numStats.get(change.uri.fsPath)?.deletions ?? 0, + })); +} + export interface BlameInformation { readonly hash: string; readonly subject?: string; @@ -1232,6 +1303,10 @@ export interface PullOptions { readonly cancellationToken?: CancellationToken; } +export interface Worktree extends ApiWorktree { + readonly commitDetails?: ApiCommit; +} + export class Repository { private _isUsingRefTable = false; @@ -1620,7 +1695,7 @@ export class Repository { diffWithHEAD(path?: string | undefined): Promise; async diffWithHEAD(path?: string | undefined): Promise { if (!path) { - return await this.diffFiles(false); + return await this.diffFiles(undefined, { cached: false }); } const args = ['diff', '--', this.sanitizeRelativePath(path)]; @@ -1628,12 +1703,16 @@ export class Repository { return result.stdout; } + async diffWithHEADShortStats(path?: string): Promise { + return this.diffFilesShortStat(undefined, { cached: false, path }); + } + diffWith(ref: string): Promise; diffWith(ref: string, path: string): Promise; diffWith(ref: string, path?: string | undefined): Promise; async diffWith(ref: string, path?: string): Promise { if (!path) { - return await this.diffFiles(false, ref); + return await this.diffFiles(ref, { cached: false }); } const args = ['diff', ref, '--', this.sanitizeRelativePath(path)]; @@ -1646,7 +1725,7 @@ export class Repository { diffIndexWithHEAD(path?: string | undefined): Promise; async diffIndexWithHEAD(path?: string): Promise { if (!path) { - return await this.diffFiles(true); + return await this.diffFiles(undefined, { cached: true }); } const args = ['diff', '--cached', '--', this.sanitizeRelativePath(path)]; @@ -1654,12 +1733,16 @@ export class Repository { return result.stdout; } + async diffIndexWithHEADShortStats(path?: string): Promise { + return this.diffFilesShortStat(undefined, { cached: true, path }); + } + diffIndexWith(ref: string): Promise; diffIndexWith(ref: string, path: string): Promise; diffIndexWith(ref: string, path?: string | undefined): Promise; async diffIndexWith(ref: string, path?: string): Promise { if (!path) { - return await this.diffFiles(true, ref); + return await this.diffFiles(ref, { cached: true }); } const args = ['diff', '--cached', ref, '--', this.sanitizeRelativePath(path)]; @@ -1679,7 +1762,7 @@ export class Repository { async diffBetween(ref1: string, ref2: string, path?: string): Promise { const range = `${ref1}...${ref2}`; if (!path) { - return await this.diffFiles(false, range); + return await this.diffFiles(range, { cached: false }); } const args = ['diff', range, '--', this.sanitizeRelativePath(path)]; @@ -1688,27 +1771,43 @@ export class Repository { return result.stdout.trim(); } - async diffBetweenShortStat(ref1: string, ref2: string): Promise<{ files: number; insertions: number; deletions: number }> { - const args = ['diff', '--shortstat', `${ref1}...${ref2}`]; + async diffBetweenWithStats(ref: string, options: { path?: string; similarityThreshold?: number }): Promise { + const args = ['diff', '--raw', '--numstat', '--diff-filter=ADMR', '-z',]; - const result = await this.exec(args); - if (result.exitCode) { - return { files: 0, insertions: 0, deletions: 0 }; + if (options.similarityThreshold) { + args.push(`--find-renames=${options.similarityThreshold}%`); } - return parseGitDiffShortStat(result.stdout.trim()); + args.push(...[ref, '--']); + if (options.path) { + args.push(this.sanitizeRelativePath(options.path)); + } + + const gitResult = await this.exec(args); + if (gitResult.exitCode) { + return []; + } + + return parseGitChangesRaw(this.repositoryRoot, gitResult.stdout); } - private async diffFiles(cached: boolean, ref?: string): Promise { + private async diffFiles(ref: string | undefined, options: { cached: boolean; similarityThreshold?: number }): Promise { const args = ['diff', '--name-status', '-z', '--diff-filter=ADMR']; - if (cached) { + + if (options.cached) { args.push('--cached'); } + if (options.similarityThreshold) { + args.push(`--find-renames=${options.similarityThreshold}%`); + } + if (ref) { args.push(ref); } + args.push('--'); + const gitResult = await this.exec(args); if (gitResult.exitCode) { return []; @@ -1717,8 +1816,34 @@ export class Repository { return parseGitChanges(this.repositoryRoot, gitResult.stdout); } - async diffTrees(treeish1: string, treeish2?: string, options?: { similarityThreshold?: number }): Promise { - const args = ['diff-tree', '-r', '--name-status', '-z', '--diff-filter=ADMR']; + private async diffFilesShortStat(ref: string | undefined, options: { cached: boolean; path?: string }): Promise { + const args = ['diff', '--shortstat']; + + if (options.cached) { + args.push('--cached'); + } + + if (ref !== undefined) { + args.push(ref); + } + + args.push('--'); + + if (options.path) { + args.push(this.sanitizeRelativePath(options.path)); + } + + const result = await this.exec(args); + if (result.exitCode) { + return { files: 0, insertions: 0, deletions: 0 }; + } + + return parseGitDiffShortStat(result.stdout.trim()); + } + + + async diffTrees(treeish1: string, treeish2?: string, options?: { similarityThreshold?: number }): Promise { + const args = ['diff-tree', '-r', '--raw', '--numstat', '--diff-filter=ADMR', '-z']; if (options?.similarityThreshold) { args.push(`--find-renames=${options.similarityThreshold}%`); @@ -1730,12 +1855,14 @@ export class Repository { args.push(treeish2); } + args.push('--'); + const gitResult = await this.exec(args); if (gitResult.exitCode) { return []; } - return parseGitChanges(this.repositoryRoot, gitResult.stdout); + return parseGitChangesRaw(this.repositoryRoot, gitResult.stdout); } async getMergeBase(ref1: string, ref2: string, ...refs: string[]): Promise { @@ -1939,11 +2066,12 @@ export class Repository { } } - private async handleCommitError(commitErr: any): Promise { - if (/not possible because you have unmerged files/.test(commitErr.stderr || '')) { + + private async handleCommitError(commitErr: unknown): Promise { + if (commitErr instanceof GitError && /not possible because you have unmerged files/.test(commitErr.stderr || '')) { commitErr.gitErrorCode = GitErrorCodes.UnmergedChanges; throw commitErr; - } else if (/Aborting commit due to empty commit message/.test(commitErr.stderr || '')) { + } else if (commitErr instanceof GitError && /Aborting commit due to empty commit message/.test(commitErr.stderr || '')) { commitErr.gitErrorCode = GitErrorCodes.EmptyCommitMessage; throw commitErr; } @@ -2077,8 +2205,8 @@ export class Repository { const pathsByGroup = groupBy(paths.map(sanitizePath), p => path.dirname(p)); const groups = Object.keys(pathsByGroup).map(k => pathsByGroup[k]); - const limiter = new Limiter(5); - const promises: Promise[] = []; + const limiter = new Limiter>(5); + const promises: Promise>[] = []; const args = ['clean', '-f', '-q']; for (const paths of groups) { @@ -2379,10 +2507,14 @@ export class Repository { } } - async blame2(path: string, ref?: string): Promise { + async blame2(path: string, ref?: string, ignoreWhitespace?: boolean): Promise { try { const args = ['blame', '--root', '--incremental']; + if (ignoreWhitespace) { + args.push('-w'); + } + if (ref) { args.push(ref); } @@ -2424,13 +2556,19 @@ export class Repository { } } - async popStash(index?: number): Promise { + async popStash(index?: number, options?: { reinstateStagedChanges?: boolean }): Promise { const args = ['stash', 'pop']; + if (options?.reinstateStagedChanges) { + args.push('--index'); + } await this.popOrApplyStash(args, index); } - async applyStash(index?: number): Promise { + async applyStash(index?: number, options?: { reinstateStagedChanges?: boolean }): Promise { const args = ['stash', 'apply']; + if (options?.reinstateStagedChanges) { + args.push('--index'); + } await this.popOrApplyStash(args, index); } @@ -2779,14 +2917,6 @@ export class Repository { } private async getWorktreesFS(): Promise { - const config = workspace.getConfiguration('git', Uri.file(this.repositoryRoot)); - const shouldDetectWorktrees = config.get('detectWorktrees') === true; - - if (!shouldDetectWorktrees) { - this.logger.info('[Git][getWorktreesFS] Worktree detection is disabled, skipping worktree detection'); - return []; - } - try { // List all worktree folder names const worktreesPath = path.join(this.dotGit.commonPath ?? this.dotGit.path, 'worktrees'); @@ -2811,6 +2941,8 @@ export class Repository { path: gitdirContent.replace(/\/.git.*$/, ''), // Remove 'ref: ' prefix ref: headContent.replace(/^ref: /, ''), + // Detached if HEAD does not start with 'ref: ' + detached: !headContent.startsWith('ref: ') }); } catch (err) { if (/ENOENT/.test(err.message)) { @@ -2972,8 +3104,8 @@ export class Repository { const result = await this.exec(['rev-list', '--left-right', '--count', `${branch.name}...${branch.upstream.remote}/${branch.upstream.name}`]); const [ahead, behind] = result.stdout.trim().split('\t'); - (branch as any).ahead = Number(ahead) || 0; - (branch as any).behind = Number(behind) || 0; + (branch as Mutable).ahead = Number(ahead) || 0; + (branch as Mutable).behind = Number(behind) || 0; } catch { } } @@ -3053,9 +3185,27 @@ export class Repository { return commits[0]; } - async showCommit(ref: string): Promise { + async showChanges(ref: string): Promise { + try { + const result = await this.exec(['log', '-p', '-n1', ref, '--']); + return result.stdout.trim(); + } catch (err) { + if (/^fatal: bad revision '.+'/.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.BadRevision; + } + + throw err; + } + } + + async showChangesBetween(ref1: string, ref2: string, path?: string): Promise { try { - const result = await this.exec(['show', ref]); + const args = ['log', '-p', `${ref1}..${ref2}`, '--']; + if (path) { + args.push(this.sanitizeRelativePath(path)); + } + + const result = await this.exec(args); return result.stdout.trim(); } catch (err) { if (/^fatal: bad revision '.+'/.test(err.stderr || '')) { diff --git a/extensions/git/src/gitEditor.ts b/extensions/git/src/gitEditor.ts index 6291e5152a72f..cbbea2c6d785a 100644 --- a/extensions/git/src/gitEditor.ts +++ b/extensions/git/src/gitEditor.ts @@ -34,7 +34,7 @@ export class GitEditor implements IIPCHandler, ITerminalEnvironmentProvider { }; } - async handle({ commitMessagePath }: GitEditorRequest): Promise { + async handle({ commitMessagePath }: GitEditorRequest): Promise { if (commitMessagePath) { const uri = Uri.file(commitMessagePath); const doc = await workspace.openTextDocument(uri); @@ -49,6 +49,8 @@ export class GitEditor implements IIPCHandler, ITerminalEnvironmentProvider { }); }); } + + return Promise.resolve(false); } getEnv(): { [key: string]: string } { diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index 9830928fcd3d5..f921f5734a50e 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -4,16 +4,17 @@ *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent, workspace, ConfigurationChangeEvent } from 'vscode'; +import { CancellationToken, Disposable, Event, EventEmitter, FileDecoration, FileDecorationProvider, SourceControlHistoryItem, SourceControlHistoryItemChange, SourceControlHistoryOptions, SourceControlHistoryProvider, ThemeIcon, Uri, window, LogOutputChannel, SourceControlHistoryItemRef, l10n, SourceControlHistoryItemRefsChangeEvent, workspace, ConfigurationChangeEvent, Command, commands } from 'vscode'; import { Repository, Resource } from './repository'; -import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, truncate } from './util'; +import { IDisposable, deltaHistoryItemRefs, dispose, filterEvent, subject, truncate } from './util'; import { toMultiFileDiffEditorUris } from './uri'; import { AvatarQuery, AvatarQueryCommit, Branch, LogOptions, Ref, RefType } from './api/git'; import { emojify, ensureEmojis } from './emoji'; import { Commit } from './git'; import { OperationKind, OperationResult } from './operation'; -import { ISourceControlHistoryItemDetailsProviderRegistry, provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider'; +import { ISourceControlHistoryItemDetailsProviderRegistry, provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider'; import { throttle } from './decorators'; +import { getHistoryItemHover, getHoverCommitHashCommands, processHoverRemoteCommands } from './hover'; function compareSourceControlHistoryItemRef(ref1: SourceControlHistoryItemRef, ref2: SourceControlHistoryItemRef): number { const getOrder = (ref: SourceControlHistoryItemRef): number => { @@ -124,7 +125,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec id: `refs/heads/${this.repository.HEAD.upstream.name}`, name: this.repository.HEAD.upstream.name, revision: this.repository.HEAD.upstream.commit, - icon: new ThemeIcon('gi-branch') + icon: new ThemeIcon('git-branch') }; } else { // Remote branch @@ -185,6 +186,14 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } } + // Update context keys for HEAD + if (this._HEAD?.ahead !== this.repository.HEAD?.ahead) { + commands.executeCommand('setContext', 'git.currentHistoryItemIsAhead', (this.repository.HEAD?.ahead ?? 0) > 0); + } + if (this._HEAD?.behind !== this.repository.HEAD?.behind) { + commands.executeCommand('setContext', 'git.currentHistoryItemIsBehind', (this.repository.HEAD?.behind ?? 0) > 0); + } + this._HEAD = this.repository.HEAD; this._currentHistoryItemRef = { @@ -282,6 +291,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const commitAvatars = await provideSourceControlHistoryItemAvatar( this.historyItemDetailProviderRegistry, this.repository, avatarQuery); + const remoteHoverCommands = await provideSourceControlHistoryItemHoverCommands(this.historyItemDetailProviderRegistry, this.repository) ?? []; + await ensureEmojis(); const historyItems: SourceControlHistoryItem[] = []; @@ -290,18 +301,20 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const messageWithLinks = await provideSourceControlHistoryItemMessageLinks( this.historyItemDetailProviderRegistry, this.repository, message) ?? message; - const newLineIndex = message.indexOf('\n'); - const subject = newLineIndex !== -1 - ? `${truncate(message, newLineIndex, false)}` - : message; - const avatarUrl = commitAvatars?.get(commit.hash); const references = this._resolveHistoryItemRefs(commit); + const commands: Command[][] = [ + getHoverCommitHashCommands(Uri.file(this.repository.root), commit.hash), + processHoverRemoteCommands(remoteHoverCommands, commit.hash) + ]; + + const tooltip = getHistoryItemHover(avatarUrl, commit.authorName, commit.authorEmail, commit.authorDate ?? commit.commitDate, messageWithLinks, commit.shortStat, commands); + historyItems.push({ id: commit.hash, parentIds: commit.parents, - subject, + subject: subject(message), message: messageWithLinks, author: commit.authorName, authorEmail: commit.authorEmail, @@ -309,7 +322,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec displayId: truncate(commit.hash, this.commitShortHashLength, false), timestamp: commit.authorDate?.getTime(), statistics: commit.shortStat ?? { files: 0, insertions: 0, deletions: 0 }, - references: references.length !== 0 ? references : undefined + references: references.length !== 0 ? references : undefined, + tooltip } satisfies SourceControlHistoryItem); } @@ -325,7 +339,7 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec const historyItemChangesUri: Uri[] = []; const historyItemChanges: SourceControlHistoryItemChange[] = []; - const changes = await this.repository.diffTrees(historyItemParentId, historyItemId); + const changes = await this.repository.diffBetweenWithStats(historyItemParentId, historyItemId); for (const change of changes) { const historyItemUri = change.uri.with({ @@ -352,10 +366,64 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return historyItemChanges; } + async resolveHistoryItem(historyItemId: string, token: CancellationToken): Promise { + try { + const commit = await this.repository.getCommit(historyItemId); + + if (!commit || token.isCancellationRequested) { + return undefined; + } + + // Avatars + const avatarQuery = { + commits: [{ + hash: commit.hash, + authorName: commit.authorName, + authorEmail: commit.authorEmail + } satisfies AvatarQueryCommit], + size: 20 + } satisfies AvatarQuery; + + const commitAvatars = await provideSourceControlHistoryItemAvatar( + this.historyItemDetailProviderRegistry, this.repository, avatarQuery); + + await ensureEmojis(); + + const message = emojify(commit.message); + const messageWithLinks = await provideSourceControlHistoryItemMessageLinks( + this.historyItemDetailProviderRegistry, this.repository, message) ?? message; + + const newLineIndex = message.indexOf('\n'); + const subject = newLineIndex !== -1 + ? `${truncate(message, newLineIndex, false)}` + : message; + + const avatarUrl = commitAvatars?.get(commit.hash); + const references = this._resolveHistoryItemRefs(commit); + + return { + id: commit.hash, + parentIds: commit.parents, + subject, + message: messageWithLinks, + author: commit.authorName, + authorEmail: commit.authorEmail, + authorIcon: avatarUrl ? Uri.parse(avatarUrl) : new ThemeIcon('account'), + displayId: truncate(commit.hash, this.commitShortHashLength, false), + timestamp: commit.authorDate?.getTime(), + statistics: commit.shortStat ?? { files: 0, insertions: 0, deletions: 0 }, + references: references.length !== 0 ? references : undefined + } satisfies SourceControlHistoryItem; + } catch (err) { + this.logger.error(`[GitHistoryProvider][resolveHistoryItem] Failed to resolve history item '${historyItemId}': ${err}`); + return undefined; + } + } + async resolveHistoryItemChatContext(historyItemId: string): Promise { try { - const commitDetails = await this.repository.showCommit(historyItemId); - return commitDetails; + const changes = await this.repository.showChanges(historyItemId); + return changes; } catch (err) { this.logger.error(`[GitHistoryProvider][resolveHistoryItemChatContext] Failed to resolve history item '${historyItemId}': ${err}`); } @@ -363,6 +431,22 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return undefined; } + async resolveHistoryItemChangeRangeChatContext(historyItemId: string, historyItemParentId: string, path: string, token: CancellationToken): Promise { + try { + const changes = await this.repository.showChangesBetween(historyItemParentId, historyItemId, path); + + if (token.isCancellationRequested) { + return undefined; + } + + return `Output of git log -p ${historyItemParentId}..${historyItemId} -- ${path}:\n\n${changes}`; + } catch (err) { + this.logger.error(`[GitHistoryProvider][resolveHistoryItemChangeRangeChatContext] Failed to resolve history item change range '${historyItemId}' for '${path}': ${err}`); + } + + return undefined; + } + async resolveHistoryItemRefsCommonAncestor(historyItemRefs: string[]): Promise { try { if (historyItemRefs.length === 0) { diff --git a/extensions/git/src/hover.ts b/extensions/git/src/hover.ts new file mode 100644 index 0000000000000..7d33893a34880 --- /dev/null +++ b/extensions/git/src/hover.ts @@ -0,0 +1,161 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Command, l10n, MarkdownString, Uri } from 'vscode'; +import { fromNow, getCommitShortHash } from './util'; +import { emojify } from './emoji'; +import { CommitShortStat } from './git'; + +export const AVATAR_SIZE = 20; + +export function getCommitHover(authorAvatar: string | undefined, authorName: string | undefined, authorEmail: string | undefined, authorDate: Date | number | undefined, message: string, shortStats: CommitShortStat | undefined, commands: Command[][] | undefined): MarkdownString { + const markdownString = new MarkdownString('', true); + markdownString.isTrusted = { + enabledCommands: commands?.flat().map(c => c.command) ?? [] + }; + + // Author, Subject | Message (escape image syntax) + appendContent(markdownString, authorAvatar, authorName, authorEmail, authorDate, message); + + // Short stats + if (shortStats) { + appendShortStats(markdownString, shortStats); + } + + // Commands + if (commands && commands.length > 0) { + appendCommands(markdownString, commands); + } + + return markdownString; +} + +export function getHistoryItemHover(authorAvatar: string | undefined, authorName: string | undefined, authorEmail: string | undefined, authorDate: Date | number | undefined, message: string, shortStats: CommitShortStat | undefined, commands: Command[][] | undefined): MarkdownString[] { + const hoverContent: MarkdownString[] = []; + + // Author, Subject | Message (escape image syntax) + const authorMarkdownString = new MarkdownString('', true); + appendContent(authorMarkdownString, authorAvatar, authorName, authorEmail, authorDate, message); + hoverContent.push(authorMarkdownString); + + // Short stats + if (shortStats) { + const shortStatsMarkdownString = new MarkdownString('', true); + shortStatsMarkdownString.supportHtml = true; + appendShortStats(shortStatsMarkdownString, shortStats); + hoverContent.push(shortStatsMarkdownString); + } + + // Commands + if (commands && commands.length > 0) { + const commandsMarkdownString = new MarkdownString('', true); + commandsMarkdownString.isTrusted = { + enabledCommands: commands?.flat().map(c => c.command) ?? [] + }; + appendCommands(commandsMarkdownString, commands); + hoverContent.push(commandsMarkdownString); + } + + return hoverContent; +} + +function appendContent(markdownString: MarkdownString, authorAvatar: string | undefined, authorName: string | undefined, authorEmail: string | undefined, authorDate: Date | number | undefined, message: string): void { + // Author + if (authorName) { + // Avatar + if (authorAvatar) { + markdownString.appendMarkdown('!['); + markdownString.appendText(authorName); + markdownString.appendMarkdown(']('); + markdownString.appendText(authorAvatar); + markdownString.appendMarkdown(`|width=${AVATAR_SIZE},height=${AVATAR_SIZE})`); + } else { + markdownString.appendMarkdown('$(account)'); + } + + // Email + if (authorEmail) { + markdownString.appendMarkdown(' [**'); + markdownString.appendText(authorName); + markdownString.appendMarkdown('**](mailto:'); + markdownString.appendText(authorEmail); + markdownString.appendMarkdown(')'); + } else { + markdownString.appendMarkdown(' **'); + markdownString.appendText(authorName); + markdownString.appendMarkdown('**'); + } + + // Date + if (authorDate && !isNaN(new Date(authorDate).getTime())) { + const dateString = new Date(authorDate).toLocaleString(undefined, { + year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' + }); + + markdownString.appendMarkdown(', $(history)'); + markdownString.appendText(` ${fromNow(authorDate, true, true)} (${dateString})`); + } + + markdownString.appendMarkdown('\n\n'); + } + + // Subject | Message (escape image syntax) + markdownString.appendMarkdown(`${emojify(message.replace(/!\[/g, '![').replace(/\r\n|\r|\n/g, '\n\n'))}`); + markdownString.appendMarkdown(`\n\n---\n\n`); +} + +function appendShortStats(markdownString: MarkdownString, shortStats: { files: number; insertions: number; deletions: number }): void { + // Short stats + markdownString.appendMarkdown(`${shortStats.files === 1 ? + l10n.t('{0} file changed', shortStats.files) : + l10n.t('{0} files changed', shortStats.files)}`); + + if (shortStats.insertions) { + markdownString.appendMarkdown(`, ${shortStats.insertions === 1 ? + l10n.t('{0} insertion{1}', shortStats.insertions, '(+)') : + l10n.t('{0} insertions{1}', shortStats.insertions, '(+)')}`); + } + + if (shortStats.deletions) { + markdownString.appendMarkdown(`, ${shortStats.deletions === 1 ? + l10n.t('{0} deletion{1}', shortStats.deletions, '(-)') : + l10n.t('{0} deletions{1}', shortStats.deletions, '(-)')}`); + } + + markdownString.appendMarkdown(`\n\n---\n\n`); +} + +function appendCommands(markdownString: MarkdownString, commands: Command[][]): void { + for (let index = 0; index < commands.length; index++) { + if (index !== 0) { + markdownString.appendMarkdown('  |  '); + } + + const commandsMarkdown = commands[index] + .map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify(command.arguments))} "${command.tooltip}")`); + markdownString.appendMarkdown(commandsMarkdown.join(' ')); + } +} + +export function getHoverCommitHashCommands(documentUri: Uri, hash: string): Command[] { + return [{ + title: `$(git-commit) ${getCommitShortHash(documentUri, hash)}`, + tooltip: l10n.t('Open Commit'), + command: 'git.viewCommit', + arguments: [documentUri, hash, documentUri] + }, { + title: `$(copy)`, + tooltip: l10n.t('Copy Commit Hash'), + command: 'git.copyContentToClipboard', + arguments: [hash] + }] satisfies Command[]; +} + +export function processHoverRemoteCommands(commands: Command[], hash: string): Command[] { + return commands.map(command => ({ + ...command, + arguments: [...command.arguments ?? [], hash] + } satisfies Command)); +} diff --git a/extensions/git/src/ipc/ipcClient.ts b/extensions/git/src/ipc/ipcClient.ts index f623b3f7b6f60..9aab55e44a360 100644 --- a/extensions/git/src/ipc/ipcClient.ts +++ b/extensions/git/src/ipc/ipcClient.ts @@ -19,7 +19,7 @@ export class IPCClient { this.ipcHandlePath = ipcHandlePath; } - call(request: any): Promise { + call(request: unknown): Promise { const opts: http.RequestOptions = { socketPath: this.ipcHandlePath, path: `/${this.handlerName}`, diff --git a/extensions/git/src/ipc/ipcServer.ts b/extensions/git/src/ipc/ipcServer.ts index a7142fe22e12b..5e56f9ceef539 100644 --- a/extensions/git/src/ipc/ipcServer.ts +++ b/extensions/git/src/ipc/ipcServer.ts @@ -25,7 +25,7 @@ function getIPCHandlePath(id: string): string { } export interface IIPCHandler { - handle(request: any): Promise; + handle(request: unknown): Promise; } export async function createIPCServer(context?: string): Promise { diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 228e981f6cecc..535c0f2f30e5d 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -27,16 +27,17 @@ import { GitPostCommitCommandsProvider } from './postCommitCommands'; import { GitEditSessionIdentityProvider } from './editSessionIdentityProvider'; import { GitCommitInputBoxCodeActionsProvider, GitCommitInputBoxDiagnosticsManager } from './diagnostics'; import { GitBlameController } from './blame'; +import { CloneManager } from './cloneManager'; -const deactivateTasks: { (): Promise }[] = []; +const deactivateTasks: { (): Promise }[] = []; -export async function deactivate(): Promise { +export async function deactivate(): Promise { for (const task of deactivateTasks) { await task(); } } -async function createModel(context: ExtensionContext, logger: LogOutputChannel, telemetryReporter: TelemetryReporter, disposables: Disposable[]): Promise { +async function createModel(context: ExtensionContext, logger: LogOutputChannel, telemetryReporter: TelemetryReporter, disposables: Disposable[]): Promise<{ model: Model; cloneManager: CloneManager }> { const pathValue = workspace.getConfiguration('git').get('path'); let pathHints = Array.isArray(pathValue) ? pathValue : pathValue ? [pathValue] : []; @@ -84,12 +85,13 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, const git = new Git({ gitPath: info.path, - userAgent: `git/${info.version} (${(os as any).version?.() ?? os.type()} ${os.release()}; ${os.platform()} ${os.arch()}) vscode/${vscodeVersion} (${env.appName})`, + userAgent: `git/${info.version} (${os.version() ?? os.type()} ${os.release()}; ${os.platform()} ${os.arch()}) vscode/${vscodeVersion} (${env.appName})`, version: info.version, env: environment, }); const model = new Model(git, askpass, context.globalState, context.workspaceState, logger, telemetryReporter); disposables.push(model); + const cloneManager = new CloneManager(model, telemetryReporter, model.repositoryCache); const onRepository = () => commands.executeCommand('setContext', 'gitOpenRepositoryCount', `${model.repositories.length}`); model.onDidOpenRepository(onRepository, null, disposables); @@ -108,7 +110,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, git.onOutput.addListener('log', onOutput); disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput))); - const cc = new CommandCenter(git, model, context.globalState, logger, telemetryReporter); + const cc = new CommandCenter(git, model, context.globalState, logger, telemetryReporter, cloneManager); disposables.push( cc, new GitFileSystemProvider(model, logger), @@ -134,7 +136,7 @@ async function createModel(context: ExtensionContext, logger: LogOutputChannel, checkGitVersion(info); commands.executeCommand('setContext', 'gitVersion2.35', git.compareGitVersionTo('2.35') >= 0); - return model; + return { model, cloneManager }; } async function isGitRepository(folder: WorkspaceFolder): Promise { @@ -210,13 +212,18 @@ export async function _activate(context: ExtensionContext): Promise workspace.getConfiguration('git', null).get('enabled') === true); const result = new GitExtensionImpl(); - eventToPromise(onEnabled).then(async () => result.model = await createModel(context, logger, telemetryReporter, disposables)); + eventToPromise(onEnabled).then(async () => { + const { model, cloneManager } = await createModel(context, logger, telemetryReporter, disposables); + result.model = model; + result.cloneManager = cloneManager; + }); return result; } try { - const model = await createModel(context, logger, telemetryReporter, disposables); - return new GitExtensionImpl(model); + const { model, cloneManager } = await createModel(context, logger, telemetryReporter, disposables); + + return new GitExtensionImpl({ model, cloneManager }); } catch (err) { console.warn(err.message); logger.warn(`[main] Failed to create model: ${err}`); diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index a199e010be6e6..6600e22c121ca 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -20,6 +20,7 @@ import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { IPostCommitCommandsProviderRegistry } from './postCommitCommands'; import { IBranchProtectionProviderRegistry } from './branchProtection'; import { ISourceControlHistoryItemDetailsProviderRegistry } from './historyItemDetailsProvider'; +import { RepositoryCache } from './repositoryCache'; class RepositoryPick implements QuickPickItem { @memoize get label(): string { @@ -226,7 +227,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi return Promise.resolve(); } - return eventToPromise(filterEvent(this.onDidChangeState, s => s === 'initialized')) as Promise; + return eventToPromise(filterEvent(this.onDidChangeState, s => s === 'initialized') as Event) as Promise; } private remoteSourcePublishers = new Set(); @@ -275,9 +276,14 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi */ private _workspaceFolders = new Map(); + private readonly _repositoryCache: RepositoryCache; + get repositoryCache(): RepositoryCache { + return this._repositoryCache; + } + private disposables: Disposable[] = []; - constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, readonly workspaceState: Memento, private logger: LogOutputChannel, private telemetryReporter: TelemetryReporter) { + constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, readonly workspaceState: Memento, private logger: LogOutputChannel, private readonly telemetryReporter: TelemetryReporter) { // Repositories managers this._closedRepositoriesManager = new ClosedRepositoriesManager(workspaceState); this._parentRepositoriesManager = new ParentRepositoriesManager(globalState); @@ -298,6 +304,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi this.setState('uninitialized'); this.doInitialScan().finally(() => this.setState('initialized')); + this._repositoryCache = new RepositoryCache(globalState, logger); } private async doInitialScan(): Promise { @@ -450,7 +457,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi @debounce(500) private eventuallyScanPossibleGitRepositories(): void { for (const path of this.possibleGitRepositoryPaths) { - this.openRepository(path, false, true); + this.openRepository(path); } this.possibleGitRepositoryPaths.clear(); @@ -539,6 +546,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi if (textEditor === undefined) { commands.executeCommand('setContext', 'git.activeResourceHasUnstagedChanges', false); commands.executeCommand('setContext', 'git.activeResourceHasStagedChanges', false); + commands.executeCommand('setContext', 'git.activeResourceHasMergeConflicts', false); return; } @@ -546,6 +554,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi if (!repository) { commands.executeCommand('setContext', 'git.activeResourceHasUnstagedChanges', false); commands.executeCommand('setContext', 'git.activeResourceHasStagedChanges', false); + commands.executeCommand('setContext', 'git.activeResourceHasMergeConflicts', false); return; } @@ -553,13 +562,17 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi .find(resource => pathEquals(resource.resourceUri.fsPath, textEditor.document.uri.fsPath)); const workingTreeResource = repository.workingTreeGroup.resourceStates .find(resource => pathEquals(resource.resourceUri.fsPath, textEditor.document.uri.fsPath)); + const mergeChangesResource = repository.mergeGroup.resourceStates + .find(resource => pathEquals(resource.resourceUri.fsPath, textEditor.document.uri.fsPath)); + const hasMergeConflicts = mergeChangesResource ? /^(<{7,}|={7,}|>{7,})/m.test(textEditor.document.getText()) : false; commands.executeCommand('setContext', 'git.activeResourceHasStagedChanges', indexResource !== undefined); commands.executeCommand('setContext', 'git.activeResourceHasUnstagedChanges', workingTreeResource !== undefined); + commands.executeCommand('setContext', 'git.activeResourceHasMergeConflicts', hasMergeConflicts); } @sequentialize - async openRepository(repoPath: string, openIfClosed = false, openIfParent = false): Promise { + async openRepository(repoPath: string, openIfClosed = false): Promise { this.logger.trace(`[Model][openRepository] Repository: ${repoPath}`); const existingRepository = await this.getRepositoryExact(repoPath); if (existingRepository) { @@ -608,7 +621,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi const parentRepositoryConfig = config.get<'always' | 'never' | 'prompt'>('openRepositoryInParentFolders', 'prompt'); if (parentRepositoryConfig !== 'always' && this.globalState.get(`parentRepository:${repositoryRoot}`) !== true) { const isRepositoryOutsideWorkspace = await this.isRepositoryOutsideWorkspace(repositoryRoot); - if (!openIfParent && isRepositoryOutsideWorkspace) { + if (isRepositoryOutsideWorkspace) { this.logger.trace(`[Model][openRepository] Repository in parent folder: ${repositoryRoot}`); if (!this._parentRepositoriesManager.hasRepository(repositoryRoot)) { @@ -647,7 +660,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi // Open repository const [dotGit, repositoryRootRealPath] = await Promise.all([this.git.getRepositoryDotGit(repositoryRoot), this.getRepositoryRootRealPath(repositoryRoot)]); const gitRepository = this.git.open(repositoryRoot, repositoryRootRealPath, dotGit, this.logger); - const repository = new Repository(gitRepository, this, this, this, this, this, this, this.globalState, this.logger, this.telemetryReporter); + const repository = new Repository(gitRepository, this, this, this, this, this, this, this.globalState, this.logger, this.telemetryReporter, this._repositoryCache); this.open(repository); this._closedRepositoriesManager.deleteRepository(repository.root); @@ -658,7 +671,9 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi // Do not await this, we want SCM // to know about the repo asap - repository.status(); + repository.status().then(() => { + this._repositoryCache.update(repository.remotes, [], repository.root); + }); } catch (err) { // noop this.logger.trace(`[Model][openRepository] Opening repository for path='${repoPath}' failed. Error:${err}`); @@ -820,7 +835,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi commands.executeCommand('setContext', 'operationInProgress', operationInProgress); }; - const operationEvent = anyEvent(repository.onDidRunOperation as Event, repository.onRunOperation as Event); + const operationEvent = anyEvent(repository.onDidRunOperation as Event, repository.onRunOperation as Event); const operationListener = operationEvent(() => updateOperationInProgressContext()); updateOperationInProgressContext(); @@ -852,7 +867,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi this.logger.info(`[Model][close] Repository: ${repository.root}`); this._closedRepositoriesManager.addRepository(openRepository.repository.root); - + this._repositoryCache.update(repository.remotes, [], repository.root); openRepository.dispose(); } @@ -886,11 +901,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi return pick && pick.repository; } - getRepository(sourceControl: SourceControl): Repository | undefined; - getRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined; - getRepository(path: string): Repository | undefined; - getRepository(resource: Uri): Repository | undefined; - getRepository(hint: any): Repository | undefined { + getRepository(hint: SourceControl | SourceControlResourceGroup | Uri | string): Repository | undefined { const liveRepository = this.getOpenRepository(hint); return liveRepository && liveRepository.repository; } @@ -917,12 +928,7 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi } } - private getOpenRepository(repository: Repository): OpenRepository | undefined; - private getOpenRepository(sourceControl: SourceControl): OpenRepository | undefined; - private getOpenRepository(resourceGroup: SourceControlResourceGroup): OpenRepository | undefined; - private getOpenRepository(path: string): OpenRepository | undefined; - private getOpenRepository(resource: Uri): OpenRepository | undefined; - private getOpenRepository(hint: any): OpenRepository | undefined { + private getOpenRepository(hint: SourceControl | SourceControlResourceGroup | Repository | Uri | string): OpenRepository | undefined { if (!hint) { return undefined; } @@ -1088,6 +1094,14 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi return true; } + // The repository path may be a worktree (usually stored outside the workspace) so we have + // to check the repository path against all the worktree paths of the repositories that have + // already been opened. + const worktreePaths = this.repositories.map(r => r.worktrees.map(w => w.path)).flat(); + if (worktreePaths.some(p => pathEquals(p, repositoryPath))) { + return false; + } + // The repository path may be a canonical path or it may contain a symbolic link so we have // to match it against the workspace folders and the canonical paths of the workspace folders const workspaceFolderPaths = new Set([ @@ -1165,16 +1179,6 @@ export class Model implements IRepositoryResolver, IBranchProtectionProviderRegi } } - disposeRepository(repository: Repository): void { - const openRepository = this.getOpenRepository(repository); - if (!openRepository) { - return; - } - - this.logger.info(`[Model][disposeRepository] Repository: ${repository.root}`); - openRepository.dispose(); - } - dispose(): void { const openRepositories = [...this.openRepositories]; openRepositories.forEach(r => r.dispose()); diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index eaa91d4a0471e..96fffa4dc8771 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -32,7 +32,6 @@ export const enum OperationKind { GetObjectDetails = 'GetObjectDetails', GetObjectFiles = 'GetObjectFiles', GetRefs = 'GetRefs', - GetWorktrees = 'GetWorktrees', GetRemoteRefs = 'GetRemoteRefs', HashObject = 'HashObject', Ignore = 'Ignore', @@ -69,8 +68,8 @@ export const enum OperationKind { export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchOperation | CheckIgnoreOperation | CherryPickOperation | CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation | - DeleteRefOperation | DeleteRemoteRefOperation | DeleteTagOperation | DeleteWorktreeOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation | - GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetObjectFilesOperation | GetRefsOperation | GetWorktreesOperation | + DeleteRefOperation | DeleteRemoteRefOperation | DeleteTagOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation | + GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetObjectFilesOperation | GetRefsOperation | GetRemoteRefsOperation | HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation | MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | RenameBranchOperation | RemoveOperation | ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RefreshOperation | RevertFilesOperation | @@ -93,7 +92,6 @@ export type DeleteBranchOperation = BaseOperation & { kind: OperationKind.Delete export type DeleteRefOperation = BaseOperation & { kind: OperationKind.DeleteRef }; export type DeleteRemoteRefOperation = BaseOperation & { kind: OperationKind.DeleteRemoteRef }; export type DeleteTagOperation = BaseOperation & { kind: OperationKind.DeleteTag }; -export type DeleteWorktreeOperation = BaseOperation & { kind: OperationKind.DeleteWorktree }; export type DiffOperation = BaseOperation & { kind: OperationKind.Diff }; export type FetchOperation = BaseOperation & { kind: OperationKind.Fetch }; export type FindTrackingBranchesOperation = BaseOperation & { kind: OperationKind.FindTrackingBranches }; @@ -103,7 +101,6 @@ export type GetCommitTemplateOperation = BaseOperation & { kind: OperationKind.G export type GetObjectDetailsOperation = BaseOperation & { kind: OperationKind.GetObjectDetails }; export type GetObjectFilesOperation = BaseOperation & { kind: OperationKind.GetObjectFiles }; export type GetRefsOperation = BaseOperation & { kind: OperationKind.GetRefs }; -export type GetWorktreesOperation = BaseOperation & { kind: OperationKind.GetWorktrees }; export type GetRemoteRefsOperation = BaseOperation & { kind: OperationKind.GetRemoteRefs }; export type HashObjectOperation = BaseOperation & { kind: OperationKind.HashObject }; export type IgnoreOperation = BaseOperation & { kind: OperationKind.Ignore }; @@ -153,7 +150,6 @@ export const Operation = { DeleteRef: { kind: OperationKind.DeleteRef, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteRefOperation, DeleteRemoteRef: { kind: OperationKind.DeleteRemoteRef, blocking: false, readOnly: false, remote: true, retry: false, showProgress: true } as DeleteRemoteRefOperation, DeleteTag: { kind: OperationKind.DeleteTag, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteTagOperation, - DeleteWorktree: { kind: OperationKind.DeleteWorktree, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteWorktreeOperation, Diff: { kind: OperationKind.Diff, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as DiffOperation, Fetch: (showProgress: boolean) => ({ kind: OperationKind.Fetch, blocking: false, readOnly: false, remote: true, retry: true, showProgress } as FetchOperation), FindTrackingBranches: { kind: OperationKind.FindTrackingBranches, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as FindTrackingBranchesOperation, @@ -163,7 +159,6 @@ export const Operation = { GetObjectDetails: { kind: OperationKind.GetObjectDetails, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetObjectDetailsOperation, GetObjectFiles: { kind: OperationKind.GetObjectFiles, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetObjectFilesOperation, GetRefs: { kind: OperationKind.GetRefs, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetRefsOperation, - GetWorktrees: { kind: OperationKind.GetWorktrees, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetWorktreesOperation, GetRemoteRefs: { kind: OperationKind.GetRemoteRefs, blocking: false, readOnly: true, remote: true, retry: false, showProgress: false } as GetRemoteRefsOperation, HashObject: { kind: OperationKind.HashObject, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as HashObjectOperation, Ignore: { kind: OperationKind.Ignore, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as IgnoreOperation, @@ -191,16 +186,16 @@ export const Operation = { Show: { kind: OperationKind.Show, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as ShowOperation, Stage: { kind: OperationKind.Stage, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as StageOperation, Status: { kind: OperationKind.Status, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as StatusOperation, - Stash: { kind: OperationKind.Stash, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as StashOperation, + Stash: (readOnly: boolean) => ({ kind: OperationKind.Stash, blocking: false, readOnly, remote: false, retry: false, showProgress: true } as StashOperation), SubmoduleUpdate: { kind: OperationKind.SubmoduleUpdate, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as SubmoduleUpdateOperation, Sync: { kind: OperationKind.Sync, blocking: true, readOnly: false, remote: true, retry: true, showProgress: true } as SyncOperation, Tag: { kind: OperationKind.Tag, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as TagOperation, - Worktree: { kind: OperationKind.Worktree, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as WorktreeOperation + Worktree: (readOnly: boolean) => ({ kind: OperationKind.Worktree, blocking: false, readOnly, remote: false, retry: false, showProgress: true } as WorktreeOperation) }; export interface OperationResult { operation: Operation; - error: any; + error: unknown; } interface IOperationManager { diff --git a/extensions/git/src/protocolHandler.ts b/extensions/git/src/protocolHandler.ts index 90491fecd5067..42289abcb9cd8 100644 --- a/extensions/git/src/protocolHandler.ts +++ b/extensions/git/src/protocolHandler.ts @@ -21,6 +21,7 @@ export class GitProtocolHandler implements UriHandler { this.disposables.push(window.registerUriHandler(this)); } + // example code-oss://vscode.git/clone?url=https://github.com/microsoft/vscode handleUri(uri: Uri): void { this.logger.info(`[GitProtocolHandler][handleUri] URI:(${uri.toString()})`); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 084acdd70d477..7977887182f30 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -10,11 +10,11 @@ import picomatch from 'picomatch'; import { CancellationError, CancellationToken, CancellationTokenSource, Command, commands, Disposable, Event, EventEmitter, FileDecoration, FileType, l10n, LogLevel, LogOutputChannel, Memento, ProgressLocation, ProgressOptions, QuickDiffProvider, RelativePattern, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, TabInputNotebookDiff, TabInputTextDiff, TabInputTextMultiDiff, ThemeColor, ThemeIcon, Uri, window, workspace, WorkspaceEdit } from 'vscode'; import { ActionButton } from './actionButton'; import { ApiRepository } from './api/api1'; -import { Branch, BranchQuery, Change, CommitOptions, FetchOptions, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status } from './api/git'; +import { Branch, BranchQuery, Change, CommitOptions, DiffChange, FetchOptions, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status } from './api/git'; import { AutoFetcher } from './autofetch'; import { GitBranchProtectionProvider, IBranchProtectionProviderRegistry } from './branchProtection'; import { debounce, memoize, sequentialize, throttle } from './decorators'; -import { Repository as BaseRepository, BlameInformation, Commit, GitError, LogFileOptions, LsTreeElement, PullOptions, RefQuery, Stash, Submodule, Worktree } from './git'; +import { Repository as BaseRepository, BlameInformation, Commit, CommitShortStat, GitError, LogFileOptions, LsTreeElement, PullOptions, RefQuery, Stash, Submodule, Worktree } from './git'; import { GitHistoryProvider } from './historyProvider'; import { Operation, OperationKind, OperationManager, OperationResult } from './operation'; import { CommitCommandsCenter, IPostCommitCommandsProviderRegistry } from './postCommitCommands'; @@ -22,9 +22,11 @@ import { IPushErrorHandlerRegistry } from './pushError'; import { IRemoteSourcePublisherRegistry } from './remotePublisher'; import { StatusBarCommands } from './statusbar'; import { toGitUri } from './uri'; -import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, getCommitShortHash, IDisposable, isDescendant, isLinuxSnap, isRemote, isWindows, Limiter, onceEvent, pathEquals, relativePath } from './util'; +import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, getCommitShortHash, IDisposable, isCopilotWorktree, isDescendant, isLinuxSnap, isRemote, isWindows, Limiter, onceEvent, pathEquals, relativePath } from './util'; import { IFileWatcher, watch } from './watch'; import { ISourceControlHistoryItemDetailsProviderRegistry } from './historyItemDetailsProvider'; +import { GitArtifactProvider } from './artifactProvider'; +import { RepositoryCache } from './repositoryCache'; const timeout = (millis: number) => new Promise(c => setTimeout(c, millis)); @@ -182,8 +184,9 @@ export class Resource implements SourceControlResourceState { get type(): Status { return this._type; } get original(): Uri { return this._resourceUri; } get renameResourceUri(): Uri | undefined { return this._renameResourceUri; } + get contextValue(): string | undefined { return this._repositoryKind; } - private static Icons: any = { + private static Icons = { light: { Modified: getIconUri('status-modified', 'light'), Added: getIconUri('status-added', 'light'), @@ -208,7 +211,7 @@ export class Resource implements SourceControlResourceState { } }; - private getIconPath(theme: string): Uri { + private getIconPath(theme: 'light' | 'dark'): Uri { switch (this.type) { case Status.INDEX_MODIFIED: return Resource.Icons[theme].Modified; case Status.MODIFIED: return Resource.Icons[theme].Modified; @@ -310,6 +313,7 @@ export class Resource implements SourceControlResourceState { private _type: Status, private _useIcons: boolean, private _renameResourceUri?: Uri, + private _repositoryKind?: 'repository' | 'submodule' | 'worktree', ) { } async open(): Promise { @@ -327,8 +331,13 @@ export class Resource implements SourceControlResourceState { await commands.executeCommand(command.command, ...(command.arguments || [])); } + async compareWithWorkspace(): Promise { + const command = this._commandResolver.resolveCompareWithWorkspaceCommand(this); + await commands.executeCommand(command.command, ...(command.arguments || [])); + } + clone(resourceGroupType?: ResourceGroupType) { - return new Resource(this._commandResolver, resourceGroupType ?? this._resourceGroupType, this._resourceUri, this._type, this._useIcons, this._renameResourceUri); + return new Resource(this._commandResolver, resourceGroupType ?? this._resourceGroupType, this._resourceUri, this._type, this._useIcons, this._renameResourceUri, this._repositoryKind); } } @@ -506,14 +515,19 @@ class ResourceCommandResolver { }; } - resolveChangeCommand(resource: Resource): Command { + resolveChangeCommand(resource: Resource, compareWithWorkspace?: boolean, leftUri?: Uri): Command { + if (!compareWithWorkspace) { + leftUri = resource.leftUri; + } + const title = this.getTitle(resource); - if (!resource.leftUri) { + if (!leftUri) { const bothModified = resource.type === Status.BOTH_MODIFIED; if (resource.rightUri && workspace.getConfiguration('git').get('mergeEditor', false) && (bothModified || resource.type === Status.BOTH_ADDED)) { + const command = this.repository.isWorktreeMigrating ? 'git.openWorktreeMergeEditor' : 'git.openMergeEditor'; return { - command: 'git.openMergeEditor', + command, title: l10n.t('Open Merge'), arguments: [resource.rightUri] }; @@ -528,11 +542,26 @@ class ResourceCommandResolver { return { command: 'vscode.diff', title: l10n.t('Open'), - arguments: [resource.leftUri, resource.rightUri, title] + arguments: [leftUri, resource.rightUri, title] }; } } + resolveCompareWithWorkspaceCommand(resource: Resource): Command { + // Resource is not a worktree + if (!this.repository.dotGit.commonPath) { + return this.resolveChangeCommand(resource); + } + + const parentRepoRoot = path.dirname(this.repository.dotGit.commonPath); + const relPath = path.relative(this.repository.root, resource.resourceUri.fsPath); + const candidateFsPath = path.join(parentRepoRoot, relPath); + + const leftUri = fs.existsSync(candidateFsPath) ? Uri.file(candidateFsPath) : undefined; + + return this.resolveChangeCommand(resource, true, leftUri); + } + getResources(resource: Resource): { left: Uri | undefined; right: Uri | undefined; original: Uri | undefined; modified: Uri | undefined } { for (const submodule of this.repository.submodules) { if (path.join(this.repository.root, submodule.path) === resource.resourceUri.fsPath) { @@ -663,14 +692,11 @@ interface BranchProtectionMatcher { } export interface IRepositoryResolver { - getRepository(sourceControl: SourceControl): Repository | undefined; - getRepository(resourceGroup: SourceControlResourceGroup): Repository | undefined; - getRepository(path: string): Repository | undefined; - getRepository(resource: Uri): Repository | undefined; - getRepository(hint: any): Repository | undefined; + getRepository(hint: SourceControl | SourceControlResourceGroup | Uri | string): Repository | undefined; } export class Repository implements Disposable { + static readonly WORKTREE_ROOT_STORAGE_KEY = 'worktreeRoot'; private _onDidChangeRepository = new EventEmitter(); readonly onDidChangeRepository: Event = this._onDidChangeRepository.event; @@ -695,7 +721,9 @@ export class Repository implements Disposable { @memoize get onDidChangeOperations(): Event { - return anyEvent(this.onRunOperation as Event, this.onDidRunOperation as Event); + return anyEvent( + this.onRunOperation as Event, + this.onDidRunOperation as Event) as Event; } private _sourceControl: SourceControl; @@ -807,6 +835,10 @@ export class Repository implements Disposable { return this._cherryPickInProgress; } + private _isWorktreeMigrating: boolean = false; + get isWorktreeMigrating(): boolean { return this._isWorktreeMigrating; } + set isWorktreeMigrating(value: boolean) { this._isWorktreeMigrating = value; } + private readonly _operations: OperationManager; get operations(): OperationManager { return this._operations; } @@ -841,6 +873,9 @@ export class Repository implements Disposable { return this.repository.kind; } + private _artifactProvider: GitArtifactProvider; + get artifactProvider(): GitArtifactProvider { return this._artifactProvider; } + private _historyProvider: GitHistoryProvider; get historyProvider(): GitHistoryProvider { return this._historyProvider; } @@ -862,9 +897,10 @@ export class Repository implements Disposable { postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry, private readonly branchProtectionProviderRegistry: IBranchProtectionProviderRegistry, historyItemDetailProviderRegistry: ISourceControlHistoryItemDetailsProviderRegistry, - globalState: Memento, + private readonly globalState: Memento, private readonly logger: LogOutputChannel, - private telemetryReporter: TelemetryReporter + private telemetryReporter: TelemetryReporter, + private readonly repositoryCache: RepositoryCache ) { this._operations = new OperationManager(this.logger); @@ -903,17 +939,28 @@ export class Repository implements Disposable { : repository.kind === 'worktree' && repository.dotGit.commonPath ? path.dirname(repository.dotGit.commonPath) : undefined; - const parent = this.repositoryResolver.getRepository(parentRoot)?.sourceControl; + const parent = parentRoot + ? this.repositoryResolver.getRepository(parentRoot)?.sourceControl + : undefined; // Icon const icon = repository.kind === 'submodule' ? new ThemeIcon('archive') : repository.kind === 'worktree' - ? new ThemeIcon('list-tree') + ? isCopilotWorktree(repository.root) + ? new ThemeIcon('chat-sparkle') + : new ThemeIcon('worktree') : new ThemeIcon('repo'); + // Hidden + // This is a temporary solution to hide worktrees created by Copilot + // when the main repository is opened. Users can still manually open + // the worktree from the Repositories view. + const hidden = repository.kind === 'worktree' && + isCopilotWorktree(repository.root) && parent !== undefined; + const root = Uri.file(repository.root); - this._sourceControl = scm.createSourceControl('git', 'Git', root, icon, parent); + this._sourceControl = scm.createSourceControl('git', 'Git', root, icon, hidden, parent); this._sourceControl.contextValue = repository.kind; this._sourceControl.quickDiffProvider = this; @@ -923,6 +970,10 @@ export class Repository implements Disposable { this._sourceControl.historyProvider = this._historyProvider; this.disposables.push(this._historyProvider); + this._artifactProvider = new GitArtifactProvider(this, logger); + this._sourceControl.artifactProvider = this._artifactProvider; + this.disposables.push(this._artifactProvider); + this._sourceControl.acceptInputCommand = { command: 'git.commit', title: l10n.t('Commit'), arguments: [this._sourceControl] }; this._sourceControl.inputBox.validateInput = this.validateInput.bind(this); @@ -1085,17 +1136,10 @@ export class Repository implements Disposable { return undefined; } - const activeTabInput = window.tabGroups.activeTabGroup.activeTab?.input; - - // Ignore file that is on the right-hand side of a diff editor - if (activeTabInput instanceof TabInputTextDiff && pathEquals(activeTabInput.modified.fsPath, uri.fsPath)) { - this.logger.trace(`[Repository][provideOriginalResource] Resource is on the right-hand side of a diff editor: ${uri.toString()}`); - return undefined; - } - - // Ignore file that is on the right -hand side of a multi-file diff editor - if (activeTabInput instanceof TabInputTextMultiDiff && activeTabInput.textDiffs.some(diff => pathEquals(diff.modified.fsPath, uri.fsPath))) { - this.logger.trace(`[Repository][provideOriginalResource] Resource is on the right-hand side of a multi-file diff editor: ${uri.toString()}`); + // Ignore path that is git ignored + const ignored = await this.checkIgnore([uri.fsPath]); + if (ignored.size > 0) { + this.logger.trace(`[Repository][provideOriginalResource] Resource is git ignored: ${uri.toString()}`); return undefined; } @@ -1166,6 +1210,10 @@ export class Repository implements Disposable { return this.run(Operation.Diff, () => this.repository.diffWithHEAD(path)); } + diffWithHEADShortStats(path?: string): Promise { + return this.run(Operation.Diff, () => this.repository.diffWithHEADShortStats(path)); + } + diffWith(ref: string): Promise; diffWith(ref: string, path: string): Promise; diffWith(ref: string, path?: string | undefined): Promise; @@ -1180,6 +1228,10 @@ export class Repository implements Disposable { return this.run(Operation.Diff, () => this.repository.diffIndexWithHEAD(path)); } + diffIndexWithHEADShortStats(path?: string): Promise { + return this.run(Operation.Diff, () => this.repository.diffIndexWithHEADShortStats(path)); + } + diffIndexWith(ref: string): Promise; diffIndexWith(ref: string, path: string): Promise; diffIndexWith(ref: string, path?: string | undefined): Promise; @@ -1198,11 +1250,21 @@ export class Repository implements Disposable { return this.run(Operation.Diff, () => this.repository.diffBetween(ref1, ref2, path)); } - diffBetweenShortStat(ref1: string, ref2: string): Promise<{ files: number; insertions: number; deletions: number }> { - return this.run(Operation.Diff, () => this.repository.diffBetweenShortStat(ref1, ref2)); + diffBetweenWithStats(ref1: string, ref2: string, path?: string): Promise { + if (ref1 === this._EMPTY_TREE) { + // Use git diff-tree to get the + // changes in the first commit + return this.diffTrees(ref1, ref2); + } + + const scopedConfig = workspace.getConfiguration('git', Uri.file(this.root)); + const similarityThreshold = scopedConfig.get('similarityThreshold', 50); + + return this.run(Operation.Diff, () => + this.repository.diffBetweenWithStats(`${ref1}...${ref2}`, { path, similarityThreshold })); } - diffTrees(treeish1: string, treeish2?: string): Promise { + diffTrees(treeish1: string, treeish2?: string): Promise { const scopedConfig = workspace.getConfiguration('git', Uri.file(this.root)); const similarityThreshold = scopedConfig.get('similarityThreshold', 50); @@ -1223,6 +1285,9 @@ export class Repository implements Disposable { async () => { await this.repository.add(resources.map(r => r.fsPath), opts); this.closeDiffEditors([], [...resources.map(r => r.fsPath)]); + + // Accept working set changes across all chat sessions + commands.executeCommand('_chat.editSessions.accept', resources); }, () => { const resourcePaths = resources.map(r => r.fsPath); @@ -1373,6 +1438,12 @@ export class Repository implements Disposable { this.inputBox.value = await this.getInputTemplate(); } this.closeDiffEditors(indexResources, workingGroupResources); + + // Accept working set changes across all chat sessions + const resources = indexResources.length !== 0 + ? indexResources.map(r => Uri.file(r)) + : workingGroupResources.map(r => Uri.file(r)); + commands.executeCommand('_chat.editSessions.accept', resources); } private commitOperationGetOptimisticResourceGroups(opts: CommitOptions): GitResourceGroups { @@ -1699,7 +1770,37 @@ export class Repository implements Disposable { } async getWorktrees(): Promise { - return await this.run(Operation.GetWorktrees, () => this.repository.getWorktrees()); + return await this.run(Operation.Worktree(true), () => this.repository.getWorktrees()); + } + + async getWorktreeDetails(): Promise { + return this.run(Operation.Worktree(true), async () => { + const worktrees = await this.repository.getWorktrees(); + if (worktrees.length === 0) { + return []; + } + + // Get refs for worktrees that point to a ref + const worktreeRefs = worktrees + .filter(worktree => !worktree.detached) + .map(worktree => worktree.ref); + + // Get the commit details for worktrees that point to a ref + const refs = await this.getRefs({ pattern: worktreeRefs, includeCommitDetails: true }); + + // Get the commit details for detached worktrees + const commits = await Promise.all(worktrees + .filter(worktree => worktree.detached) + .map(worktree => this.repository.getCommit(worktree.ref))); + + return worktrees.map(worktree => { + const commitDetails = worktree.detached + ? commits.find(commit => commit.hash === worktree.ref) + : refs.find(ref => `refs/heads/${ref.name}` === worktree.ref)?.commitDetails; + + return { ...worktree, commitDetails } satisfies Worktree; + }); + }); } async getRemoteRefs(remote: string, opts?: { heads?: boolean; tags?: boolean }): Promise { @@ -1730,12 +1831,74 @@ export class Repository implements Disposable { await this.run(Operation.DeleteTag, () => this.repository.deleteTag(name)); } - async addWorktree(options: { path: string; commitish: string; branch?: string }): Promise { - await this.run(Operation.Worktree, () => this.repository.addWorktree(options)); + async createWorktree(options?: { path?: string; commitish?: string; branch?: string }): Promise { + const defaultWorktreeRoot = this.globalState.get(`${Repository.WORKTREE_ROOT_STORAGE_KEY}:${this.root}`); + const config = workspace.getConfiguration('git', Uri.file(this.root)); + const branchPrefix = config.get('branchPrefix', ''); + + return await this.run(Operation.Worktree(false), async () => { + let worktreeName: string | undefined; + let { path: worktreePath, commitish, branch } = options || {}; + + // Create worktree path based on the branch name + if (worktreePath === undefined && branch !== undefined) { + worktreeName = branch.startsWith(branchPrefix) + ? branch.substring(branchPrefix.length).replace(/\//g, '-') + : branch.replace(/\//g, '-'); + + worktreePath = defaultWorktreeRoot + ? path.join(defaultWorktreeRoot, worktreeName) + : path.join(path.dirname(this.root), `${path.basename(this.root)}.worktrees`, worktreeName); + } + + // Ensure that the worktree path is unique + if (this.worktrees.some(worktree => pathEquals(path.normalize(worktree.path), path.normalize(worktreePath!)))) { + let counter = 0, uniqueWorktreePath: string; + do { + uniqueWorktreePath = `${worktreePath}-${++counter}`; + } while (this.worktrees.some(wt => pathEquals(path.normalize(wt.path), path.normalize(uniqueWorktreePath)))); + + worktreePath = uniqueWorktreePath; + } + + // Create the worktree + await this.repository.addWorktree({ path: worktreePath!, commitish: commitish ?? 'HEAD', branch }); + + // Update worktree root in global state + const newWorktreeRoot = path.dirname(worktreePath!); + if (defaultWorktreeRoot && !pathEquals(newWorktreeRoot, defaultWorktreeRoot)) { + this.globalState.update(`${Repository.WORKTREE_ROOT_STORAGE_KEY}:${this.root}`, newWorktreeRoot); + } + + return worktreePath!; + }); } async deleteWorktree(path: string, options?: { force?: boolean }): Promise { - await this.run(Operation.DeleteWorktree, () => this.repository.deleteWorktree(path, options)); + await this.run(Operation.Worktree(false), async () => { + const worktree = this.repositoryResolver.getRepository(path); + + const deleteWorktree = async (options?: { force?: boolean }): Promise => { + await this.repository.deleteWorktree(path, options); + worktree?.dispose(); + }; + + try { + await deleteWorktree(); + } catch (err) { + if (err.gitErrorCode === GitErrorCodes.WorktreeContainsChanges) { + const forceDelete = l10n.t('Force Delete'); + const message = l10n.t('The worktree contains modified or untracked files. Do you want to force delete?'); + const choice = await window.showWarningMessage(message, { modal: true }, forceDelete); + if (choice === forceDelete) { + await deleteWorktree({ ...options, force: true }); + } + return; + } + + throw err; + } + }); } async deleteRemoteRef(remoteName: string, refName: string, options?: { force?: boolean }): Promise { @@ -1773,8 +1936,12 @@ export class Repository implements Disposable { return await this.repository.getCommit(ref); } - async showCommit(ref: string): Promise { - return await this.run(Operation.Show, () => this.repository.showCommit(ref)); + async showChanges(ref: string): Promise { + return await this.run(Operation.Log(false), () => this.repository.showChanges(ref)); + } + + async showChangesBetween(ref1: string, ref2: string, path?: string): Promise { + return await this.run(Operation.Log(false), () => this.repository.showChangesBetween(ref1, ref2, path)); } async getEmptyTree(): Promise { @@ -1803,11 +1970,23 @@ export class Repository implements Disposable { } async addRemote(name: string, url: string): Promise { - await this.run(Operation.Remote, () => this.repository.addRemote(name, url)); + await this.run(Operation.Remote, async () => { + const result = await this.repository.addRemote(name, url); + this.repositoryCache.update(this.remotes, [], this.root); + return result; + }); } async removeRemote(name: string): Promise { - await this.run(Operation.Remote, () => this.repository.removeRemote(name)); + await this.run(Operation.Remote, async () => { + const result = this.repository.removeRemote(name); + const remote = this.remotes.find(remote => remote.name === name); + if (remote) { + this.repositoryCache.update([], [remote], this.root); + } + return result; + }); + } async renameRemote(name: string, newName: string): Promise { @@ -1935,7 +2114,11 @@ export class Repository implements Disposable { } async blame2(path: string, ref?: string): Promise { - return await this.run(Operation.Blame(false), () => this.repository.blame2(path, ref)); + return await this.run(Operation.Blame(false), () => { + const config = workspace.getConfiguration('git', Uri.file(this.root)); + const ignoreWhitespace = config.get('blame.ignoreWhitespace', false); + return this.repository.blame2(path, ref, ignoreWhitespace); + }); } @throttle @@ -2087,7 +2270,7 @@ export class Repository implements Disposable { } async getStashes(): Promise { - return this.run(Operation.Stash, () => this.repository.getStashes()); + return this.run(Operation.Stash(true), () => this.repository.getStashes()); } async createStash(message?: string, includeUntracked?: boolean, staged?: boolean): Promise { @@ -2096,26 +2279,26 @@ export class Repository implements Disposable { ...!staged ? this.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath) : [], ...includeUntracked ? this.untrackedGroup.resourceStates.map(r => r.resourceUri.fsPath) : []]; - return await this.run(Operation.Stash, async () => { + return await this.run(Operation.Stash(false), async () => { await this.repository.createStash(message, includeUntracked, staged); this.closeDiffEditors(indexResources, workingGroupResources); }); } - async popStash(index?: number): Promise { - return await this.run(Operation.Stash, () => this.repository.popStash(index)); + async popStash(index?: number, options?: { reinstateStagedChanges?: boolean }): Promise { + return await this.run(Operation.Stash(false), () => this.repository.popStash(index, options)); } async dropStash(index?: number): Promise { - return await this.run(Operation.Stash, () => this.repository.dropStash(index)); + return await this.run(Operation.Stash(false), () => this.repository.dropStash(index)); } - async applyStash(index?: number): Promise { - return await this.run(Operation.Stash, () => this.repository.applyStash(index)); + async applyStash(index?: number, options?: { reinstateStagedChanges?: boolean }): Promise { + return await this.run(Operation.Stash(false), () => this.repository.applyStash(index, options)); } async showStash(index: number): Promise { - return await this.run(Operation.Stash, () => this.repository.showStash(index)); + return await this.run(Operation.Stash(true), () => this.repository.showStash(index)); } async getCommitTemplate(): Promise { @@ -2243,14 +2426,15 @@ export class Repository implements Disposable { private async run( operation: Operation, - runOperation: () => Promise = () => Promise.resolve(null), - getOptimisticResourceGroups: () => GitResourceGroups | undefined = () => undefined): Promise { + runOperation: () => Promise = () => Promise.resolve(null) as Promise, + getOptimisticResourceGroups: () => GitResourceGroups | undefined = () => undefined + ): Promise { if (this.state !== RepositoryState.Idle) { throw new Error('Repository not initialized'); } - let error: any = null; + let error: unknown = null; this._operations.start(operation); this._onRunOperation.fire(operation.kind); @@ -2266,7 +2450,7 @@ export class Repository implements Disposable { } catch (err) { error = err; - if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) { + if (err instanceof GitError && err.gitErrorCode === GitErrorCodes.NotAGitRepository) { this.state = RepositoryState.Disposed; } @@ -2281,7 +2465,90 @@ export class Repository implements Disposable { } } - private async retryRun(operation: Operation, runOperation: () => Promise = () => Promise.resolve(null)): Promise { + async migrateChanges(sourceRepositoryRoot: string, options?: { confirmation?: boolean; deleteFromSource?: boolean; untracked?: boolean }): Promise { + const sourceRepository = this.repositoryResolver.getRepository(sourceRepositoryRoot); + if (!sourceRepository) { + window.showWarningMessage(l10n.t('The source repository could not be found.')); + return; + } + + if (sourceRepository.indexGroup.resourceStates.length === 0 && + sourceRepository.workingTreeGroup.resourceStates.length === 0 && + sourceRepository.untrackedGroup.resourceStates.length === 0) { + await window.showInformationMessage(l10n.t('There are no changes in the selected worktree to migrate.')); + return; + } + + const sourceFilePaths = [ + ...sourceRepository.indexGroup.resourceStates, + ...sourceRepository.workingTreeGroup.resourceStates, + ...sourceRepository.untrackedGroup.resourceStates + ].map(resource => path.relative(sourceRepository.root, resource.resourceUri.fsPath)); + + const targetFilePaths = [ + ...this.workingTreeGroup.resourceStates, + ...this.untrackedGroup.resourceStates + ].map(resource => path.relative(this.root, resource.resourceUri.fsPath)); + + // Detect overlapping unstaged files in worktree stash and target repository + const conflicts = sourceFilePaths.filter(path => targetFilePaths.includes(path)); + + if (conflicts.length > 0) { + const maxFilesShown = 5; + const filesToShow = conflicts.slice(0, maxFilesShown); + const remainingCount = conflicts.length - maxFilesShown; + + const fileList = filesToShow.join('\n ') + + (remainingCount > 0 ? l10n.t('\n and {0} more file{1}...', remainingCount, remainingCount > 1 ? 's' : '') : ''); + + const message = l10n.t('Your local changes to the following files would be overwritten by merge:\n {0}\n\nPlease stage, commit, or stash your changes in the repository before migrating changes.', fileList); + await window.showErrorMessage(message, { modal: true }); + return; + } + + if (options?.confirmation) { + // Non-interactive migration, do not show confirmation dialog + const message = l10n.t('Proceed with migrating changes to the current repository?'); + const detail = l10n.t('This will apply the worktree\'s changes to this repository and discard changes in the worktree.\nThis is IRREVERSIBLE!'); + const proceed = l10n.t('Proceed'); + const pick = await window.showWarningMessage(message, { modal: true, detail }, proceed); + if (pick !== proceed) { + return; + } + } + + const stashName = `migration:${sourceRepository.HEAD?.name ?? sourceRepository.HEAD?.commit}-${this.HEAD?.name ?? this.HEAD?.commit}`; + await sourceRepository.createStash(stashName, options?.untracked); + const stashes = await sourceRepository.getStashes(); + + try { + if (options?.deleteFromSource) { + await this.popStash(stashes[0].index); + } else { + await this.applyStash(stashes[0].index); + await sourceRepository.popStash(stashes[0].index, { reinstateStagedChanges: true }); + } + } catch (err) { + if (err.gitErrorCode === GitErrorCodes.StashConflict) { + this.isWorktreeMigrating = true; + + const message = l10n.t('There are merge conflicts from migrating changes. Please resolve them before committing.'); + const show = l10n.t('Show Changes'); + const choice = await window.showWarningMessage(message, show); + if (choice === show) { + await commands.executeCommand('workbench.view.scm'); + } + + await sourceRepository.popStash(stashes[0].index, { reinstateStagedChanges: true }); + return; + } + + await sourceRepository.popStash(stashes[0].index, { reinstateStagedChanges: true }); + throw err; + } + } + + private async retryRun(operation: Operation, runOperation: () => Promise): Promise { let attempt = 0; while (true) { @@ -2395,6 +2662,11 @@ export class Repository implements Disposable { if (resourcesGroups.untrackedGroup) { this.untrackedGroup.resourceStates = resourcesGroups.untrackedGroup; } if (resourcesGroups.workingTreeGroup) { this.workingTreeGroup.resourceStates = resourcesGroups.workingTreeGroup; } + // clear worktree migrating flag once all conflicts are resolved + if (this._isWorktreeMigrating && resourcesGroups.mergeGroup && resourcesGroups.mergeGroup.length === 0) { + this._isWorktreeMigrating = false; + } + // set count badge this.setCountBadge(); } @@ -2502,12 +2774,12 @@ export class Repository implements Disposable { switch (raw.x + raw.y) { case '??': switch (untrackedChanges) { - case 'mixed': return workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons)); + case 'mixed': return workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.UNTRACKED, useIcons, undefined, this.kind)); case 'separate': return untrackedGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.UNTRACKED, useIcons)); default: return undefined; } case '!!': switch (untrackedChanges) { - case 'mixed': return workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons)); + case 'mixed': return workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.IGNORED, useIcons, undefined, this.kind)); case 'separate': return untrackedGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Untracked, uri, Status.IGNORED, useIcons)); default: return undefined; } @@ -2521,19 +2793,19 @@ export class Repository implements Disposable { } switch (raw.x) { - case 'M': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons)); break; - case 'A': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons)); break; - case 'D': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons)); break; - case 'R': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri)); break; - case 'C': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_COPIED, useIcons, renameUri)); break; + case 'M': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_MODIFIED, useIcons, undefined, this.kind)); break; + case 'A': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_ADDED, useIcons, undefined, this.kind)); break; + case 'D': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_DELETED, useIcons, undefined, this.kind)); break; + case 'R': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_RENAMED, useIcons, renameUri, this.kind)); break; + case 'C': indexGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.Index, uri, Status.INDEX_COPIED, useIcons, renameUri, this.kind)); break; } switch (raw.y) { - case 'M': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri)); break; - case 'D': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri)); break; - case 'A': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri)); break; - case 'R': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_RENAME, useIcons, renameUri)); break; - case 'T': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.TYPE_CHANGED, useIcons, renameUri)); break; + case 'M': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.MODIFIED, useIcons, renameUri, this.kind)); break; + case 'D': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.DELETED, useIcons, renameUri, this.kind)); break; + case 'A': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_ADD, useIcons, renameUri, this.kind)); break; + case 'R': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.INTENT_TO_RENAME, useIcons, renameUri, this.kind)); break; + case 'T': workingTreeGroup.push(new Resource(this.resourceCommandResolver, ResourceGroupType.WorkingTree, uri, Status.TYPE_CHANGED, useIcons, renameUri, this.kind)); break; } return undefined; @@ -2616,7 +2888,7 @@ export class Repository implements Disposable { const result = await runOperation(); return result; } finally { - await this.repository.popStash(); + await this.repository.popStash(undefined, { reinstateStagedChanges: true }); } } diff --git a/extensions/git/src/repositoryCache.ts b/extensions/git/src/repositoryCache.ts new file mode 100644 index 0000000000000..5e3f8cbe59452 --- /dev/null +++ b/extensions/git/src/repositoryCache.ts @@ -0,0 +1,194 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LogOutputChannel, Memento, workspace } from 'vscode'; +import { LRUCache } from './cache'; +import { Remote } from './api/git'; +import { isDescendant } from './util'; + +export interface RepositoryCacheInfo { + workspacePath: string; // path of the workspace folder or workspace file +} + +function isRepositoryCacheInfo(obj: unknown): obj is RepositoryCacheInfo { + if (!obj || typeof obj !== 'object') { + return false; + } + const rec = obj as Record; + return typeof rec.workspacePath === 'string'; +} + +export class RepositoryCache { + + private static readonly STORAGE_KEY = 'git.repositoryCache'; + private static readonly MAX_REPO_ENTRIES = 30; // Max repositories tracked + private static readonly MAX_FOLDER_ENTRIES = 10; // Max folders per repository + + private normalizeRepoUrl(url: string): string { + try { + const trimmed = url.trim(); + return trimmed.replace(/(?:\.git)?\/*$/i, ''); + } catch { + return url; + } + } + + // Outer LRU: repoUrl -> inner LRU (folderPathOrWorkspaceFile -> RepositoryCacheInfo). + private readonly lru = new LRUCache>(RepositoryCache.MAX_REPO_ENTRIES); + + constructor(public readonly _globalState: Memento, private readonly _logger: LogOutputChannel) { + this.load(); + } + + // Exposed for testing + protected get _workspaceFile() { + return workspace.workspaceFile; + } + + // Exposed for testing + protected get _workspaceFolders() { + return workspace.workspaceFolders; + } + + /** + * Associate a repository remote URL with a local workspace folder or workspace file. + * Re-associating bumps recency and persists the updated LRU state. + * @param repoUrl Remote repository URL (e.g. https://github.com/owner/repo.git) + * @param rootPath Root path of the local repo clone. + */ + set(repoUrl: string, rootPath: string): void { + const key = this.normalizeRepoUrl(repoUrl); + let foldersLru = this.lru.get(key); + if (!foldersLru) { + foldersLru = new LRUCache(RepositoryCache.MAX_FOLDER_ENTRIES); + } + const folderPathOrWorkspaceFile: string | undefined = this._findWorkspaceForRepo(rootPath); + if (!folderPathOrWorkspaceFile) { + return; + } + + foldersLru.set(folderPathOrWorkspaceFile, { + workspacePath: folderPathOrWorkspaceFile + }); // touch entry + this.lru.set(key, foldersLru); + this.save(); + } + + private _findWorkspaceForRepo(rootPath: string): string | undefined { + // If the current workspace is a workspace file, use that. Otherwise, find the workspace folder that contains the rootUri + let folderPathOrWorkspaceFile: string | undefined; + try { + if (this._workspaceFile) { + folderPathOrWorkspaceFile = this._workspaceFile.fsPath; + } else if (this._workspaceFolders && this._workspaceFolders.length) { + const sorted = [...this._workspaceFolders].sort((a, b) => b.uri.fsPath.length - a.uri.fsPath.length); + for (const folder of sorted) { + const folderPath = folder.uri.fsPath; + if (isDescendant(folderPath, rootPath) || isDescendant(rootPath, folderPath)) { + folderPathOrWorkspaceFile = folderPath; + break; + } + } + } + return folderPathOrWorkspaceFile; + } catch { + return; + } + + } + + update(addedRemotes: Remote[], removedRemotes: Remote[], rootPath: string): void { + for (const remote of removedRemotes) { + const url = remote.fetchUrl; + if (!url) { + continue; + } + const relatedWorkspace = this._findWorkspaceForRepo(rootPath); + if (relatedWorkspace) { + this.delete(url, relatedWorkspace); + } + } + + for (const remote of addedRemotes) { + const url = remote.fetchUrl; + if (!url) { + continue; + } + this.set(url, rootPath); + } + } + + /** + * We should possibly support converting between ssh remotes and http remotes. + */ + get(repoUrl: string): RepositoryCacheInfo[] | undefined { + const key = this.normalizeRepoUrl(repoUrl); + const inner = this.lru.get(key); + return inner ? Array.from(inner.values()) : undefined; + } + + delete(repoUrl: string, folderPathOrWorkspaceFile: string) { + const key = this.normalizeRepoUrl(repoUrl); + const inner = this.lru.get(key); + if (!inner) { + return; + } + if (!inner.remove(folderPathOrWorkspaceFile)) { + return; + } + if (inner.size === 0) { + this.lru.remove(key); + } else { + // Re-set to bump outer LRU recency after modification + this.lru.set(key, inner); + } + this.save(); + } + + private load(): void { + try { + const raw = this._globalState.get<[string, [string, RepositoryCacheInfo][]][]>(RepositoryCache.STORAGE_KEY); + if (!Array.isArray(raw)) { + return; + } + for (const [repo, storedFolders] of raw) { + if (typeof repo !== 'string' || !Array.isArray(storedFolders)) { + continue; + } + const inner = new LRUCache(RepositoryCache.MAX_FOLDER_ENTRIES); + for (const entry of storedFolders) { + if (!Array.isArray(entry) || entry.length !== 2) { + continue; + } + const [folderPath, info] = entry; + if (typeof folderPath !== 'string' || !isRepositoryCacheInfo(info)) { + continue; + } + + inner.set(folderPath, info); + } + if (inner.size) { + this.lru.set(repo, inner); + } + } + + } catch { + this._logger.warn('[CachedRepositories][load] Failed to load cached repositories from global state.'); + } + } + + private save(): void { + // Serialize as [repoUrl, [folderPathOrWorkspaceFile, RepositoryCacheInfo][]] preserving outer LRU order. + const serialized: [string, [string, RepositoryCacheInfo][]][] = []; + for (const [repo, inner] of this.lru) { + const folders: [string, RepositoryCacheInfo][] = []; + for (const [folder, info] of inner) { + folders.push([folder, info]); + } + serialized.push([repo, folders]); + } + void this._globalState.update(RepositoryCache.STORAGE_KEY, serialized); + } +} diff --git a/extensions/git/src/statusbar.ts b/extensions/git/src/statusbar.ts index e58096442f2f2..6a06209eb41cb 100644 --- a/extensions/git/src/statusbar.ts +++ b/extensions/git/src/statusbar.ts @@ -71,7 +71,18 @@ class CheckoutStatusBar { // Branch if (this.repository.HEAD.type === RefType.Head && this.repository.HEAD.name) { - return this.repository.isBranchProtected() ? '$(lock)' : '$(git-branch)'; + switch (true) { + case this.repository.isBranchProtected(): + return '$(lock)'; + case this.repository.mergeInProgress || !!this.repository.rebaseCommit: + return '$(git-branch-conflicts)'; + case this.repository.indexGroup.resourceStates.length > 0: + return '$(git-branch-staged-changes)'; + case this.repository.workingTreeGroup.resourceStates.length + this.repository.untrackedGroup.resourceStates.length > 0: + return '$(git-branch-changes)'; + default: + return '$(git-branch)'; + } } // Tag diff --git a/extensions/git/src/test/repositoryCache.test.ts b/extensions/git/src/test/repositoryCache.test.ts new file mode 100644 index 0000000000000..ce3ec98272d13 --- /dev/null +++ b/extensions/git/src/test/repositoryCache.test.ts @@ -0,0 +1,197 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import * as assert from 'assert'; +import { RepositoryCache } from '../repositoryCache'; +import { Event, EventEmitter, LogLevel, LogOutputChannel, Memento, Uri, WorkspaceFolder } from 'vscode'; + +class InMemoryMemento implements Memento { + private store = new Map(); + + constructor(initial?: Record) { + if (initial) { + for (const k of Object.keys(initial)) { + this.store.set(k, initial[k]); + } + } + } + + get(key: string): T | undefined; + get(key: string, defaultValue: T): T; + get(key: string, defaultValue?: T): T | undefined { + if (this.store.has(key)) { + return this.store.get(key); + } + return defaultValue as (T | undefined); + } + + update(key: string, value: any): Thenable { + this.store.set(key, value); + return Promise.resolve(); + } + + keys(): readonly string[] { + return Array.from(this.store.keys()); + } +} + +class MockLogOutputChannel implements LogOutputChannel { + logLevel: LogLevel = LogLevel.Info; + onDidChangeLogLevel: Event = new EventEmitter().event; + trace(_message: string, ..._args: any[]): void { } + debug(_message: string, ..._args: any[]): void { } + info(_message: string, ..._args: any[]): void { } + warn(_message: string, ..._args: any[]): void { } + error(_error: string | Error, ..._args: any[]): void { } + name: string = 'MockLogOutputChannel'; + append(_value: string): void { } + appendLine(_value: string): void { } + replace(_value: string): void { } + clear(): void { } + show(_column?: unknown, _preserveFocus?: unknown): void { } + hide(): void { } + dispose(): void { } +} + +class TestRepositoryCache extends RepositoryCache { + constructor(memento: Memento, logger: LogOutputChannel, private readonly _workspaceFileProp: Uri | undefined, private readonly _workspaceFoldersProp: readonly WorkspaceFolder[] | undefined) { + super(memento, logger); + } + + protected override get _workspaceFile() { + return this._workspaceFileProp; + } + + protected override get _workspaceFolders() { + return this._workspaceFoldersProp; + } +} + +suite('RepositoryCache', () => { + + test('set & get basic', () => { + const memento = new InMemoryMemento(); + const folder = Uri.file('/workspace/repo'); + const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, [{ uri: folder, name: 'workspace', index: 0 }]); + + cache.set('https://example.com/repo.git', folder.fsPath); + const folders = cache.get('https://example.com/repo.git')!.map(folder => folder.workspacePath); + assert.ok(folders, 'folders should be defined'); + assert.deepStrictEqual(folders, [folder.fsPath]); + }); + + test('inner LRU capped at 10 entries', () => { + const memento = new InMemoryMemento(); + const workspaceFolders: WorkspaceFolder[] = []; + for (let i = 1; i <= 12; i++) { + workspaceFolders.push({ uri: Uri.file(`/ws/folder-${i.toString().padStart(2, '0')}`), name: `folder-${i.toString().padStart(2, '0')}`, index: i - 1 }); + } + const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, workspaceFolders); + const repo = 'https://example.com/repo.git'; + for (let i = 1; i <= 12; i++) { + cache.set(repo, Uri.file(`/ws/folder-${i.toString().padStart(2, '0')}`).fsPath); + } + const folders = cache.get(repo)!.map(folder => folder.workspacePath); + assert.strictEqual(folders.length, 10, 'should only retain 10 most recent folders'); + assert.ok(!folders.includes(Uri.file('/ws/folder-01').fsPath), 'oldest folder-01 should be evicted'); + assert.ok(!folders.includes(Uri.file('/ws/folder-02').fsPath), 'second oldest folder-02 should be evicted'); + assert.ok(folders.includes(Uri.file('/ws/folder-12').fsPath), 'latest folder should be present'); + }); + + test('outer LRU capped at 30 repos', () => { + const memento = new InMemoryMemento(); + const workspaceFolders: WorkspaceFolder[] = []; + for (let i = 1; i <= 35; i++) { + workspaceFolders.push({ uri: Uri.file(`/ws/r${i}`), name: `r${i}`, index: i - 1 }); + } + const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, workspaceFolders); + for (let i = 1; i <= 35; i++) { + const repo = `https://example.com/r${i}.git`; + cache.set(repo, Uri.file(`/ws/r${i}`).fsPath); + } + assert.strictEqual(cache.get('https://example.com/r1.git'), undefined, 'oldest repo should be trimmed'); + assert.ok(cache.get('https://example.com/r35.git'), 'newest repo should remain'); + }); + + test('delete removes folder and prunes empty repo', () => { + const memento = new InMemoryMemento(); + const workspaceFolders: WorkspaceFolder[] = []; + workspaceFolders.push({ uri: Uri.file(`/ws/a`), name: `a`, index: 0 }); + workspaceFolders.push({ uri: Uri.file(`/ws/b`), name: `b`, index: 1 }); + + const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, workspaceFolders); + const repo = 'https://example.com/repo.git'; + const a = Uri.file('/ws/a').fsPath; + const b = Uri.file('/ws/b').fsPath; + cache.set(repo, a); + cache.set(repo, b); + assert.deepStrictEqual(new Set(cache.get(repo)?.map(folder => folder.workspacePath)), new Set([a, b])); + cache.delete(repo, a); + assert.deepStrictEqual(cache.get(repo)!.map(folder => folder.workspacePath), [b]); + cache.delete(repo, b); + assert.strictEqual(cache.get(repo), undefined, 'repo should be pruned when last folder removed'); + }); + + test('normalizes URLs with trailing .git', () => { + const memento = new InMemoryMemento(); + const folder = Uri.file('/workspace/repo'); + const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, [{ uri: folder, name: 'workspace', index: 0 }]); + + // Set with .git extension + cache.set('https://example.com/repo.git', folder.fsPath); + + // Should be able to get with or without .git + const withGit = cache.get('https://example.com/repo.git'); + const withoutGit = cache.get('https://example.com/repo'); + + assert.ok(withGit, 'should find repo when querying with .git'); + assert.ok(withoutGit, 'should find repo when querying without .git'); + assert.deepStrictEqual(withGit, withoutGit, 'should return same result regardless of .git suffix'); + }); + + test('normalizes URLs with trailing slashes and .git', () => { + const memento = new InMemoryMemento(); + const folder = Uri.file('/workspace/repo'); + const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, [{ uri: folder, name: 'workspace', index: 0 }]); + + // Set with .git and trailing slashes + cache.set('https://example.com/repo.git///', folder.fsPath); + + // Should be able to get with various combinations + const variations = [ + 'https://example.com/repo.git///', + 'https://example.com/repo.git/', + 'https://example.com/repo.git', + 'https://example.com/repo/', + 'https://example.com/repo' + ]; + + const results = variations.map(url => cache.get(url)); + + // All should return the same non-undefined result + assert.ok(results[0], 'should find repo with original URL'); + for (let i = 1; i < results.length; i++) { + assert.deepStrictEqual(results[i], results[0], `variation ${variations[i]} should return same result`); + } + }); + + test('handles URLs without .git correctly', () => { + const memento = new InMemoryMemento(); + const folder = Uri.file('/workspace/repo'); + const cache = new TestRepositoryCache(memento, new MockLogOutputChannel(), undefined, [{ uri: folder, name: 'workspace', index: 0 }]); + + // Set without .git extension + cache.set('https://example.com/repo', folder.fsPath); + + // Should be able to get with or without .git + const withoutGit = cache.get('https://example.com/repo'); + const withGit = cache.get('https://example.com/repo.git'); + + assert.ok(withoutGit, 'should find repo when querying without .git'); + assert.ok(withGit, 'should find repo when querying with .git'); + assert.deepStrictEqual(withoutGit, withGit, 'should return same result regardless of .git suffix'); + }); +}); diff --git a/extensions/git/src/timelineProvider.ts b/extensions/git/src/timelineProvider.ts index 54d1c0661d163..1ccf04a423d8d 100644 --- a/extensions/git/src/timelineProvider.ts +++ b/extensions/git/src/timelineProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, ConfigurationChangeEvent, Disposable, env, Event, EventEmitter, MarkdownString, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace, l10n, Command } from 'vscode'; +import { CancellationToken, ConfigurationChangeEvent, Disposable, Event, EventEmitter, ThemeIcon, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider, Uri, workspace, l10n, Command } from 'vscode'; import { Model } from './model'; import { Repository, Resource } from './repository'; import { debounce } from './decorators'; @@ -11,11 +11,9 @@ import { emojify, ensureEmojis } from './emoji'; import { CommandCenter } from './commands'; import { OperationKind, OperationResult } from './operation'; import { truncate } from './util'; -import { CommitShortStat } from './git'; import { provideSourceControlHistoryItemAvatar, provideSourceControlHistoryItemHoverCommands, provideSourceControlHistoryItemMessageLinks } from './historyItemDetailsProvider'; import { AvatarQuery, AvatarQueryCommit } from './api/git'; - -const AVATAR_SIZE = 20; +import { getCommitHover, getHoverCommitHashCommands, processHoverRemoteCommands } from './hover'; export class GitTimelineItem extends TimelineItem { static is(item: TimelineItem): item is GitTimelineItem { @@ -54,61 +52,6 @@ export class GitTimelineItem extends TimelineItem { return this.shortenRef(this.previousRef); } - setItemDetails(uri: Uri, hash: string | undefined, shortHash: string | undefined, avatar: string | undefined, author: string, email: string | undefined, date: string, message: string, shortStat?: CommitShortStat, remoteSourceCommands: Command[] = []): void { - this.tooltip = new MarkdownString('', true); - this.tooltip.isTrusted = true; - - const avatarMarkdown = avatar - ? `![${author}](${avatar}|width=${AVATAR_SIZE},height=${AVATAR_SIZE})` - : '$(account)'; - - if (email) { - const emailTitle = l10n.t('Email'); - this.tooltip.appendMarkdown(`${avatarMarkdown} [**${author}**](mailto:${email} "${emailTitle} ${author}")`); - } else { - this.tooltip.appendMarkdown(`${avatarMarkdown} **${author}**`); - } - - this.tooltip.appendMarkdown(`, $(history) ${date}\n\n`); - this.tooltip.appendMarkdown(`${message}\n\n`); - - if (shortStat) { - this.tooltip.appendMarkdown(`---\n\n`); - - const labels: string[] = []; - if (shortStat.insertions) { - labels.push(`${shortStat.insertions === 1 ? - l10n.t('{0} insertion{1}', shortStat.insertions, '(+)') : - l10n.t('{0} insertions{1}', shortStat.insertions, '(+)')}`); - } - - if (shortStat.deletions) { - labels.push(`${shortStat.deletions === 1 ? - l10n.t('{0} deletion{1}', shortStat.deletions, '(-)') : - l10n.t('{0} deletions{1}', shortStat.deletions, '(-)')}`); - } - - this.tooltip.appendMarkdown(`${labels.join(', ')}\n\n`); - } - - if (hash && shortHash) { - this.tooltip.appendMarkdown(`---\n\n`); - - this.tooltip.appendMarkdown(`[\`$(git-commit) ${shortHash} \`](command:git.viewCommit?${encodeURIComponent(JSON.stringify([uri, hash]))} "${l10n.t('Open Commit')}")`); - this.tooltip.appendMarkdown(' '); - this.tooltip.appendMarkdown(`[$(copy)](command:git.copyContentToClipboard?${encodeURIComponent(JSON.stringify(hash))} "${l10n.t('Copy Commit Hash')}")`); - - // Remote commands - if (remoteSourceCommands.length > 0) { - this.tooltip.appendMarkdown('  |  '); - - const remoteCommandsMarkdown = remoteSourceCommands - .map(command => `[${command.title}](command:${command.command}?${encodeURIComponent(JSON.stringify([...command.arguments ?? [], hash]))} "${command.tooltip}")`); - this.tooltip.appendMarkdown(remoteCommandsMarkdown.join(' ')); - } - } - } - private shortenRef(ref: string): string { if (ref === '' || ref === '~' || ref === 'HEAD') { return ref; @@ -215,13 +158,10 @@ export class GitTimelineProvider implements TimelineProvider { commits.splice(commits.length - 1, 1); } - const dateFormatter = new Intl.DateTimeFormat(env.language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }); - const config = workspace.getConfiguration('git', Uri.file(repo.root)); const dateType = config.get<'committed' | 'authored'>('timeline.date'); const showAuthor = config.get('timeline.showAuthor'); const showUncommitted = config.get('timeline.showUncommitted'); - const commitShortHashLength = config.get('commitShortHashLength') ?? 7; const openComparison = l10n.t('Open Comparison'); @@ -254,10 +194,15 @@ export class GitTimelineProvider implements TimelineProvider { item.description = c.authorName; } - const commitRemoteSourceCommands = !unpublishedCommits.has(c.hash) ? remoteHoverCommands : []; + const commitRemoteSourceCommands = !unpublishedCommits.has(c.hash) ? remoteHoverCommands ?? [] : []; const messageWithLinks = await provideSourceControlHistoryItemMessageLinks(this.model, repo, message) ?? message; - item.setItemDetails(uri, c.hash, truncate(c.hash, commitShortHashLength, false), avatars?.get(c.hash), c.authorName!, c.authorEmail, dateFormatter.format(date), messageWithLinks, c.shortStat, commitRemoteSourceCommands); + const commands: Command[][] = [ + getHoverCommitHashCommands(uri, c.hash), + processHoverRemoteCommands(commitRemoteSourceCommands, c.hash) + ]; + + item.tooltip = getCommitHover(avatars?.get(c.hash), c.authorName, c.authorEmail, date, messageWithLinks, c.shortStat, commands); const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri); if (cmd) { @@ -282,7 +227,7 @@ export class GitTimelineProvider implements TimelineProvider { // TODO@eamodio: Replace with a better icon -- reflecting its status maybe? item.iconPath = new ThemeIcon('git-commit'); item.description = ''; - item.setItemDetails(uri, undefined, undefined, undefined, you, undefined, dateFormatter.format(date), Resource.getStatusText(index.type)); + item.tooltip = getCommitHover(undefined, you, undefined, date, Resource.getStatusText(index.type), undefined, undefined); const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri); if (cmd) { @@ -304,7 +249,7 @@ export class GitTimelineProvider implements TimelineProvider { const item = new GitTimelineItem('', index ? '~' : 'HEAD', l10n.t('Uncommitted Changes'), date.getTime(), 'working', 'git:file:working'); item.iconPath = new ThemeIcon('circle-outline'); item.description = ''; - item.setItemDetails(uri, undefined, undefined, undefined, you, undefined, dateFormatter.format(date), Resource.getStatusText(working.type)); + item.tooltip = getCommitHover(undefined, you, undefined, date, Resource.getStatusText(working.type), undefined, undefined); const cmd = this.commands.resolveTimelineOpenDiffCommand(item, uri); if (cmd) { diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index a4c5036255f7f..c6ec6ece45c69 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event, Disposable, EventEmitter, SourceControlHistoryItemRef, l10n, workspace, Uri, DiagnosticSeverity, env } from 'vscode'; -import { dirname, sep, relative } from 'path'; +import { Event, Disposable, EventEmitter, SourceControlHistoryItemRef, l10n, workspace, Uri, DiagnosticSeverity, env, SourceControlHistoryItem } from 'vscode'; +import { dirname, normalize, sep, relative } from 'path'; import { Readable } from 'stream'; import { promises as fs, createReadStream } from 'fs'; import byline from 'byline'; +import { Stash } from './git'; export const isMacintosh = process.platform === 'darwin'; export const isWindows = process.platform === 'win32'; @@ -15,6 +16,10 @@ export const isRemote = env.remoteName !== undefined; export const isLinux = process.platform === 'linux'; export const isLinuxSnap = isLinux && !!process.env['SNAP'] && !!process.env['SNAP_REVISION']; +export type Mutable = { + -readonly [P in keyof T]: T[P] +}; + export function log(...args: any[]): void { console.log.apply(console, ['git:', ...args]); } @@ -38,10 +43,6 @@ export function combinedDisposable(disposables: IDisposable[]): IDisposable { export const EmptyDisposable = toDisposable(() => null); -export function fireEvent(event: Event): Event { - return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => event(_ => (listener as any).call(thisArgs), null, disposables); -} - export function mapEvent(event: Event, map: (i: I) => O): Event { return (listener: (e: O) => any, thisArgs?: any, disposables?: Disposable[]) => event(i => listener.call(thisArgs, map(i)), null, disposables); } @@ -110,7 +111,8 @@ export function once(fn: (...args: any[]) => any): (...args: any[]) => any { export function assign(destination: T, ...sources: any[]): T { for (const source of sources) { - Object.keys(source).forEach(key => (destination as any)[key] = source[key]); + Object.keys(source).forEach(key => + (destination as Record)[key] = source[key]); } return destination; @@ -139,6 +141,9 @@ export function groupBy(arr: T[], fn: (el: T) => string): { [key: string]: T[ }, Object.create(null)); } +export function coalesce(array: ReadonlyArray): T[] { + return array.filter((e): e is T => !!e); +} export async function mkdirp(path: string, mode?: number): Promise { const mkdir = async () => { @@ -236,7 +241,7 @@ export function readBytes(stream: Readable, bytes: number): Promise { bytesRead += bytesToRead; if (bytesRead === bytes) { - (stream as any).destroy(); // Will trigger the close event eventually + stream.destroy(); // Will trigger the close event eventually } }); @@ -295,14 +300,26 @@ export function truncate(value: string, maxLength = 20, ellipsis = true): string return value.length <= maxLength ? value : `${value.substring(0, maxLength)}${ellipsis ? '\u2026' : ''}`; } +export function subject(value: string): string { + const index = value.indexOf('\n'); + return index === -1 ? value : truncate(value, index, false); +} + function normalizePath(path: string): string { // Windows & Mac are currently being handled // as case insensitive file systems in VS Code. if (isWindows || isMacintosh) { - return path.toLowerCase(); + path = path.toLowerCase(); } - return path; + // Trailing separator + if (/[/\\]$/.test(path)) { + // Remove trailing separator + path = path.substring(0, path.length - 1); + } + + // Normalize the path + return normalize(path); } export function isDescendant(parent: string, descendant: string): boolean { @@ -310,11 +327,16 @@ export function isDescendant(parent: string, descendant: string): boolean { return true; } + // Normalize the paths + parent = normalizePath(parent); + descendant = normalizePath(descendant); + + // Ensure parent ends with separator if (parent.charAt(parent.length - 1) !== sep) { parent += sep; } - return normalizePath(descendant).startsWith(normalizePath(parent)); + return descendant.startsWith(parent); } export function pathEquals(a: string, b: string): boolean { @@ -779,6 +801,12 @@ export function getCommitShortHash(scope: Uri, hash: string): string { return hash.substring(0, shortHashLength); } +export function getHistoryItemDisplayName(historyItem: SourceControlHistoryItem): string { + return historyItem.references?.length + ? historyItem.references[0].name + : historyItem.displayId ?? historyItem.id; +} + export type DiagnosticSeverityConfig = 'error' | 'warning' | 'information' | 'hint' | 'none'; export function toDiagnosticSeverity(value: DiagnosticSeverityConfig): DiagnosticSeverity { @@ -822,3 +850,27 @@ export function extractFilePathFromArgs(argv: string[], startIndex: number): str // leading quote and return the path as-is return path.slice(1); } + +export function getStashDescription(stash: Stash): string | undefined { + if (!stash.commitDate && !stash.branchName) { + return undefined; + } + + const descriptionSegments: string[] = []; + if (stash.commitDate) { + descriptionSegments.push(fromNow(stash.commitDate)); + } + if (stash.branchName) { + descriptionSegments.push(stash.branchName); + } + + return descriptionSegments.join(' \u2022 '); +} + +export function isCopilotWorktree(path: string): boolean { + const lastSepIndex = path.lastIndexOf(sep); + + return lastSepIndex !== -1 + ? path.substring(lastSepIndex + 1).startsWith('copilot-worktree-') + : path.startsWith('copilot-worktree-'); +} diff --git a/extensions/git/tsconfig.json b/extensions/git/tsconfig.json index c1179f34d2905..eac688f81de13 100644 --- a/extensions/git/tsconfig.json +++ b/extensions/git/tsconfig.json @@ -2,10 +2,10 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "experimentalDecorators": true, "typeRoots": [ "./node_modules/@types" - ] + ], + "skipLibCheck": true }, "include": [ "src/**/*", @@ -16,6 +16,7 @@ "../../src/vscode-dts/vscode.proposed.quickInputButtonLocation.d.ts", "../../src/vscode-dts/vscode.proposed.quickPickSortByLabel.d.ts", "../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts", + "../../src/vscode-dts/vscode.proposed.scmArtifactProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmHistoryProvider.d.ts", "../../src/vscode-dts/vscode.proposed.scmProviderOptions.d.ts", "../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts", diff --git a/extensions/github-authentication/extension-browser.webpack.config.js b/extensions/github-authentication/extension-browser.webpack.config.js index f109e203569be..70a7fd87cf4a3 100644 --- a/extensions/github-authentication/extension-browser.webpack.config.js +++ b/extensions/github-authentication/extension-browser.webpack.config.js @@ -2,27 +2,23 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import path from 'path'; +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const path = require('path'); -const withBrowserDefaults = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, node: false, entry: { extension: './src/extension.ts', }, resolve: { alias: { - 'uuid': path.resolve(__dirname, 'node_modules/uuid/dist/esm-browser/index.js'), - './node/authServer': path.resolve(__dirname, 'src/browser/authServer'), - './node/crypto': path.resolve(__dirname, 'src/browser/crypto'), - './node/fetch': path.resolve(__dirname, 'src/browser/fetch'), - './node/buffer': path.resolve(__dirname, 'src/browser/buffer'), + 'uuid': path.resolve(import.meta.dirname, 'node_modules/uuid/dist/esm-browser/index.js'), + './node/authServer': path.resolve(import.meta.dirname, 'src/browser/authServer'), + './node/crypto': path.resolve(import.meta.dirname, 'src/browser/crypto'), + './node/fetch': path.resolve(import.meta.dirname, 'src/browser/fetch'), + './node/buffer': path.resolve(import.meta.dirname, 'src/browser/buffer'), } } }); diff --git a/extensions/github-authentication/extension.webpack.config.js b/extensions/github-authentication/extension.webpack.config.js index d356151d68c57..166c1d8b1e340 100644 --- a/extensions/github-authentication/extension.webpack.config.js +++ b/extensions/github-authentication/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { extension: './src/extension.ts', }, diff --git a/extensions/github-authentication/package-lock.json b/extensions/github-authentication/package-lock.json index 60fbdfe141cbc..93b4b14be1712 100644 --- a/extensions/github-authentication/package-lock.json +++ b/extensions/github-authentication/package-lock.json @@ -14,7 +14,7 @@ "vscode-tas-client": "^0.1.84" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/node-fetch": "^2.5.7" }, @@ -147,10 +147,11 @@ "license": "MIT" }, "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.10", @@ -192,6 +193,20 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k= sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -213,36 +228,219 @@ "node": ">=0.4.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "license": "MIT", "dependencies": { - "mime-db": "1.44.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" diff --git a/extensions/github-authentication/package.json b/extensions/github-authentication/package.json index cf422e067ef9f..91f2d1b57dbcb 100644 --- a/extensions/github-authentication/package.json +++ b/extensions/github-authentication/package.json @@ -48,17 +48,30 @@ ] } ], - "configuration": [{ - "title": "%config.github-enterprise.title%", - "properties": { - "github-enterprise.uri": { - "type": "string", - "markdownDescription": "%config.github-enterprise.uri.description%", - "pattern": "^(?:$|(https?)://(?!github\\.com).*)" + "configuration": [ + { + "title": "%config.github-enterprise.title%", + "properties": { + "github-enterprise.uri": { + "type": "string", + "markdownDescription": "%config.github-enterprise.uri.description%", + "pattern": "^(?:$|(https?)://(?!github\\.com).*)" + }, + "github-authentication.useElectronFetch": { + "type": "boolean", + "default": true, + "scope": "application", + "markdownDescription": "%config.github-authentication.useElectronFetch.description%" + }, + "github-authentication.preferDeviceCodeFlow": { + "type": "boolean", + "default": false, + "scope": "application", + "markdownDescription": "%config.github-authentication.preferDeviceCodeFlow.description%" + } } } - } - ] + ] }, "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "main": "./out/extension.js", @@ -76,7 +89,7 @@ "vscode-tas-client": "^0.1.84" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x", "@types/node-fetch": "^2.5.7" }, diff --git a/extensions/github-authentication/package.nls.json b/extensions/github-authentication/package.nls.json index e326f00f5c916..dfbd033224d68 100644 --- a/extensions/github-authentication/package.nls.json +++ b/extensions/github-authentication/package.nls.json @@ -2,5 +2,7 @@ "displayName": "GitHub Authentication", "description": "GitHub Authentication Provider", "config.github-enterprise.title": "GHE.com & GitHub Enterprise Server Authentication", - "config.github-enterprise.uri.description": "The URI for your GHE.com or GitHub Enterprise Server instance.\n\nExamples:\n* GHE.com: `https://octocat.ghe.com`\n* GitHub Enterprise Server: `https://github.octocat.com`\n\n> **Note:** This should _not_ be set to a GitHub.com URI. If your account exists on GitHub.com or is a GitHub Enterprise Managed User, you do not need any additional configuration and can simply log in to GitHub." + "config.github-enterprise.uri.description": "The URI for your GHE.com or GitHub Enterprise Server instance.\n\nExamples:\n* GHE.com: `https://octocat.ghe.com`\n* GitHub Enterprise Server: `https://github.octocat.com`\n\n> **Note:** This should _not_ be set to a GitHub.com URI. If your account exists on GitHub.com or is a GitHub Enterprise Managed User, you do not need any additional configuration and can simply log in to GitHub.", + "config.github-authentication.useElectronFetch.description": "When true, uses Electron's built-in fetch function for HTTP requests. When false, uses the Node.js global fetch function. This setting only applies when running in the Electron environment. **Note:** A restart is required for this setting to take effect.", + "config.github-authentication.preferDeviceCodeFlow.description": "When true, prioritize the device code flow for authentication instead of other available flows. This is useful for environments like WSL where the local server or URL handler flows may not work as expected." } diff --git a/extensions/github-authentication/src/common/logger.ts b/extensions/github-authentication/src/common/logger.ts index cf90c4176a9ba..429ecdf596c51 100644 --- a/extensions/github-authentication/src/common/logger.ts +++ b/extensions/github-authentication/src/common/logger.ts @@ -18,6 +18,10 @@ export class Log { this.output.trace(message); } + public debug(message: string): void { + this.output.debug(message); + } + public info(message: string): void { this.output.info(message); } diff --git a/extensions/github-authentication/src/extension.ts b/extensions/github-authentication/src/extension.ts index a20fde2bea330..89a42a364b714 100644 --- a/extensions/github-authentication/src/extension.ts +++ b/extensions/github-authentication/src/extension.ts @@ -79,4 +79,25 @@ export function activate(context: vscode.ExtensionContext) { } } })); + + // Listener to prompt for reload when the fetch implementation setting changes + const beforeFetchSetting = vscode.workspace.getConfiguration().get('github-authentication.useElectronFetch', true); + context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(async e => { + if (e.affectsConfiguration('github-authentication.useElectronFetch')) { + const afterFetchSetting = vscode.workspace.getConfiguration().get('github-authentication.useElectronFetch', true); + if (beforeFetchSetting !== afterFetchSetting) { + const selection = await vscode.window.showInformationMessage( + vscode.l10n.t('GitHub Authentication - Reload required'), + { + modal: true, + detail: vscode.l10n.t('A reload is required for the fetch setting change to take effect.') + }, + vscode.l10n.t('Reload Window') + ); + if (selection) { + await vscode.commands.executeCommand('workbench.action.reloadWindow'); + } + } + } + })); } diff --git a/extensions/github-authentication/src/flows.ts b/extensions/github-authentication/src/flows.ts index e9954dc2d027a..76da600118afa 100644 --- a/extensions/github-authentication/src/flows.ts +++ b/extensions/github-authentication/src/flows.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'path'; -import { ProgressLocation, Uri, commands, env, l10n, window } from 'vscode'; +import { ProgressLocation, Uri, commands, env, l10n, window, workspace } from 'vscode'; import { Log } from './common/logger'; import { Config } from './config'; import { UriEventHandler } from './github'; import { fetching } from './node/fetch'; +import { crypto } from './node/crypto'; import { LoopbackAuthServer } from './node/authServer'; import { promiseFromEvent } from './common/utils'; import { isHostedGitHubEnterprise } from './common/env'; @@ -72,6 +73,10 @@ interface IFlowTriggerOptions { * The specific auth provider to use for the flow. */ signInProvider?: GitHubSocialSignInProvider; + /** + * Extra parameters to include in the OAuth flow. + */ + extraAuthorizeParameters?: Record; /** * The Uri that the OAuth flow will redirect to. (i.e. vscode.dev/redirect) */ @@ -108,11 +113,44 @@ interface IFlow { trigger(options: IFlowTriggerOptions): Promise; } +/** + * Generates a cryptographically secure random string for PKCE code verifier. + * @param length The length of the string to generate + * @returns A random hex string + */ +function generateRandomString(length: number): string { + const array = new Uint8Array(length); + crypto.getRandomValues(array); + return Array.from(array) + .map(b => b.toString(16).padStart(2, '0')) + .join('') + .substring(0, length); +} + +/** + * Generates a PKCE code challenge from a code verifier using SHA-256. + * @param codeVerifier The code verifier string + * @returns A base64url-encoded SHA-256 hash of the code verifier + */ +async function generateCodeChallenge(codeVerifier: string): Promise { + const encoder = new TextEncoder(); + const data = encoder.encode(codeVerifier); + const digest = await crypto.subtle.digest('SHA-256', data); + + // Base64url encode the digest + const base64String = btoa(String.fromCharCode(...new Uint8Array(digest))); + return base64String + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); +} + async function exchangeCodeForToken( logger: Log, endpointUri: Uri, redirectUri: Uri, code: string, + codeVerifier: string, enterpriseUri?: Uri ): Promise { logger.info('Exchanging code for token...'); @@ -126,12 +164,16 @@ async function exchangeCodeForToken( ['code', code], ['client_id', Config.gitHubClientId], ['redirect_uri', redirectUri.toString(true)], - ['client_secret', clientSecret] + ['client_secret', clientSecret], + ['code_verifier', codeVerifier] ]); if (enterpriseUri) { body.append('github_enterprise', enterpriseUri.toString(true)); } const result = await fetching(endpointUri.toString(true), { + logger, + retryFallbacks: true, + expectJSON: true, method: 'POST', headers: { Accept: 'application/json', @@ -178,6 +220,7 @@ class UrlHandlerFlow implements IFlow { enterpriseUri, nonce, signInProvider, + extraAuthorizeParameters, uriHandler, existingLogin, logger, @@ -192,13 +235,19 @@ class UrlHandlerFlow implements IFlow { }), cancellable: true }, async (_, token) => { + // Generate PKCE parameters + const codeVerifier = generateRandomString(64); + const codeChallenge = await generateCodeChallenge(codeVerifier); + const promise = uriHandler.waitForCode(logger, scopes, nonce, token); const searchParams = new URLSearchParams([ ['client_id', Config.gitHubClientId], ['redirect_uri', redirectUri.toString(true)], ['scope', scopes], - ['state', encodeURIComponent(callbackUri.toString(true))] + ['state', encodeURIComponent(callbackUri.toString(true))], + ['code_challenge', codeChallenge], + ['code_challenge_method', 'S256'] ]); if (existingLogin) { searchParams.append('login', existingLogin); @@ -208,6 +257,11 @@ class UrlHandlerFlow implements IFlow { if (signInProvider) { searchParams.append('provider', signInProvider); } + if (extraAuthorizeParameters) { + for (const [key, value] of Object.entries(extraAuthorizeParameters)) { + searchParams.append(key, value); + } + } // The extra toString, parse is apparently needed for env.openExternal // to open the correct URL. @@ -224,7 +278,7 @@ class UrlHandlerFlow implements IFlow { ? Uri.parse(`${proxyEndpoints.github}login/oauth/access_token`) : baseUri.with({ path: '/login/oauth/access_token' }); - const accessToken = await exchangeCodeForToken(logger, endpointUrl, redirectUri, code, enterpriseUri); + const accessToken = await exchangeCodeForToken(logger, endpointUrl, redirectUri, code, codeVerifier, enterpriseUri); return accessToken; }); } @@ -257,6 +311,7 @@ class LocalServerFlow implements IFlow { callbackUri, enterpriseUri, signInProvider, + extraAuthorizeParameters, existingLogin, logger }: IFlowTriggerOptions): Promise { @@ -270,10 +325,16 @@ class LocalServerFlow implements IFlow { }), cancellable: true }, async (_, token) => { + // Generate PKCE parameters + const codeVerifier = generateRandomString(64); + const codeChallenge = await generateCodeChallenge(codeVerifier); + const searchParams = new URLSearchParams([ ['client_id', Config.gitHubClientId], ['redirect_uri', redirectUri.toString(true)], ['scope', scopes], + ['code_challenge', codeChallenge], + ['code_challenge_method', 'S256'] ]); if (existingLogin) { searchParams.append('login', existingLogin); @@ -283,6 +344,11 @@ class LocalServerFlow implements IFlow { if (signInProvider) { searchParams.append('provider', signInProvider); } + if (extraAuthorizeParameters) { + for (const [key, value] of Object.entries(extraAuthorizeParameters)) { + searchParams.append(key, value); + } + } const loginUrl = baseUri.with({ path: '/login/oauth/authorize', @@ -311,6 +377,7 @@ class LocalServerFlow implements IFlow { baseUri.with({ path: '/login/oauth/access_token' }), redirectUri, codeToExchange, + codeVerifier, enterpriseUri); return accessToken; }); @@ -330,7 +397,7 @@ class DeviceCodeFlow implements IFlow { supportsSupportedClients: true, supportsUnsupportedClients: true }; - async trigger({ scopes, baseUri, signInProvider, logger }: IFlowTriggerOptions) { + async trigger({ scopes, baseUri, signInProvider, extraAuthorizeParameters, logger }: IFlowTriggerOptions) { logger.info(`Trying device code flow... (${scopes})`); // Get initial device code @@ -339,6 +406,9 @@ class DeviceCodeFlow implements IFlow { query: `client_id=${Config.gitHubClientId}&scope=${scopes}` }); const result = await fetching(uri.toString(true), { + logger, + retryFallbacks: true, + expectJSON: true, method: 'POST', headers: { Accept: 'application/json' @@ -365,18 +435,26 @@ class DeviceCodeFlow implements IFlow { await env.clipboard.writeText(json.user_code); let open = Uri.parse(json.verification_uri); + const query = new URLSearchParams(open.query); if (signInProvider) { - const query = new URLSearchParams(open.query); query.set('provider', signInProvider); + } + if (extraAuthorizeParameters) { + for (const [key, value] of Object.entries(extraAuthorizeParameters)) { + query.set(key, value); + } + } + if (signInProvider || extraAuthorizeParameters) { open = open.with({ query: query.toString() }); } const uriToOpen = await env.asExternalUri(open); await env.openExternal(uriToOpen); - return await this.waitForDeviceCodeAccessToken(baseUri, json); + return await this.waitForDeviceCodeAccessToken(logger, baseUri, json); } private async waitForDeviceCodeAccessToken( + logger: Log, baseUri: Uri, json: IGitHubDeviceCodeResponse, ): Promise { @@ -407,6 +485,9 @@ class DeviceCodeFlow implements IFlow { let accessTokenResult; try { accessTokenResult = await fetching(refreshTokenUri.toString(true), { + logger, + retryFallbacks: true, + expectJSON: true, method: 'POST', headers: { Accept: 'application/json' @@ -500,6 +581,9 @@ class PatFlow implements IFlow { try { logger.info('Getting token scopes...'); const result = await fetching(serverUri.toString(), { + logger, + retryFallbacks: true, + expectJSON: false, headers: { Authorization: `token ${token}`, 'User-Agent': `${env.appName} (${env.appHost})` @@ -528,7 +612,7 @@ const allFlows: IFlow[] = [ ]; export function getFlows(query: IFlowQuery) { - return allFlows.filter(flow => { + const validFlows = allFlows.filter(flow => { let useFlow: boolean = true; switch (query.target) { case GitHubTarget.DotCom: @@ -564,6 +648,16 @@ export function getFlows(query: IFlowQuery) { } return useFlow; }); + + const preferDeviceCodeFlow = workspace.getConfiguration('github-authentication').get('preferDeviceCodeFlow', false); + if (preferDeviceCodeFlow) { + return [ + ...validFlows.filter(flow => flow instanceof DeviceCodeFlow), + ...validFlows.filter(flow => !(flow instanceof DeviceCodeFlow)) + ]; + } + + return validFlows; } /** diff --git a/extensions/github-authentication/src/github.ts b/extensions/github-authentication/src/github.ts index c5762ada869ea..e9f97282bf8e6 100644 --- a/extensions/github-authentication/src/github.ts +++ b/extensions/github-authentication/src/github.ts @@ -44,6 +44,27 @@ interface GitHubAuthenticationProviderOptions extends vscode.AuthenticationProvi * leaving it up to the user to choose the social sign-in provider on the sign-in page. */ readonly provider?: GitHubSocialSignInProvider; + readonly extraAuthorizeParameters?: Record; +} + +function isGitHubAuthenticationProviderOptions(object: any): object is GitHubAuthenticationProviderOptions { + if (!object || typeof object !== 'object') { + throw new Error('Options are not an object'); + } + if (object.provider !== undefined && !isSocialSignInProvider(object.provider)) { + throw new Error(`Provider is invalid: ${object.provider}`); + } + if (object.extraAuthorizeParameters !== undefined) { + if (!object.extraAuthorizeParameters || typeof object.extraAuthorizeParameters !== 'object') { + throw new Error('Extra parameters must be a record of string keys and string values.'); + } + for (const [key, value] of Object.entries(object.extraAuthorizeParameters)) { + if (typeof key !== 'string' || typeof value !== 'string') { + throw new Error('Extra parameters must be a record of string keys and string values.'); + } + } + } + return true; } export class UriEventHandler extends vscode.EventEmitter implements vscode.UriHandler { @@ -150,6 +171,9 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid return sessions; }); + const supportedAuthorizationServers = ghesUri + ? [vscode.Uri.joinPath(ghesUri, '/login/oauth')] + : [vscode.Uri.parse('https://github.com/login/oauth')]; this._disposable = vscode.Disposable.from( this._telemetryReporter, vscode.authentication.registerAuthenticationProvider( @@ -158,9 +182,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid this, { supportsMultipleAccounts: true, - supportedAuthorizationServers: [ - ghesUri ?? vscode.Uri.parse('https://github.com/login/oauth') - ] + supportedAuthorizationServers } ), this.context.secrets.onDidChange(() => this.checkForUpdates()) @@ -338,12 +360,15 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid scopes: JSON.stringify(scopes), }); + if (options && !isGitHubAuthenticationProviderOptions(options)) { + throw new Error('Invalid options'); + } const sessions = await this._sessionsPromise; const loginWith = options?.account?.label; - const signInProvider = isSocialSignInProvider(options?.provider) ? options.provider : undefined; + const signInProvider = options?.provider; this._logger.info(`Logging in with${signInProvider ? ` ${signInProvider}, ` : ''} '${loginWith ? loginWith : 'any'}' account...`); const scopeString = sortedScopes.join(' '); - const token = await this._githubServer.login(scopeString, signInProvider, loginWith); + const token = await this._githubServer.login(scopeString, signInProvider, options?.extraAuthorizeParameters, loginWith); const session = await this.tokenToSession(token, scopes); this.afterSessionLoad(session); diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 62f2f5f20bfb9..b8646696a8ae2 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -19,7 +19,7 @@ const REDIRECT_URL_STABLE = 'https://vscode.dev/redirect'; const REDIRECT_URL_INSIDERS = 'https://insiders.vscode.dev/redirect'; export interface IGitHubServer { - login(scopes: string, signInProvider?: GitHubSocialSignInProvider, existingLogin?: string): Promise; + login(scopes: string, signInProvider?: GitHubSocialSignInProvider, extraAuthorizeParameters?: Record, existingLogin?: string): Promise; logout(session: vscode.AuthenticationSession): Promise; getUserInfo(token: string): Promise<{ id: string; accountName: string }>; sendAdditionalTelemetryInfo(session: vscode.AuthenticationSession): Promise; @@ -87,7 +87,7 @@ export class GitHubServer implements IGitHubServer { return this._isNoCorsEnvironment; } - public async login(scopes: string, signInProvider?: GitHubSocialSignInProvider, existingLogin?: string): Promise { + public async login(scopes: string, signInProvider?: GitHubSocialSignInProvider, extraAuthorizeParameters?: Record, existingLogin?: string): Promise { this._logger.info(`Logging in for the following scopes: ${scopes}`); // Used for showing a friendlier message to the user when the explicitly cancel a flow. @@ -136,6 +136,7 @@ export class GitHubServer implements IGitHubServer { callbackUri, nonce, signInProvider, + extraAuthorizeParameters, baseUri: this.baseUri, logger: this._logger, uriHandler: this._uriHandler, @@ -177,6 +178,9 @@ export class GitHubServer implements IGitHubServer { try { // Defined here: https://docs.github.com/en/rest/apps/oauth-applications?apiVersion=2022-11-28#delete-an-app-token const result = await fetching(uri.toString(true), { + logger: this._logger, + retryFallbacks: true, + expectJSON: false, method: 'DELETE', headers: { Accept: 'application/vnd.github+json', @@ -218,6 +222,9 @@ export class GitHubServer implements IGitHubServer { try { this._logger.info('Getting user info...'); result = await fetching(this.getServerUri('/user').toString(), { + logger: this._logger, + retryFallbacks: true, + expectJSON: true, headers: { Authorization: `token ${token}`, 'User-Agent': `${vscode.env.appName} (${vscode.env.appHost})` @@ -276,6 +283,9 @@ export class GitHubServer implements IGitHubServer { try { const result = await fetching('https://education.github.com/api/user', { + logger: this._logger, + retryFallbacks: true, + expectJSON: true, headers: { Authorization: `token ${session.accessToken}`, 'faculty-check-preview': 'true', @@ -291,9 +301,11 @@ export class GitHubServer implements IGitHubServer { ? 'faculty' : 'none'; } else { + this._logger.info(`Unable to resolve optional EDU details. Status: ${result.status} ${result.statusText}`); edu = 'unknown'; } } catch (e) { + this._logger.info(`Unable to resolve optional EDU details. Error: ${e}`); edu = 'unknown'; } @@ -316,6 +328,9 @@ export class GitHubServer implements IGitHubServer { let version: string; if (!isSupportedTarget(this._type, this._ghesUri)) { const result = await fetching(this.getServerUri('/meta').toString(), { + logger: this._logger, + retryFallbacks: true, + expectJSON: true, headers: { Authorization: `token ${token}`, 'User-Agent': `${vscode.env.appName} (${vscode.env.appHost})` diff --git a/extensions/github-authentication/src/node/crypto.ts b/extensions/github-authentication/src/node/crypto.ts index 367246fdbede8..27b3cafd8b16a 100644 --- a/extensions/github-authentication/src/node/crypto.ts +++ b/extensions/github-authentication/src/node/crypto.ts @@ -5,4 +5,4 @@ import { webcrypto } from 'crypto'; -export const crypto = webcrypto as any as Crypto; +export const crypto = webcrypto; diff --git a/extensions/github-authentication/src/node/fetch.ts b/extensions/github-authentication/src/node/fetch.ts index ca344d436b111..7bd5f92902939 100644 --- a/extensions/github-authentication/src/node/fetch.ts +++ b/extensions/github-authentication/src/node/fetch.ts @@ -3,10 +3,261 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -let _fetch: typeof fetch; +import * as http from 'http'; +import * as https from 'https'; +import { workspace } from 'vscode'; +import { Log } from '../common/logger'; +import { Readable } from 'stream'; + +export interface FetchOptions { + logger: Log; + retryFallbacks: boolean; + expectJSON: boolean; + method?: 'GET' | 'POST' | 'DELETE'; + headers?: Record; + body?: string; + signal?: AbortSignal; +} + +export interface FetchHeaders { + get(name: string): string | null; +} + +export interface FetchResponse { + ok: boolean; + status: number; + statusText: string; + headers: FetchHeaders; + text(): Promise; + json(): Promise; +} + +export type Fetch = (url: string, options: FetchOptions) => Promise; + +interface Fetcher { + name: string; + fetch: Fetch; +} + +const _fetchers: Fetcher[] = []; try { - _fetch = require('electron').net.fetch; + _fetchers.push({ + name: 'Electron fetch', + fetch: require('electron').net.fetch + }); } catch { - _fetch = fetch; + // ignore +} + +const nodeFetch = { + name: 'Node fetch', + fetch, +}; +const useElectronFetch = workspace.getConfiguration('github-authentication').get('useElectronFetch', true); +if (useElectronFetch) { + _fetchers.push(nodeFetch); +} else { + _fetchers.unshift(nodeFetch); +} + +_fetchers.push({ + name: 'Node http/s', + fetch: nodeHTTP, +}); + +export function createFetch(): Fetch { + let fetchers: readonly Fetcher[] = _fetchers; + return async (url, options) => { + const result = await fetchWithFallbacks(fetchers, url, options, options.logger); + if (result.updatedFetchers) { + fetchers = result.updatedFetchers; + } + return result.response; + }; +} + +function shouldNotRetry(status: number): boolean { + // Don't retry with other fetchers for these HTTP status codes: + // - 429 Too Many Requests (rate limiting) + // - 401 Unauthorized (authentication issue) + // - 403 Forbidden (authorization issue) + // - 404 Not Found (resource doesn't exist) + // These are application-level errors where retrying with a different fetcher won't help + return status === 429 || status === 401 || status === 403 || status === 404; +} + +async function fetchWithFallbacks(availableFetchers: readonly Fetcher[], url: string, options: FetchOptions, logService: Log): Promise<{ response: FetchResponse; updatedFetchers?: Fetcher[] }> { + if (options.retryFallbacks && availableFetchers.length > 1) { + let firstResult: { ok: boolean; response: FetchResponse } | { ok: false; err: any } | undefined; + for (const fetcher of availableFetchers) { + const result = await tryFetch(fetcher, url, options, logService); + if (fetcher === availableFetchers[0]) { + firstResult = result; + } + if (!result.ok) { + // For certain HTTP status codes, don't retry with other fetchers + // These are application-level errors, not network-level errors + if ('response' in result && shouldNotRetry(result.response.status)) { + return { response: result.response }; + } + continue; + } + if (fetcher !== availableFetchers[0]) { + const retry = await tryFetch(availableFetchers[0], url, options, logService); + if (retry.ok) { + return { response: retry.response }; + } + logService.info(`FetcherService: using ${fetcher.name} from now on`); + const updatedFetchers = availableFetchers.slice(); + updatedFetchers.splice(updatedFetchers.indexOf(fetcher), 1); + updatedFetchers.unshift(fetcher); + return { response: result.response, updatedFetchers }; + } + return { response: result.response }; + } + if ('response' in firstResult!) { + return { response: firstResult.response }; + } + throw firstResult!.err; + } + return { response: await availableFetchers[0].fetch(url, options) }; +} + +async function tryFetch(fetcher: Fetcher, url: string, options: FetchOptions, logService: Log): Promise<{ ok: boolean; response: FetchResponse } | { ok: false; err: any }> { + try { + logService.debug(`FetcherService: trying fetcher ${fetcher.name} for ${url}`); + const response = await fetcher.fetch(url, options); + if (!response.ok) { + logService.info(`FetcherService: ${fetcher.name} failed with status: ${response.status} ${response.statusText}`); + return { ok: false, response }; + } + if (!options.expectJSON) { + logService.debug(`FetcherService: ${fetcher.name} succeeded (not JSON)`); + return { ok: response.ok, response }; + } + const text = await response.text(); + try { + const json = JSON.parse(text); // Verify JSON + logService.debug(`FetcherService: ${fetcher.name} succeeded (JSON)`); + return { ok: true, response: new FetchResponseImpl(response.status, response.statusText, response.headers, async () => text, async () => json, async () => Readable.from([text])) }; + } catch (err) { + logService.info(`FetcherService: ${fetcher.name} failed to parse JSON: ${err.message}`); + return { ok: false, err, response: new FetchResponseImpl(response.status, response.statusText, response.headers, async () => text, async () => { throw err; }, async () => Readable.from([text])) }; + } + } catch (err) { + logService.info(`FetcherService: ${fetcher.name} failed with error: ${err.message}`); + return { ok: false, err }; + } +} + +export const fetching = createFetch(); + +class FetchResponseImpl implements FetchResponse { + public readonly ok: boolean; + constructor( + public readonly status: number, + public readonly statusText: string, + public readonly headers: FetchHeaders, + public readonly text: () => Promise, + public readonly json: () => Promise, + public readonly body: () => Promise, + ) { + this.ok = this.status >= 200 && this.status < 300; + } +} + +async function nodeHTTP(url: string, options: FetchOptions): Promise { + return new Promise((resolve, reject) => { + const { method, headers, body, signal } = options; + const module = url.startsWith('https:') ? https : http; + const req = module.request(url, { method, headers }, res => { + if (signal?.aborted) { + res.destroy(); + req.destroy(); + reject(makeAbortError(signal)); + return; + } + + const nodeFetcherResponse = new NodeFetcherResponse(req, res, signal); + resolve(new FetchResponseImpl( + res.statusCode || 0, + res.statusMessage || '', + nodeFetcherResponse.headers, + async () => nodeFetcherResponse.text(), + async () => nodeFetcherResponse.json(), + async () => nodeFetcherResponse.body(), + )); + }); + req.setTimeout(60 * 1000); // time out after 60s of receiving no data + req.on('error', reject); + + if (body) { + req.write(body); + } + req.end(); + }); +} + +class NodeFetcherResponse { + + readonly headers: FetchHeaders; + + constructor( + readonly req: http.ClientRequest, + readonly res: http.IncomingMessage, + readonly signal: AbortSignal | undefined, + ) { + this.headers = new class implements FetchHeaders { + get(name: string): string | null { + const result = res.headers[name]; + return Array.isArray(result) ? result[0] : result ?? null; + } + [Symbol.iterator](): Iterator<[string, string], any, undefined> { + const keys = Object.keys(res.headers); + let index = 0; + return { + next: (): IteratorResult<[string, string]> => { + if (index >= keys.length) { + return { done: true, value: undefined }; + } + const key = keys[index++]; + return { done: false, value: [key, this.get(key)!] }; + } + }; + } + }; + } + + public text(): Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + this.res.on('data', chunk => chunks.push(chunk)); + this.res.on('end', () => resolve(Buffer.concat(chunks).toString())); + this.res.on('error', reject); + this.signal?.addEventListener('abort', () => { + this.res.destroy(); + this.req.destroy(); + reject(makeAbortError(this.signal!)); + }); + }); + } + + public async json(): Promise { + const text = await this.text(); + return JSON.parse(text); + } + + public async body(): Promise { + this.signal?.addEventListener('abort', () => { + this.res.emit('error', makeAbortError(this.signal!)); + this.res.destroy(); + this.req.destroy(); + }); + return this.res; + } +} + +function makeAbortError(signal: AbortSignal): Error { + // see https://github.com/nodejs/node/issues/38361#issuecomment-1683839467 + return signal.reason; } -export const fetching = _fetch; diff --git a/extensions/github-authentication/src/test/flows.test.ts b/extensions/github-authentication/src/test/flows.test.ts index 77c023e4819f2..fdcdd0f3f45c1 100644 --- a/extensions/github-authentication/src/test/flows.test.ts +++ b/extensions/github-authentication/src/test/flows.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import { ExtensionHost, GitHubTarget, IFlowQuery, getFlows } from '../flows'; import { Config } from '../config'; +import * as vscode from 'vscode'; const enum Flows { UrlHandlerFlow = 'url handler', @@ -193,4 +194,68 @@ suite('getFlows', () => { } }); } + + suite('preferDeviceCodeFlow configuration', () => { + let originalConfig: boolean | undefined; + + suiteSetup(async () => { + const config = vscode.workspace.getConfiguration('github-authentication'); + originalConfig = config.get('preferDeviceCodeFlow'); + }); + + suiteTeardown(async () => { + const config = vscode.workspace.getConfiguration('github-authentication'); + await config.update('preferDeviceCodeFlow', originalConfig, vscode.ConfigurationTarget.Global); + }); + + test('returns device code flow first when preferDeviceCodeFlow is true - VS Code Desktop', async () => { + const config = vscode.workspace.getConfiguration('github-authentication'); + await config.update('preferDeviceCodeFlow', true, vscode.ConfigurationTarget.Global); + + const flows = getFlows({ + extensionHost: ExtensionHost.Local, + isSupportedClient: true, + target: GitHubTarget.DotCom + }); + + // Should return device code flow first, then other flows + assert.strictEqual(flows.length, 3, `Expected 3 flows, got ${flows.length}: ${flows.map(f => f.label).join(',')}`); + assert.strictEqual(flows[0].label, Flows.DeviceCodeFlow); + // Other flows should still be available + assert.strictEqual(flows[1].label, Flows.LocalServerFlow); + assert.strictEqual(flows[2].label, Flows.UrlHandlerFlow); + }); + + test('returns device code flow first when preferDeviceCodeFlow is true - Remote', async () => { + const config = vscode.workspace.getConfiguration('github-authentication'); + await config.update('preferDeviceCodeFlow', true, vscode.ConfigurationTarget.Global); + + const flows = getFlows({ + extensionHost: ExtensionHost.Remote, + isSupportedClient: true, + target: GitHubTarget.DotCom + }); + + // Should return device code flow first, then other flows + assert.strictEqual(flows.length, 2, `Expected 2 flows, got ${flows.length}: ${flows.map(f => f.label).join(',')}`); + assert.strictEqual(flows[0].label, Flows.DeviceCodeFlow); + assert.strictEqual(flows[1].label, Flows.UrlHandlerFlow); + }); + + test('returns normal flows when preferDeviceCodeFlow is true but device code flow is not supported - WebWorker', async () => { + const config = vscode.workspace.getConfiguration('github-authentication'); + await config.update('preferDeviceCodeFlow', true, vscode.ConfigurationTarget.Global); + + const flows = getFlows({ + extensionHost: ExtensionHost.WebWorker, + isSupportedClient: true, + target: GitHubTarget.DotCom + }); + + // WebWorker doesn't support DeviceCodeFlow, so should return normal flows + // Based on the original logic, WebWorker + DotCom should return UrlHandlerFlow + assert.strictEqual(flows.length, 1, `Expected 1 flow for WebWorker configuration, got ${flows.length}: ${flows.map(f => f.label).join(',')}`); + assert.strictEqual(flows[0].label, Flows.UrlHandlerFlow); + }); + }); }); diff --git a/extensions/github-authentication/src/test/node/fetch.test.ts b/extensions/github-authentication/src/test/node/fetch.test.ts new file mode 100644 index 0000000000000..211b133e406b5 --- /dev/null +++ b/extensions/github-authentication/src/test/node/fetch.test.ts @@ -0,0 +1,189 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as http from 'http'; +import * as net from 'net'; +import { createFetch } from '../../node/fetch'; +import { Log } from '../../common/logger'; +import { AuthProviderType } from '../../github'; + + +suite('fetching', () => { + const logger = new Log(AuthProviderType.github); + let server: http.Server; + let port: number; + + setup(async () => { + await new Promise((resolve) => { + server = http.createServer((req, res) => { + const reqUrl = new URL(req.url!, `http://${req.headers.host}`); + const expectAgent = reqUrl.searchParams.get('expectAgent'); + const actualAgent = String(req.headers['user-agent']).toLowerCase(); + if (expectAgent && !actualAgent.includes(expectAgent)) { + if (reqUrl.searchParams.get('error') === 'html') { + res.writeHead(200, { + 'Content-Type': 'text/html', + 'X-Client-User-Agent': actualAgent, + }); + res.end('

Bad Request

'); + return; + } else { + res.writeHead(400, { + 'X-Client-User-Agent': actualAgent, + }); + res.end('Bad Request'); + return; + } + } + switch (reqUrl.pathname) { + case '/json': { + res.writeHead(200, { + 'Content-Type': 'application/json', + 'X-Client-User-Agent': actualAgent, + }); + res.end(JSON.stringify({ message: 'Hello, world!' })); + break; + } + case '/text': { + res.writeHead(200, { + 'Content-Type': 'text/plain', + 'X-Client-User-Agent': actualAgent, + }); + res.end('Hello, world!'); + break; + } + default: + res.writeHead(404); + res.end('Not Found'); + break; + } + }).listen(() => { + port = (server.address() as net.AddressInfo).port; + resolve(); + }); + }); + }); + + teardown(async () => { + await new Promise((resolve) => { + server.close(resolve); + }); + }); + + test('should use Electron fetch', async () => { + const res = await createFetch()(`http://localhost:${port}/json`, { + logger, + retryFallbacks: true, + expectJSON: true, + }); + const actualAgent = res.headers.get('x-client-user-agent') || 'None'; + assert.ok(actualAgent.includes('electron'), actualAgent); + assert.strictEqual(res.status, 200); + assert.deepStrictEqual(await res.json(), { message: 'Hello, world!' }); + }); + + test('should use Electron fetch 2', async () => { + const res = await createFetch()(`http://localhost:${port}/text`, { + logger, + retryFallbacks: true, + expectJSON: false, + }); + const actualAgent = res.headers.get('x-client-user-agent') || 'None'; + assert.ok(actualAgent.includes('electron'), actualAgent); + assert.strictEqual(res.status, 200); + assert.deepStrictEqual(await res.text(), 'Hello, world!'); + }); + + test('should fall back to Node.js fetch', async () => { + const res = await createFetch()(`http://localhost:${port}/json?expectAgent=node`, { + logger, + retryFallbacks: true, + expectJSON: true, + }); + const actualAgent = res.headers.get('x-client-user-agent') || 'None'; + assert.strictEqual(actualAgent, 'node'); + assert.strictEqual(res.status, 200); + assert.deepStrictEqual(await res.json(), { message: 'Hello, world!' }); + }); + + test('should fall back to Node.js fetch 2', async () => { + const res = await createFetch()(`http://localhost:${port}/json?expectAgent=node&error=html`, { + logger, + retryFallbacks: true, + expectJSON: true, + }); + const actualAgent = res.headers.get('x-client-user-agent') || 'None'; + assert.strictEqual(actualAgent, 'node'); + assert.strictEqual(res.status, 200); + assert.deepStrictEqual(await res.json(), { message: 'Hello, world!' }); + }); + + test('should fall back to Node.js http/s', async () => { + const res = await createFetch()(`http://localhost:${port}/json?expectAgent=undefined`, { + logger, + retryFallbacks: true, + expectJSON: true, + }); + const actualAgent = res.headers.get('x-client-user-agent') || 'None'; + assert.strictEqual(actualAgent, 'undefined'); + assert.strictEqual(res.status, 200); + assert.deepStrictEqual(await res.json(), { message: 'Hello, world!' }); + }); + + test('should fail with first error', async () => { + const res = await createFetch()(`http://localhost:${port}/text`, { + logger, + retryFallbacks: true, + expectJSON: true, // Expect JSON but server returns text + }); + const actualAgent = res.headers.get('x-client-user-agent') || 'None'; + assert.ok(actualAgent.includes('electron'), actualAgent); + assert.strictEqual(res.status, 200); + assert.deepStrictEqual(await res.text(), 'Hello, world!'); + }); + + test('should not retry with other fetchers on 429 status', async () => { + // Set up server to return 429 for the first request + let requestCount = 0; + const oldListener = server.listeners('request')[0] as (req: http.IncomingMessage, res: http.ServerResponse) => void; + if (!oldListener) { + throw new Error('No request listener found on server'); + } + + server.removeAllListeners('request'); + server.on('request', (req, res) => { + requestCount++; + if (req.url === '/rate-limited') { + res.writeHead(429, { + 'Content-Type': 'text/plain', + 'X-Client-User-Agent': String(req.headers['user-agent'] ?? '').toLowerCase(), + }); + res.end('Too Many Requests'); + } else { + oldListener(req, res); + } + }); + + try { + const res = await createFetch()(`http://localhost:${port}/rate-limited`, { + logger, + retryFallbacks: true, + expectJSON: false, + }); + + // Verify only one request was made (no fallback attempts) + assert.strictEqual(requestCount, 1, 'Should only make one request for 429 status'); + assert.strictEqual(res.status, 429); + // Note: We only check that we got a response, not which fetcher was used, + // as the fetcher order may vary by configuration + assert.strictEqual(await res.text(), 'Too Many Requests'); + } finally { + // Restore original listener + server.removeAllListeners('request'); + server.on('request', oldListener); + } + }); +}); diff --git a/extensions/github-authentication/tsconfig.json b/extensions/github-authentication/tsconfig.json index 17c9dbad6103b..1a65fe8109588 100644 --- a/extensions/github-authentication/tsconfig.json +++ b/extensions/github-authentication/tsconfig.json @@ -2,7 +2,6 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "experimentalDecorators": true, "typeRoots": [ "./node_modules/@types" ], diff --git a/extensions/github/.vscodeignore b/extensions/github/.vscodeignore index 24b1bd4e73a06..54d237c1f3ec4 100644 --- a/extensions/github/.vscodeignore +++ b/extensions/github/.vscodeignore @@ -5,3 +5,4 @@ build/** extension.webpack.config.js tsconfig.json package-lock.json +testWorkspace/** diff --git a/extensions/github/extension.webpack.config.cjs b/extensions/github/extension.webpack.config.cjs deleted file mode 100644 index 75b86c82b68d5..0000000000000 --- a/extensions/github/extension.webpack.config.cjs +++ /dev/null @@ -1,27 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, - entry: { - extension: './src/extension.ts' - }, - output: { - libraryTarget: 'module', - chunkFormat: 'module', - }, - externals: { - 'vscode': 'module vscode', - }, - experiments: { - outputModule: true - } -}); diff --git a/extensions/github/extension.webpack.config.js b/extensions/github/extension.webpack.config.js new file mode 100644 index 0000000000000..9e2b191a389d4 --- /dev/null +++ b/extensions/github/extension.webpack.config.js @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; + +export default withDefaults({ + context: import.meta.dirname, + entry: { + extension: './src/extension.ts' + }, + output: { + libraryTarget: 'module', + chunkFormat: 'module', + }, + externals: { + 'vscode': 'module vscode', + }, + experiments: { + outputModule: true + } +}); diff --git a/extensions/github/package.json b/extensions/github/package.json index 726a882ebc35b..cd70cfea26b65 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -157,13 +157,6 @@ "group": "0_view@2" } ], - "scm/historyItem/hover": [ - { - "command": "github.graph.openOnGitHub", - "when": "github.hasGitHubRepo", - "group": "1_open@1" - } - ], "timeline/item/context": [ { "command": "github.timeline.openOnGitHub", diff --git a/extensions/github/src/historyItemDetailsProvider.ts b/extensions/github/src/historyItemDetailsProvider.ts index 1a5d58a1c52ff..9a267b9e8443b 100644 --- a/extensions/github/src/historyItemDetailsProvider.ts +++ b/extensions/github/src/historyItemDetailsProvider.ts @@ -114,7 +114,8 @@ export class GitHubSourceControlHistoryItemDetailsProvider implements SourceCont return undefined; } - const descriptor = getRepositoryDefaultRemote(repository); + // upstream -> origin -> first + const descriptor = getRepositoryDefaultRemote(repository, ['upstream', 'origin']); if (!descriptor) { this._logger.trace(`[GitHubSourceControlHistoryItemDetailsProvider][provideAvatar] Repository does not have a GitHub remote.`); return undefined; @@ -206,7 +207,8 @@ export class GitHubSourceControlHistoryItemDetailsProvider implements SourceCont } async provideHoverCommands(repository: Repository): Promise { - const url = getRepositoryDefaultRemoteUrl(repository); + // origin -> upstream -> first + const url = getRepositoryDefaultRemoteUrl(repository, ['origin', 'upstream']); if (!url) { return undefined; } @@ -220,7 +222,8 @@ export class GitHubSourceControlHistoryItemDetailsProvider implements SourceCont } async provideMessageLinks(repository: Repository, message: string): Promise { - const descriptor = getRepositoryDefaultRemote(repository); + // upstream -> origin -> first + const descriptor = getRepositoryDefaultRemote(repository, ['upstream', 'origin']); if (!descriptor) { return undefined; } diff --git a/extensions/github/src/links.ts b/extensions/github/src/links.ts index 8eb0f6b23f641..b4f8379e5f79e 100644 --- a/extensions/github/src/links.ts +++ b/extensions/github/src/links.ts @@ -47,12 +47,12 @@ interface EditorLineNumberContext { export type LinkContext = vscode.Uri | EditorLineNumberContext | undefined; function extractContext(context: LinkContext): { fileUri: vscode.Uri | undefined; lineNumber: number | undefined } { - if (context instanceof vscode.Uri) { + if (context === undefined) { + return { fileUri: undefined, lineNumber: undefined }; + } else if (context instanceof vscode.Uri) { return { fileUri: context, lineNumber: undefined }; - } else if (context !== undefined && 'lineNumber' in context && 'uri' in context) { - return { fileUri: context.uri, lineNumber: context.lineNumber }; } else { - return { fileUri: undefined, lineNumber: undefined }; + return { fileUri: context.uri, lineNumber: context.lineNumber }; } } diff --git a/extensions/github/src/remoteSourceProvider.ts b/extensions/github/src/remoteSourceProvider.ts index 291a3f1a6bace..bed2bb1aa6b1c 100644 --- a/extensions/github/src/remoteSourceProvider.ts +++ b/extensions/github/src/remoteSourceProvider.ts @@ -10,7 +10,15 @@ import { Octokit } from '@octokit/rest'; import { getRepositoryFromQuery, getRepositoryFromUrl } from './util.js'; import { getBranchLink, getVscodeDevHost } from './links.js'; -function asRemoteSource(raw: any): RemoteSource { +type RemoteSourceResponse = { + readonly full_name: string; + readonly description: string | null; + readonly stargazers_count: number; + readonly clone_url: string; + readonly ssh_url: string; +}; + +function asRemoteSource(raw: RemoteSourceResponse): RemoteSource { const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); return { name: `$(github) ${raw.full_name}`, diff --git a/extensions/github/src/test/github.test.ts b/extensions/github/src/test/github.test.ts index db0eba515cbbb..838fd37923ef2 100644 --- a/extensions/github/src/test/github.test.ts +++ b/extensions/github/src/test/github.test.ts @@ -39,11 +39,11 @@ suite('github smoke test', function () { }); test('selecting non-default quick-pick item should correspond to a template', async () => { - const template0 = Uri.file("some-imaginary-template-0"); - const template1 = Uri.file("some-imaginary-template-1"); + const template0 = Uri.file('some-imaginary-template-0'); + const template1 = Uri.file('some-imaginary-template-1'); const templates = [template0, template1]; - const pick = pickPullRequestTemplate(Uri.file("/"), templates); + const pick = pickPullRequestTemplate(Uri.file('/'), templates); await commands.executeCommand('workbench.action.quickOpenSelectNext'); await commands.executeCommand('workbench.action.quickOpenSelectNext'); @@ -53,9 +53,9 @@ suite('github smoke test', function () { }); test('selecting first quick-pick item should return undefined', async () => { - const templates = [Uri.file("some-imaginary-file")]; + const templates = [Uri.file('some-imaginary-file')]; - const pick = pickPullRequestTemplate(Uri.file("/"), templates); + const pick = pickPullRequestTemplate(Uri.file('/'), templates); await commands.executeCommand('workbench.action.quickOpenSelectNext'); await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); diff --git a/extensions/github/src/util.ts b/extensions/github/src/util.ts index a06eeba869249..f7f54ec5f3f4b 100644 --- a/extensions/github/src/util.ts +++ b/extensions/github/src/util.ts @@ -24,23 +24,11 @@ export class DisposableStore { } function decorate(decorator: (fn: Function, key: string) => Function): Function { - return (_target: any, key: string, descriptor: any) => { - let fnKey: string | null = null; - let fn: Function | null = null; - - if (typeof descriptor.value === 'function') { - fnKey = 'value'; - fn = descriptor.value; - } else if (typeof descriptor.get === 'function') { - fnKey = 'get'; - fn = descriptor.get; + return function (original: any, context: ClassMethodDecoratorContext) { + if (context.kind === 'method' || context.kind === 'getter' || context.kind === 'setter') { + return decorator(original, context.name.toString()); } - - if (!fn || !fnKey) { - throw new Error('not supported'); - } - - descriptor[fnKey] = decorator(fn, key); + throw new Error('not supported'); }; } @@ -86,7 +74,7 @@ export function repositoryHasGitHubRemote(repository: Repository) { return !!repository.state.remotes.find(remote => remote.fetchUrl ? getRepositoryFromUrl(remote.fetchUrl) : undefined); } -export function getRepositoryDefaultRemoteUrl(repository: Repository): string | undefined { +export function getRepositoryDefaultRemoteUrl(repository: Repository, order: string[]): string | undefined { const remotes = repository.state.remotes .filter(remote => remote.fetchUrl && getRepositoryFromUrl(remote.fetchUrl)); @@ -94,15 +82,20 @@ export function getRepositoryDefaultRemoteUrl(repository: Repository): string | return undefined; } - // origin -> upstream -> first - const remote = remotes.find(remote => remote.name === 'origin') - ?? remotes.find(remote => remote.name === 'upstream') - ?? remotes[0]; + for (const name of order) { + const remote = remotes + .find(remote => remote.name === name); + + if (remote) { + return remote.fetchUrl; + } + } - return remote.fetchUrl; + // Fallback to first remote + return remotes[0].fetchUrl; } -export function getRepositoryDefaultRemote(repository: Repository): { owner: string; repo: string } | undefined { - const fetchUrl = getRepositoryDefaultRemoteUrl(repository); +export function getRepositoryDefaultRemote(repository: Repository, order: string[]): { owner: string; repo: string } | undefined { + const fetchUrl = getRepositoryDefaultRemoteUrl(repository, order); return fetchUrl ? getRepositoryFromUrl(fetchUrl) : undefined; } diff --git a/extensions/github/tsconfig.json b/extensions/github/tsconfig.json index f8459d0f629a1..c82524f6d26ec 100644 --- a/extensions/github/tsconfig.json +++ b/extensions/github/tsconfig.json @@ -5,8 +5,6 @@ "moduleResolution": "NodeNext", "outDir": "./out", "skipLibCheck": true, - "experimentalDecorators": true, - "allowSyntheticDefaultImports": false, "typeRoots": [ "./node_modules/@types" ] diff --git a/extensions/grunt/extension.webpack.config.js b/extensions/grunt/extension.webpack.config.js index beee7b3d6762e..1e221c2fa8500 100644 --- a/extensions/grunt/extension.webpack.config.js +++ b/extensions/grunt/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { main: './src/main.ts', }, diff --git a/extensions/grunt/src/main.ts b/extensions/grunt/src/main.ts index fd99ba335c420..b94b00c44621e 100644 --- a/extensions/grunt/src/main.ts +++ b/extensions/grunt/src/main.ts @@ -120,7 +120,7 @@ class FolderDetector { } public async getTask(_task: vscode.Task): Promise { - const taskDefinition = _task.definition; + const taskDefinition = _task.definition; const gruntTask = taskDefinition.task; if (gruntTask) { const options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath }; diff --git a/extensions/grunt/tsconfig.json b/extensions/grunt/tsconfig.json index 7234fdfeb9757..22c47de77dbe1 100644 --- a/extensions/grunt/tsconfig.json +++ b/extensions/grunt/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/gulp/extension.webpack.config.js b/extensions/gulp/extension.webpack.config.js index beee7b3d6762e..1e221c2fa8500 100644 --- a/extensions/gulp/extension.webpack.config.js +++ b/extensions/gulp/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { main: './src/main.ts', }, diff --git a/extensions/gulp/src/main.ts b/extensions/gulp/src/main.ts index b0b85ca29b96e..c83bdb65897eb 100644 --- a/extensions/gulp/src/main.ts +++ b/extensions/gulp/src/main.ts @@ -150,9 +150,9 @@ class FolderDetector { } public async getTask(_task: vscode.Task): Promise { - const gulpTask = (_task.definition).task; + const gulpTask = _task.definition.task; if (gulpTask) { - const kind: GulpTaskDefinition = (_task.definition); + const kind = _task.definition as GulpTaskDefinition; const options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath }; const task = new vscode.Task(kind, this.workspaceFolder, gulpTask, 'gulp', new vscode.ShellExecution(await this._gulpCommand, [gulpTask], options)); return task; diff --git a/extensions/gulp/tsconfig.json b/extensions/gulp/tsconfig.json index 7234fdfeb9757..22c47de77dbe1 100644 --- a/extensions/gulp/tsconfig.json +++ b/extensions/gulp/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 2b0f961899bc8..54fc91469da62 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -179,8 +179,9 @@ async function startClientWithParticipants(languageParticipants: LanguagePartici } return r; } - const isThenable = (obj: ProviderResult): obj is Thenable => obj && (obj)['then']; - + function isThenable(obj: unknown): obj is Thenable { + return !!obj && typeof (obj as unknown as Thenable).then === 'function'; + } const r = next(document, position, context, token); if (isThenable(r)) { return r.then(updateProposals); diff --git a/extensions/html-language-features/client/tsconfig.json b/extensions/html-language-features/client/tsconfig.json index 615adaeea046e..051d5823fe564 100644 --- a/extensions/html-language-features/client/tsconfig.json +++ b/extensions/html-language-features/client/tsconfig.json @@ -6,6 +6,9 @@ "webworker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/html-language-features/extension-browser.webpack.config.js b/extensions/html-language-features/extension-browser.webpack.config.js index 9988ab826aaf4..2d9f2d1281485 100644 --- a/extensions/html-language-features/extension-browser.webpack.config.js +++ b/extensions/html-language-features/extension-browser.webpack.config.js @@ -2,21 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; +import path from 'path'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; -const path = require('path'); - -module.exports = withBrowserDefaults({ - context: path.join(__dirname, 'client'), +export default withBrowserDefaults({ + context: path.join(import.meta.dirname, 'client'), entry: { extension: './src/browser/htmlClientMain.ts' }, output: { filename: 'htmlClientMain.js', - path: path.join(__dirname, 'client', 'dist', 'browser') + path: path.join(import.meta.dirname, 'client', 'dist', 'browser') } }); diff --git a/extensions/html-language-features/extension.webpack.config.js b/extensions/html-language-features/extension.webpack.config.js index 6af444db93064..1bb6632e92b09 100644 --- a/extensions/html-language-features/extension.webpack.config.js +++ b/extensions/html-language-features/extension.webpack.config.js @@ -2,21 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; +import path from 'path'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); -const path = require('path'); - -module.exports = withDefaults({ - context: path.join(__dirname, 'client'), +export default withDefaults({ + context: path.join(import.meta.dirname, 'client'), entry: { extension: './src/node/htmlClientMain.ts', }, output: { filename: 'htmlClientMain.js', - path: path.join(__dirname, 'client', 'dist', 'node') + path: path.join(import.meta.dirname, 'client', 'dist', 'node') } }); diff --git a/extensions/html-language-features/package-lock.json b/extensions/html-language-features/package-lock.json index f9cb28b91791d..442b79121ebb7 100644 --- a/extensions/html-language-features/package-lock.json +++ b/extensions/html-language-features/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", - "vscode-languageclient": "^10.0.0-next.16", + "vscode-languageclient": "^10.0.0-next.18", "vscode-uri": "^3.1.0" }, "devDependencies": { @@ -20,6 +20,27 @@ "vscode": "^1.77.0" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@microsoft/1ds-core-js": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", @@ -168,28 +189,13 @@ "vscode": "^1.75.0" } }, - "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==", - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { "node": "20 || >=22" @@ -218,35 +224,35 @@ "license": "MIT" }, "node_modules/vscode-jsonrpc": { - "version": "9.0.0-next.8", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.8.tgz", - "integrity": "sha512-pN6L5eiNBvUpNFBJvudaZ83klir0T/wLFCDpYhpOEsKXyhsWyYsNMzoG7BK6zJoZLHGSSsaTJDjCcPwnLgUyPQ==", + "version": "9.0.0-next.10", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.10.tgz", + "integrity": "sha512-P+UOjuG/B1zkLM+bGIdmBwSkDejxtgo6EjG0pIkwnFBI0a2Mb7od36uUu8CPbECeQuh+n3zGcNwDl16DhuJ5IA==", "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/vscode-languageclient": { - "version": "10.0.0-next.16", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.16.tgz", - "integrity": "sha512-aVJ950olGncxehPezP61wsEHjB3zgDETCThH1FTQ4V9EZ9mcVSNfjXM0SWC+VYF3nZulI2hBZe0od2Ajib4hNA==", + "version": "10.0.0-next.18", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.18.tgz", + "integrity": "sha512-Dpcr0VEEf4SuMW17TFCuKovhvbCx6/tHTnmFyLW1KTJCdVmNG08hXVAmw8Z/izec7TQlzEvzw5PvRfYGzdtr5Q==", "license": "MIT", "dependencies": { - "minimatch": "^10.0.1", + "minimatch": "^10.0.3", "semver": "^7.7.1", - "vscode-languageserver-protocol": "3.17.6-next.13" + "vscode-languageserver-protocol": "3.17.6-next.15" }, "engines": { "vscode": "^1.91.0" } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.17.6-next.13", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.13.tgz", - "integrity": "sha512-IE+/j+OOqJ392KMhcexIGt9MVqcTZ4n7DVyaSp5txuC1kNUnfzxlkPzzDwo0p7hdINLCfWjbcjuW5tGYLof4Vw==", + "version": "3.17.6-next.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.15.tgz", + "integrity": "sha512-aoWX1wwGCndzfrTRhGKVpKAPVy9+WYhUtZW/PJQfHODmVwhVwb4we68CgsQZRTl36t8ZqlSOO2c2TdBPW7hrCw==", "license": "MIT", "dependencies": { - "vscode-jsonrpc": "9.0.0-next.8", + "vscode-jsonrpc": "9.0.0-next.10", "vscode-languageserver-types": "3.17.6-next.6" } }, diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index 4380032fadaeb..4e630b7273623 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -182,6 +182,12 @@ "default": true, "description": "%html.suggest.html5.desc%" }, + "html.suggest.hideEndTagSuggestions": { + "type": "boolean", + "scope": "resource", + "default": false, + "description": "%html.suggest.hideEndTagSuggestions.desc%" + }, "html.validate.scripts": { "type": "boolean", "scope": "resource", @@ -259,7 +265,7 @@ }, "dependencies": { "@vscode/extension-telemetry": "^0.9.8", - "vscode-languageclient": "^10.0.0-next.16", + "vscode-languageclient": "^10.0.0-next.18", "vscode-uri": "^3.1.0" }, "devDependencies": { diff --git a/extensions/html-language-features/package.nls.json b/extensions/html-language-features/package.nls.json index f36ecf34f023f..d8390703757af 100644 --- a/extensions/html-language-features/package.nls.json +++ b/extensions/html-language-features/package.nls.json @@ -23,6 +23,7 @@ "html.format.unformattedContentDelimiter.desc": "Keep text content together between this string.", "html.format.wrapAttributesIndentSize.desc": "Indent wrapped attributes to after N characters. Use `null` to use the default indent size. Ignored if `#html.format.wrapAttributes#` is set to `aligned`.", "html.suggest.html5.desc": "Controls whether the built-in HTML language support suggests HTML5 tags, properties and values.", + "html.suggest.hideEndTagSuggestions.desc": "Controls whether the built-in HTML language support suggests closing tags. When disabled, end tag completions like `` will not be shown.", "html.trace.server.desc": "Traces the communication between VS Code and the HTML language server.", "html.validate.scripts": "Controls whether the built-in HTML language support validates embedded scripts.", "html.validate.styles": "Controls whether the built-in HTML language support validates embedded styles.", diff --git a/extensions/html-language-features/server/extension-browser.webpack.config.js b/extensions/html-language-features/server/extension-browser.webpack.config.js index 07848486cbd9c..7aa12ea0ff7d8 100644 --- a/extensions/html-language-features/server/extension-browser.webpack.config.js +++ b/extensions/html-language-features/server/extension-browser.webpack.config.js @@ -2,16 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../../shared.webpack.config').browser; -const path = require('path'); +// @ts-check +import { browser as withBrowserDefaults } from '../../shared.webpack.config.mjs'; +import path from 'path'; const serverConfig = withBrowserDefaults({ - context: __dirname, + context: import.meta.dirname, entry: { extension: './src/browser/htmlServerWorkerMain.ts', }, @@ -23,7 +19,7 @@ const serverConfig = withBrowserDefaults({ }, output: { filename: 'htmlServerMain.js', - path: path.join(__dirname, 'dist', 'browser'), + path: path.join(import.meta.dirname, 'dist', 'browser'), libraryTarget: 'var', library: 'serverExportVar' }, @@ -38,9 +34,9 @@ serverConfig.module.rules.push({ test: /javascriptLibs.ts$/, use: [ { - loader: path.resolve(__dirname, 'build', 'javaScriptLibraryLoader.js') + loader: path.resolve(import.meta.dirname, 'build', 'javaScriptLibraryLoader.js') } ] }); -module.exports = serverConfig; +export default serverConfig; diff --git a/extensions/html-language-features/server/extension.webpack.config.js b/extensions/html-language-features/server/extension.webpack.config.js index 61289a4993c15..491327c8bc3b1 100644 --- a/extensions/html-language-features/server/extension.webpack.config.js +++ b/extensions/html-language-features/server/extension.webpack.config.js @@ -2,22 +2,18 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../../shared.webpack.config.mjs'; +import path from 'path'; -//@ts-check - -'use strict'; - -const withDefaults = require('../../shared.webpack.config'); -const path = require('path'); - -module.exports = withDefaults({ - context: path.join(__dirname), +export default withDefaults({ + context: path.join(import.meta.dirname), entry: { extension: './src/node/htmlServerNodeMain.ts', }, output: { filename: 'htmlServerMain.js', - path: path.join(__dirname, 'dist', 'node'), + path: path.join(import.meta.dirname, 'dist', 'node'), }, externals: { 'typescript': 'commonjs typescript' diff --git a/extensions/html-language-features/server/package-lock.json b/extensions/html-language-features/server/package-lock.json index 4dbf88130c079..dc257f8b9d78f 100644 --- a/extensions/html-language-features/server/package-lock.json +++ b/extensions/html-language-features/server/package-lock.json @@ -10,14 +10,14 @@ "license": "MIT", "dependencies": { "@vscode/l10n": "^0.0.18", - "vscode-css-languageservice": "^6.3.7", - "vscode-html-languageservice": "^5.5.1", - "vscode-languageserver": "^10.0.0-next.13", + "vscode-css-languageservice": "^6.3.9", + "vscode-html-languageservice": "^5.6.1", + "vscode-languageserver": "^10.0.0-next.15", "vscode-languageserver-textdocument": "^1.0.12", "vscode-uri": "^3.1.0" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x" }, "engines": { @@ -25,10 +25,11 @@ } }, "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.10", @@ -53,9 +54,9 @@ "license": "MIT" }, "node_modules/vscode-css-languageservice": { - "version": "6.3.7", - "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.7.tgz", - "integrity": "sha512-5TmXHKllPzfkPhW4UE9sODV3E0bIOJPOk+EERKllf2SmAczjfTmYeq5txco+N3jpF8KIZ6loj/JptpHBQuVQRA==", + "version": "6.3.9", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-6.3.9.tgz", + "integrity": "sha512-1tLWfp+TDM5ZuVWht3jmaY5y7O6aZmpeXLoHl5bv1QtRsRKt4xYGRMmdJa5Pqx/FTkgRbsna9R+Gn2xE+evVuA==", "license": "MIT", "dependencies": { "@vscode/l10n": "^0.0.18", @@ -65,9 +66,9 @@ } }, "node_modules/vscode-html-languageservice": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.5.1.tgz", - "integrity": "sha512-/ZdEtsZ3OiFSyL00kmmu7crFV9KwWR+MgpzjsxO60DQH7sIfHZM892C/E4iDd11EKocr+NYuvOA4Y7uc3QzLEA==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.6.1.tgz", + "integrity": "sha512-5Mrqy5CLfFZUgkyhNZLA1Ye5g12Cb/v6VM7SxUzZUaRKWMDz4md+y26PrfRTSU0/eQAl3XpO9m2og+GGtDMuaA==", "license": "MIT", "dependencies": { "@vscode/l10n": "^0.0.18", @@ -77,33 +78,33 @@ } }, "node_modules/vscode-jsonrpc": { - "version": "9.0.0-next.8", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.8.tgz", - "integrity": "sha512-pN6L5eiNBvUpNFBJvudaZ83klir0T/wLFCDpYhpOEsKXyhsWyYsNMzoG7BK6zJoZLHGSSsaTJDjCcPwnLgUyPQ==", + "version": "9.0.0-next.10", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.10.tgz", + "integrity": "sha512-P+UOjuG/B1zkLM+bGIdmBwSkDejxtgo6EjG0pIkwnFBI0a2Mb7od36uUu8CPbECeQuh+n3zGcNwDl16DhuJ5IA==", "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/vscode-languageserver": { - "version": "10.0.0-next.13", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-10.0.0-next.13.tgz", - "integrity": "sha512-4tSufM2XrNrrzBUGPcYh62qBYhm41yFwFZBgJ63I1dPHRh1aZPK65+TcVa3nG0/K62Q9phhk87TWdQFp+UnYFA==", + "version": "10.0.0-next.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-10.0.0-next.15.tgz", + "integrity": "sha512-vs+bwci/lM83ZhrR9t8DcZ2AgS2CKx4i6Yw86teKKkqlzlrYWTixuBd9w6H/UP9s8EGBvii0jnbjQd6wsKJ0ig==", "license": "MIT", "dependencies": { - "vscode-languageserver-protocol": "3.17.6-next.13" + "vscode-languageserver-protocol": "3.17.6-next.15" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.17.6-next.13", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.13.tgz", - "integrity": "sha512-IE+/j+OOqJ392KMhcexIGt9MVqcTZ4n7DVyaSp5txuC1kNUnfzxlkPzzDwo0p7hdINLCfWjbcjuW5tGYLof4Vw==", + "version": "3.17.6-next.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.15.tgz", + "integrity": "sha512-aoWX1wwGCndzfrTRhGKVpKAPVy9+WYhUtZW/PJQfHODmVwhVwb4we68CgsQZRTl36t8ZqlSOO2c2TdBPW7hrCw==", "license": "MIT", "dependencies": { - "vscode-jsonrpc": "9.0.0-next.8", + "vscode-jsonrpc": "9.0.0-next.10", "vscode-languageserver-types": "3.17.6-next.6" } }, diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 3df971c7eefc6..8208d3f22e67e 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -10,14 +10,14 @@ "main": "./out/node/htmlServerMain", "dependencies": { "@vscode/l10n": "^0.0.18", - "vscode-css-languageservice": "^6.3.7", - "vscode-html-languageservice": "^5.5.1", - "vscode-languageserver": "^10.0.0-next.13", + "vscode-css-languageservice": "^6.3.9", + "vscode-html-languageservice": "^5.6.1", + "vscode-languageserver": "^10.0.0-next.15", "vscode-languageserver-textdocument": "^1.0.12", "vscode-uri": "^3.1.0" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x" }, "scripts": { diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts index 22ab2e180762a..877c42f5c7cbf 100644 --- a/extensions/html-language-features/server/src/htmlServer.ts +++ b/extensions/html-language-features/server/src/htmlServer.ts @@ -134,14 +134,15 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // After the server has started the client sends an initialize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities connection.onInitialize((params: InitializeParams): InitializeResult => { - const initializationOptions = params.initializationOptions as any || {}; + const initializationOptions = params.initializationOptions || {}; - workspaceFolders = (params).workspaceFolders; - if (!Array.isArray(workspaceFolders)) { + if (!Array.isArray(params.workspaceFolders)) { workspaceFolders = []; if (params.rootPath) { workspaceFolders.push({ name: '', uri: URI.file(params.rootPath).toString() }); } + } else { + workspaceFolders = params.workspaceFolders; } const handledSchemas = initializationOptions?.handledSchemas as string[] ?? ['file']; @@ -540,6 +541,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) }); connection.languages.onLinkedEditingRange((params, token) => { + // eslint-disable-next-line local/code-no-any-casts return /* todo remove when microsoft/vscode-languageserver-node#700 fixed */ runSafe(runtime, async () => { const document = documents.get(params.textDocument.uri); if (document) { diff --git a/extensions/html-language-features/server/tsconfig.json b/extensions/html-language-features/server/tsconfig.json index 4f24a50855ccd..0b49ec72b8ff7 100644 --- a/extensions/html-language-features/server/tsconfig.json +++ b/extensions/html-language-features/server/tsconfig.json @@ -7,6 +7,9 @@ "WebWorker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*" diff --git a/extensions/ini/package.json b/extensions/ini/package.json index 8523df264c1f8..f4837eb881f22 100644 --- a/extensions/ini/package.json +++ b/extensions/ini/package.json @@ -40,13 +40,11 @@ ".repo" ], "filenames": [ - "gitconfig", - ".env" + "gitconfig" ], "filenamePatterns": [ "**/.config/git/config", - "**/.git/config", - ".*.env" + "**/.git/config" ], "aliases": [ "Properties", diff --git a/extensions/ipynb/.vscodeignore b/extensions/ipynb/.vscodeignore index 2d13f5a0c2264..543420a2de021 100644 --- a/extensions/ipynb/.vscodeignore +++ b/extensions/ipynb/.vscodeignore @@ -7,4 +7,4 @@ extension.webpack.config.js extension-browser.webpack.config.js package-lock.json .gitignore -esbuild.js +esbuild.* diff --git a/extensions/ipynb/esbuild.js b/extensions/ipynb/esbuild.js deleted file mode 100644 index 64b58109fecc7..0000000000000 --- a/extensions/ipynb/esbuild.js +++ /dev/null @@ -1,18 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -//@ts-check - -const path = require('path'); - -const srcDir = path.join(__dirname, 'notebook-src'); -const outDir = path.join(__dirname, 'notebook-out'); - -require('../esbuild-webview-common').run({ - entryPoints: [ - path.join(srcDir, 'cellAttachmentRenderer.ts'), - ], - srcDir, - outdir: outDir, -}, process.argv); diff --git a/extensions/ipynb/esbuild.mjs b/extensions/ipynb/esbuild.mjs new file mode 100644 index 0000000000000..3003959c1eb45 --- /dev/null +++ b/extensions/ipynb/esbuild.mjs @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import path from 'node:path'; +import { run } from '../esbuild-webview-common.mjs'; + +const srcDir = path.join(import.meta.dirname, 'notebook-src'); +const outDir = path.join(import.meta.dirname, 'notebook-out'); + +run({ + entryPoints: [ + path.join(srcDir, 'cellAttachmentRenderer.ts'), + ], + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/ipynb/extension-browser.webpack.config.js b/extensions/ipynb/extension-browser.webpack.config.js index 32fc7de8ee482..08a0edc8ad03e 100644 --- a/extensions/ipynb/extension-browser.webpack.config.js +++ b/extensions/ipynb/extension-browser.webpack.config.js @@ -2,37 +2,33 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; -const path = require('path'); +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; +import path from 'path'; const mainConfig = withBrowserDefaults({ - context: __dirname, + context: import.meta.dirname, entry: { extension: './src/ipynbMain.browser.ts' }, output: { filename: 'ipynbMain.browser.js', - path: path.join(__dirname, 'dist', 'browser') + path: path.join(import.meta.dirname, 'dist', 'browser') } }); const workerConfig = withBrowserDefaults({ - context: __dirname, + context: import.meta.dirname, entry: { notebookSerializerWorker: './src/notebookSerializerWorker.web.ts', }, output: { filename: 'notebookSerializerWorker.js', - path: path.join(__dirname, 'dist', 'browser'), + path: path.join(import.meta.dirname, 'dist', 'browser'), libraryTarget: 'var', library: 'serverExportVar' }, }); -module.exports = [mainConfig, workerConfig]; +export default [mainConfig, workerConfig]; diff --git a/extensions/ipynb/extension.webpack.config.js b/extensions/ipynb/extension.webpack.config.js index aad5f55845a96..2bfea01dd914f 100644 --- a/extensions/ipynb/extension.webpack.config.js +++ b/extensions/ipynb/extension.webpack.config.js @@ -2,25 +2,21 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults, { nodePlugins } from '../shared.webpack.config.mjs'; +import path from 'path'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); -const path = require('path'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { ['ipynbMain.node']: './src/ipynbMain.node.ts', notebookSerializerWorker: './src/notebookSerializerWorker.ts', }, output: { - path: path.resolve(__dirname, 'dist'), + path: path.resolve(import.meta.dirname, 'dist'), filename: '[name].js' }, plugins: [ - ...withDefaults.nodePlugins(__dirname), // add plugins, don't replace inherited + ...nodePlugins(import.meta.dirname), // add plugins, don't replace inherited ] }); diff --git a/extensions/ipynb/package-lock.json b/extensions/ipynb/package-lock.json index 7042d6a22b2e6..8dc9c5a8a42db 100644 --- a/extensions/ipynb/package-lock.json +++ b/extensions/ipynb/package-lock.json @@ -14,7 +14,8 @@ }, "devDependencies": { "@jupyterlab/nbformat": "^3.2.9", - "@types/markdown-it": "12.2.3" + "@types/markdown-it": "12.2.3", + "@types/node": "^22.18.10" }, "engines": { "vscode": "^1.57.0" @@ -65,6 +66,16 @@ "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", "dev": true }, + "node_modules/@types/node": { + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/detect-indent": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", @@ -72,6 +83,13 @@ "engines": { "node": ">=8" } + }, + "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" } } } diff --git a/extensions/ipynb/package.json b/extensions/ipynb/package.json index beb65118eb775..89a24e5cc15c9 100644 --- a/extensions/ipynb/package.json +++ b/extensions/ipynb/package.json @@ -158,7 +158,7 @@ "scripts": { "compile": "npx gulp compile-extension:ipynb && npm run build-notebook", "watch": "npx gulp watch-extension:ipynb", - "build-notebook": "node ./esbuild" + "build-notebook": "node ./esbuild.mjs" }, "dependencies": { "@enonic/fnv-plus": "^1.3.0", @@ -166,7 +166,8 @@ }, "devDependencies": { "@jupyterlab/nbformat": "^3.2.9", - "@types/markdown-it": "12.2.3" + "@types/markdown-it": "12.2.3", + "@types/node": "^22.18.10" }, "repository": { "type": "git", diff --git a/extensions/ipynb/src/common.ts b/extensions/ipynb/src/common.ts index dbd3ea1a618d2..f0330c884408d 100644 --- a/extensions/ipynb/src/common.ts +++ b/extensions/ipynb/src/common.ts @@ -65,3 +65,36 @@ export interface CellMetadata { execution_count?: number | null; } + + +type KeysOfUnionType = T extends T ? keyof T : never; +type FilterType = T extends TTest ? T : never; +type MakeOptionalAndBool = { [K in keyof T]?: boolean }; + +/** + * Type guard that checks if an object has specific keys and narrows the type accordingly. + * + * @param x - The object to check + * @param key - An object with boolean values indicating which keys to check for + * @returns true if all specified keys exist in the object, false otherwise + * + * @example + * ```typescript + * type A = { a: string }; + * type B = { b: number }; + * const obj: A | B = getObject(); + * + * if (hasKey(obj, { a: true })) { + * // obj is now narrowed to type A + * console.log(obj.a); + * } + * ``` + */ +export function hasKey(x: T, key: TKeys & MakeOptionalAndBool): x is FilterType & keyof TKeys]: unknown }> { + for (const k in key) { + if (!(k in x)) { + return false; + } + } + return true; +} diff --git a/extensions/ipynb/src/deserializers.ts b/extensions/ipynb/src/deserializers.ts index e49931e060c82..596a03db46879 100644 --- a/extensions/ipynb/src/deserializers.ts +++ b/extensions/ipynb/src/deserializers.ts @@ -20,7 +20,7 @@ const jupyterLanguageToMonacoLanguageMapping = new Map([ export function getPreferredLanguage(metadata?: nbformat.INotebookMetadata) { const jupyterLanguage = metadata?.language_info?.name || - (metadata?.kernelspec as any)?.language; + (metadata?.kernelspec as unknown as { language: string })?.language; // Default to python language only if the Python extension is installed. const defaultLanguage = @@ -150,7 +150,7 @@ function convertJupyterOutputToBuffer(mime: string, value: unknown): NotebookCel } } -function getNotebookCellMetadata(cell: nbformat.IBaseCell): { +function getNotebookCellMetadata(cell: nbformat.ICell): { [key: string]: any; } { // We put this only for VSC to display in diff view. @@ -168,7 +168,7 @@ function getNotebookCellMetadata(cell: nbformat.IBaseCell): { cellMetadata['metadata'] = JSON.parse(JSON.stringify(cell['metadata'])); } - if ('id' in cell && typeof cell.id === 'string') { + if (typeof cell.id === 'string') { cellMetadata.id = cell.id; } @@ -290,7 +290,7 @@ export function jupyterCellOutputToCellOutput(output: nbformat.IOutput): Noteboo if (fn) { result = fn(output); } else { - result = translateDisplayDataOutput(output as any); + result = translateDisplayDataOutput(output as unknown as nbformat.IDisplayData | nbformat.IDisplayUpdate | nbformat.IExecuteResult); } return result; } @@ -322,7 +322,7 @@ function createNotebookCellDataFromCodeCell(cell: nbformat.ICodeCell, cellLangua ? { executionOrder: cell.execution_count as number } : {}; - const vscodeCustomMetadata = cell.metadata['vscode'] as { [key: string]: any } | undefined; + const vscodeCustomMetadata = cell.metadata?.['vscode'] as { [key: string]: any } | undefined; const cellLanguageId = vscodeCustomMetadata && vscodeCustomMetadata.languageId && typeof vscodeCustomMetadata.languageId === 'string' ? vscodeCustomMetadata.languageId : cellLanguage; const cellData = new NotebookCellData(NotebookCellKind.Code, source, cellLanguageId); diff --git a/extensions/ipynb/src/helper.ts b/extensions/ipynb/src/helper.ts index 6d67b7d529fa6..6a23633f52c8f 100644 --- a/extensions/ipynb/src/helper.ts +++ b/extensions/ipynb/src/helper.ts @@ -6,22 +6,26 @@ import { CancellationError } from 'vscode'; export function deepClone(obj: T): T { - if (!obj || typeof obj !== 'object') { + if (obj === null || typeof obj !== 'object') { return obj; } if (obj instanceof RegExp) { // See https://github.com/microsoft/TypeScript/issues/10990 - return obj as any; + return obj; + } + if (Array.isArray(obj)) { + return obj.map(item => deepClone(item)) as unknown as T; } - const result: any = Array.isArray(obj) ? [] : {}; - Object.keys(obj).forEach((key: string) => { - if ((obj)[key] && typeof (obj)[key] === 'object') { - result[key] = deepClone((obj)[key]); + const result = {}; + for (const key of Object.keys(obj as object) as Array) { + const value = obj[key]; + if (value && typeof value === 'object') { + (result as T)[key] = deepClone(value); } else { - result[key] = (obj)[key]; + (result as T)[key] = value; } - }); - return result; + } + return result as T; } // from https://github.com/microsoft/vscode/blob/43ae27a30e7b5e8711bf6b218ee39872ed2b8ef6/src/vs/base/common/objects.ts#L117 diff --git a/extensions/ipynb/src/notebookImagePaste.ts b/extensions/ipynb/src/notebookImagePaste.ts index 70a24e9bf2d2d..97c2ee7394612 100644 --- a/extensions/ipynb/src/notebookImagePaste.ts +++ b/extensions/ipynb/src/notebookImagePaste.ts @@ -274,7 +274,7 @@ function buildAttachment( const filenameWithoutExt = basename(attachment.fileName, fileExt); let tempFilename = filenameWithoutExt + fileExt; - for (let appendValue = 2; tempFilename in cellMetadata.attachments; appendValue++) { + for (let appendValue = 2; cellMetadata.attachments[tempFilename]; appendValue++) { const objEntries = Object.entries(cellMetadata.attachments[tempFilename]); if (objEntries.length) { // check that mime:b64 are present const [mime, attachmentb64] = objEntries[0]; diff --git a/extensions/ipynb/src/serializers.ts b/extensions/ipynb/src/serializers.ts index 54a05aba20577..6647c27176f00 100644 --- a/extensions/ipynb/src/serializers.ts +++ b/extensions/ipynb/src/serializers.ts @@ -5,7 +5,7 @@ import type * as nbformat from '@jupyterlab/nbformat'; import type { NotebookCell, NotebookCellData, NotebookCellOutput, NotebookData, NotebookDocument } from 'vscode'; -import { CellOutputMetadata, type CellMetadata } from './common'; +import { CellOutputMetadata, hasKey, type CellMetadata } from './common'; import { textMimeTypes, NotebookCellKindMarkup, CellOutputMimeTypes, defaultNotebookFormat } from './constants'; const textDecoder = new TextDecoder(); @@ -39,17 +39,17 @@ export function sortObjectPropertiesRecursively(obj: any): any { return ( Object.keys(obj) .sort() - .reduce>((sortedObj, prop) => { + .reduce>((sortedObj, prop) => { sortedObj[prop] = sortObjectPropertiesRecursively(obj[prop]); return sortedObj; - }, {}) as any + }, {}) ); } return obj; } export function getCellMetadata(options: { cell: NotebookCell | NotebookCellData } | { metadata?: { [key: string]: any } }): CellMetadata { - if ('cell' in options) { + if (hasKey(options, { cell: true })) { const cell = options.cell; const metadata = { execution_count: null, @@ -57,7 +57,7 @@ export function getCellMetadata(options: { cell: NotebookCell | NotebookCellData ...(cell.metadata ?? {}) } satisfies CellMetadata; if (cell.kind === NotebookCellKindMarkup) { - delete (metadata as any).execution_count; + delete (metadata as Record).execution_count; } return metadata; } else { @@ -398,8 +398,8 @@ export function pruneCell(cell: nbformat.ICell): nbformat.ICell { // Remove outputs and execution_count from non code cells if (result.cell_type !== 'code') { - delete (result).outputs; - delete (result).execution_count; + delete (result as Record).outputs; + delete (result as Record).execution_count; } else { // Clean outputs from code cells result.outputs = result.outputs ? (result.outputs as nbformat.IOutput[]).map(fixupOutput) : []; @@ -468,7 +468,7 @@ export function serializeNotebookToString(data: NotebookData): string { .map(cell => createJupyterCellFromNotebookCell(cell, preferredCellLanguage)) .map(pruneCell); - const indentAmount = data.metadata && 'indentAmount' in data.metadata && typeof data.metadata.indentAmount === 'string' ? + const indentAmount = data.metadata && typeof data.metadata.indentAmount === 'string' ? data.metadata.indentAmount : ' '; diff --git a/extensions/ipynb/src/test/notebookModelStoreSync.test.ts b/extensions/ipynb/src/test/notebookModelStoreSync.test.ts index 7174678ad61c5..42395b0a238ff 100644 --- a/extensions/ipynb/src/test/notebookModelStoreSync.test.ts +++ b/extensions/ipynb/src/test/notebookModelStoreSync.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; -import { CancellationTokenSource, Disposable, EventEmitter, ExtensionContext, NotebookCellKind, NotebookDocumentChangeEvent, NotebookDocumentWillSaveEvent, NotebookEdit, NotebookRange, TextDocumentSaveReason, workspace, type CancellationToken, type NotebookCell, type NotebookDocument, type WorkspaceEdit, type WorkspaceEditMetadata } from 'vscode'; +import { CancellationTokenSource, Disposable, EventEmitter, ExtensionContext, NotebookCellKind, NotebookDocumentChangeEvent, NotebookDocumentWillSaveEvent, NotebookEdit, NotebookRange, TextDocument, TextDocumentSaveReason, workspace, type CancellationToken, type NotebookCell, type NotebookDocument, type WorkspaceEdit, type WorkspaceEditMetadata } from 'vscode'; import { activate } from '../notebookModelStoreSync'; suite(`Notebook Model Store Sync`, () => { @@ -36,8 +36,8 @@ suite(`Notebook Model Store Sync`, () => { disposables.push(onDidChangeNotebookDocument); onWillSaveNotebookDocument = new AsyncEmitter(); - sinon.stub(NotebookEdit, 'updateCellMetadata').callsFake((index, metadata) => { - const edit = (NotebookEdit.updateCellMetadata as any).wrappedMethod.call(NotebookEdit, index, metadata); + const stub = sinon.stub(NotebookEdit, 'updateCellMetadata').callsFake((index, metadata) => { + const edit = stub.wrappedMethod.call(NotebookEdit, index, metadata); cellMetadataUpdates.push(edit); return edit; } @@ -75,7 +75,7 @@ suite(`Notebook Model Store Sync`, () => { test('Adding cell for non Jupyter Notebook will not result in any updates', async () => { sinon.stub(notebook, 'notebookType').get(() => 'some-other-type'); const cell: NotebookCell = { - document: {} as any, + document: {} as unknown as TextDocument, executionSummary: {}, index: 0, kind: NotebookCellKind.Code, @@ -104,7 +104,7 @@ suite(`Notebook Model Store Sync`, () => { test('Adding cell to nbformat 4.2 notebook will result in adding empty metadata', async () => { sinon.stub(notebook, 'metadata').get(() => ({ nbformat: 4, nbformat_minor: 2 })); const cell: NotebookCell = { - document: {} as any, + document: {} as unknown as TextDocument, executionSummary: {}, index: 0, kind: NotebookCellKind.Code, @@ -135,7 +135,7 @@ suite(`Notebook Model Store Sync`, () => { test('Added cell will have a cell id if nbformat is 4.5', async () => { sinon.stub(notebook, 'metadata').get(() => ({ nbformat: 4, nbformat_minor: 5 })); const cell: NotebookCell = { - document: {} as any, + document: {} as unknown as TextDocument, executionSummary: {}, index: 0, kind: NotebookCellKind.Code, @@ -169,7 +169,7 @@ suite(`Notebook Model Store Sync`, () => { test('Do not add cell id if one already exists', async () => { sinon.stub(notebook, 'metadata').get(() => ({ nbformat: 4, nbformat_minor: 5 })); const cell: NotebookCell = { - document: {} as any, + document: {} as unknown as TextDocument, executionSummary: {}, index: 0, kind: NotebookCellKind.Code, @@ -205,7 +205,7 @@ suite(`Notebook Model Store Sync`, () => { test('Do not perform any updates if cell id and metadata exists', async () => { sinon.stub(notebook, 'metadata').get(() => ({ nbformat: 4, nbformat_minor: 5 })); const cell: NotebookCell = { - document: {} as any, + document: {} as unknown as TextDocument, executionSummary: {}, index: 0, kind: NotebookCellKind.Code, @@ -244,7 +244,7 @@ suite(`Notebook Model Store Sync`, () => { const cell: NotebookCell = { document: { languageId: 'javascript' - } as any, + } as unknown as TextDocument, executionSummary: {}, index: 0, kind: NotebookCellKind.Code, @@ -266,7 +266,7 @@ suite(`Notebook Model Store Sync`, () => { cell, document: { languageId: 'javascript' - } as any, + } as unknown as TextDocument, metadata: undefined, outputs: undefined, executionSummary: undefined @@ -294,7 +294,7 @@ suite(`Notebook Model Store Sync`, () => { const cell: NotebookCell = { document: { languageId: 'javascript' - } as any, + } as unknown as TextDocument, executionSummary: {}, index: 0, kind: NotebookCellKind.Code, @@ -337,7 +337,7 @@ suite(`Notebook Model Store Sync`, () => { const cell: NotebookCell = { document: { languageId: 'javascript' - } as any, + } as unknown as TextDocument, executionSummary: {}, index: 0, kind: NotebookCellKind.Code, @@ -360,7 +360,7 @@ suite(`Notebook Model Store Sync`, () => { cell, document: { languageId: 'javascript' - } as any, + } as unknown as TextDocument, metadata: undefined, outputs: undefined, executionSummary: undefined @@ -388,7 +388,7 @@ suite(`Notebook Model Store Sync`, () => { const cell: NotebookCell = { document: { languageId: 'powershell' - } as any, + } as unknown as TextDocument, executionSummary: {}, index: 0, kind: NotebookCellKind.Code, @@ -411,7 +411,7 @@ suite(`Notebook Model Store Sync`, () => { cell, document: { languageId: 'powershell' - } as any, + } as unknown as TextDocument, metadata: undefined, outputs: undefined, executionSummary: undefined @@ -443,7 +443,7 @@ suite(`Notebook Model Store Sync`, () => { }); const cell: NotebookCell = { - document: {} as any, + document: {} as unknown as TextDocument, executionSummary: {}, index: 0, kind: NotebookCellKind.Code, diff --git a/extensions/ipynb/src/test/serializers.test.ts b/extensions/ipynb/src/test/serializers.test.ts index e132b6b2b1d1b..acc13995ff52d 100644 --- a/extensions/ipynb/src/test/serializers.test.ts +++ b/extensions/ipynb/src/test/serializers.test.ts @@ -75,6 +75,53 @@ suite(`ipynb serializer`, () => { assert.deepStrictEqual(notebook.cells, [expectedCodeCell, expectedCodeCell2, expectedMarkdownCell]); }); + test('Deserialize cells without metadata field', async () => { + // Test case for issue where cells without metadata field cause "Cannot read properties of undefined" error + const cells: nbformat.ICell[] = [ + { + cell_type: 'code', + execution_count: 10, + outputs: [], + source: 'print(1)' + }, + { + cell_type: 'code', + outputs: [], + source: 'print(2)' + }, + { + cell_type: 'markdown', + source: '# HEAD' + } + ] as unknown as nbformat.ICell[]; + const notebook = jupyterNotebookModelToNotebookData({ cells }, 'python'); + assert.ok(notebook); + assert.strictEqual(notebook.cells.length, 3); + + // First cell with execution count + const cell1 = notebook.cells[0]; + assert.strictEqual(cell1.kind, vscode.NotebookCellKind.Code); + assert.strictEqual(cell1.value, 'print(1)'); + assert.strictEqual(cell1.languageId, 'python'); + assert.ok(cell1.metadata); + assert.strictEqual(cell1.metadata.execution_count, 10); + assert.deepStrictEqual(cell1.executionSummary, { executionOrder: 10 }); + + // Second cell without execution count + const cell2 = notebook.cells[1]; + assert.strictEqual(cell2.kind, vscode.NotebookCellKind.Code); + assert.strictEqual(cell2.value, 'print(2)'); + assert.strictEqual(cell2.languageId, 'python'); + assert.ok(cell2.metadata); + assert.strictEqual(cell2.metadata.execution_count, null); + assert.deepStrictEqual(cell2.executionSummary, {}); + + // Markdown cell + const cell3 = notebook.cells[2]; + assert.strictEqual(cell3.kind, vscode.NotebookCellKind.Markup); + assert.strictEqual(cell3.value, '# HEAD'); + assert.strictEqual(cell3.languageId, 'markdown'); + }); test('Serialize', async () => { const markdownCell = new vscode.NotebookCellData(vscode.NotebookCellKind.Markup, '# header1', 'markdown'); diff --git a/extensions/ipynb/tsconfig.json b/extensions/ipynb/tsconfig.json index ee21f68d22a26..39ab6fc882df4 100644 --- a/extensions/ipynb/tsconfig.json +++ b/extensions/ipynb/tsconfig.json @@ -2,7 +2,13 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "lib": ["dom"] + "lib": [ + "ES2024", + "DOM" + ], + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/jake/extension.webpack.config.js b/extensions/jake/extension.webpack.config.js index beee7b3d6762e..1e221c2fa8500 100644 --- a/extensions/jake/extension.webpack.config.js +++ b/extensions/jake/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { main: './src/main.ts', }, diff --git a/extensions/jake/src/main.ts b/extensions/jake/src/main.ts index a2511dc62dfa1..654cc951b9c86 100644 --- a/extensions/jake/src/main.ts +++ b/extensions/jake/src/main.ts @@ -120,9 +120,9 @@ class FolderDetector { } public async getTask(_task: vscode.Task): Promise { - const jakeTask = (_task.definition).task; + const jakeTask = _task.definition.task; if (jakeTask) { - const kind: JakeTaskDefinition = (_task.definition); + const kind = _task.definition as JakeTaskDefinition; const options: vscode.ShellExecutionOptions = { cwd: this.workspaceFolder.uri.fsPath }; const task = new vscode.Task(kind, this.workspaceFolder, jakeTask, 'jake', new vscode.ShellExecution(await this._jakeCommand, [jakeTask], options)); return task; diff --git a/extensions/jake/tsconfig.json b/extensions/jake/tsconfig.json index 7234fdfeb9757..22c47de77dbe1 100644 --- a/extensions/jake/tsconfig.json +++ b/extensions/jake/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/java/cgmanifest.json b/extensions/java/cgmanifest.json index ecfeb0eb66876..ebb3d19beb549 100644 --- a/extensions/java/cgmanifest.json +++ b/extensions/java/cgmanifest.json @@ -6,7 +6,8 @@ "git": { "name": "redhat-developer/vscode-java", "repositoryUrl": "https://github.com/redhat-developer/vscode-java", - "commitHash": "f09b712f5d6d6339e765f58c8dfab3f78a378183" + "commitHash": "f09b712f5d6d6339e765f58c8dfab3f78a378183", + "tag": "1.26.0" } }, "license": "MIT", diff --git a/extensions/javascript/snippets/javascript.code-snippets b/extensions/javascript/snippets/javascript.code-snippets index 5bf6aa5edeeaa..e0b5af3b8e6cf 100644 --- a/extensions/javascript/snippets/javascript.code-snippets +++ b/extensions/javascript/snippets/javascript.code-snippets @@ -98,10 +98,11 @@ "prefix": "forin", "body": [ "for (const ${1:key} in ${2:object}) {", - "\tif (Object.prototype.hasOwnProperty.call(${2:object}, ${1:key})) {", - "\t\tconst ${3:element} = ${2:object}[${1:key}];", - "\t\t$TM_SELECTED_TEXT$0", - "\t}", + "\tif (!Object.hasOwn(${2:object}, ${1:key})) continue;", + "\t", + "\tconst ${3:element} = ${2:object}[${1:key}];", + "\t", + "\t$TM_SELECTED_TEXT$0", "}" ], "description": "For-In Loop" diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 44d13c211f181..6d832e6c1592e 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -392,7 +392,7 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP } catch (e) { throw new ResponseError(2, e.toString(), e); } - } else if (schemaDownloadEnabled) { + } else if (schemaDownloadEnabled && workspace.isTrusted) { if (runtime.telemetry && uri.authority === 'schema.management.azure.com') { /* __GDPR__ "json.schema" : { @@ -409,6 +409,9 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP throw new ResponseError(4, e.toString()); } } else { + if (!workspace.isTrusted) { + throw new ResponseError(1, l10n.t('Downloading schemas is disabled in untrusted workspaces')); + } throw new ResponseError(1, l10n.t('Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload)); } }); @@ -533,6 +536,7 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP client.sendNotification(DidChangeConfigurationNotification.type, { settings: getSettings() }); } })); + toDispose.push(workspace.onDidGrantWorkspaceTrust(updateSchemaDownloadSetting)); toDispose.push(createLanguageStatusItem(documentSelector, (uri: string) => client.sendRequest(LanguageStatusRequest.type, uri))); @@ -569,6 +573,11 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP } function updateSchemaDownloadSetting() { + if (!workspace.isTrusted) { + schemaResolutionErrorStatusBarItem.tooltip = l10n.t('Unable to download schemas in untrusted workspaces.'); + schemaResolutionErrorStatusBarItem.command = 'workbench.trust.manage'; + return; + } schemaDownloadEnabled = workspace.getConfiguration().get(SettingIds.enableSchemaDownload) !== false; if (schemaDownloadEnabled) { schemaResolutionErrorStatusBarItem.tooltip = l10n.t('Unable to resolve schema. Click to retry.'); @@ -762,8 +771,8 @@ function getSchemaId(schema: JSONSchemaSettings, settingsLocation?: Uri): string return url; } -function isThenable(obj: ProviderResult): obj is Thenable { - return obj && (obj)['then']; +function isThenable(obj: unknown): obj is Thenable { + return !!obj && typeof (obj as unknown as Thenable).then === 'function'; } function updateMarkdownString(h: MarkdownString): MarkdownString { diff --git a/extensions/json-language-features/client/tsconfig.json b/extensions/json-language-features/client/tsconfig.json index cf91914c87439..bc775d950e5e5 100644 --- a/extensions/json-language-features/client/tsconfig.json +++ b/extensions/json-language-features/client/tsconfig.json @@ -6,6 +6,9 @@ "webworker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/json-language-features/extension-browser.webpack.config.js b/extensions/json-language-features/extension-browser.webpack.config.js index 900ef6a3b1243..30ddd34eb32c4 100644 --- a/extensions/json-language-features/extension-browser.webpack.config.js +++ b/extensions/json-language-features/extension-browser.webpack.config.js @@ -2,22 +2,18 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; +import path from 'path'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; -const path = require('path'); - -module.exports = withBrowserDefaults({ +export default withBrowserDefaults({ target: 'webworker', - context: path.join(__dirname, 'client'), + context: path.join(import.meta.dirname, 'client'), entry: { extension: './src/browser/jsonClientMain.ts' }, output: { filename: 'jsonClientMain.js', - path: path.join(__dirname, 'client', 'dist', 'browser') + path: path.join(import.meta.dirname, 'client', 'dist', 'browser') } }); diff --git a/extensions/json-language-features/extension.webpack.config.js b/extensions/json-language-features/extension.webpack.config.js index 20b8550c447ff..afc9a5b6d18a2 100644 --- a/extensions/json-language-features/extension.webpack.config.js +++ b/extensions/json-language-features/extension.webpack.config.js @@ -2,24 +2,20 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); -const path = require('path'); +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; +import path from 'path'; const config = withDefaults({ - context: path.join(__dirname, 'client'), + context: path.join(import.meta.dirname, 'client'), entry: { extension: './src/node/jsonClientMain.ts' }, output: { filename: 'jsonClientMain.js', - path: path.join(__dirname, 'client', 'dist', 'node') + path: path.join(import.meta.dirname, 'client', 'dist', 'node') } }); -module.exports = config; +export default config; diff --git a/extensions/json-language-features/package-lock.json b/extensions/json-language-features/package-lock.json index 11c7b3a7a9114..c7c66f40ccc77 100644 --- a/extensions/json-language-features/package-lock.json +++ b/extensions/json-language-features/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@vscode/extension-telemetry": "^0.9.8", "request-light": "^0.8.0", - "vscode-languageclient": "^10.0.0-next.16" + "vscode-languageclient": "^10.0.0-next.18" }, "devDependencies": { "@types/node": "22.x" @@ -20,6 +20,27 @@ "vscode": "^1.77.0" } }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@microsoft/1ds-core-js": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/@microsoft/1ds-core-js/-/1ds-core-js-4.3.4.tgz", @@ -168,28 +189,13 @@ "vscode": "^1.75.0" } }, - "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==", - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { "node": "20 || >=22" @@ -223,35 +229,35 @@ "license": "MIT" }, "node_modules/vscode-jsonrpc": { - "version": "9.0.0-next.8", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.8.tgz", - "integrity": "sha512-pN6L5eiNBvUpNFBJvudaZ83klir0T/wLFCDpYhpOEsKXyhsWyYsNMzoG7BK6zJoZLHGSSsaTJDjCcPwnLgUyPQ==", + "version": "9.0.0-next.10", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.10.tgz", + "integrity": "sha512-P+UOjuG/B1zkLM+bGIdmBwSkDejxtgo6EjG0pIkwnFBI0a2Mb7od36uUu8CPbECeQuh+n3zGcNwDl16DhuJ5IA==", "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/vscode-languageclient": { - "version": "10.0.0-next.16", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.16.tgz", - "integrity": "sha512-aVJ950olGncxehPezP61wsEHjB3zgDETCThH1FTQ4V9EZ9mcVSNfjXM0SWC+VYF3nZulI2hBZe0od2Ajib4hNA==", + "version": "10.0.0-next.18", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-10.0.0-next.18.tgz", + "integrity": "sha512-Dpcr0VEEf4SuMW17TFCuKovhvbCx6/tHTnmFyLW1KTJCdVmNG08hXVAmw8Z/izec7TQlzEvzw5PvRfYGzdtr5Q==", "license": "MIT", "dependencies": { - "minimatch": "^10.0.1", + "minimatch": "^10.0.3", "semver": "^7.7.1", - "vscode-languageserver-protocol": "3.17.6-next.13" + "vscode-languageserver-protocol": "3.17.6-next.15" }, "engines": { "vscode": "^1.91.0" } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.17.6-next.13", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.13.tgz", - "integrity": "sha512-IE+/j+OOqJ392KMhcexIGt9MVqcTZ4n7DVyaSp5txuC1kNUnfzxlkPzzDwo0p7hdINLCfWjbcjuW5tGYLof4Vw==", + "version": "3.17.6-next.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.15.tgz", + "integrity": "sha512-aoWX1wwGCndzfrTRhGKVpKAPVy9+WYhUtZW/PJQfHODmVwhVwb4we68CgsQZRTl36t8ZqlSOO2c2TdBPW7hrCw==", "license": "MIT", "dependencies": { - "vscode-jsonrpc": "9.0.0-next.8", + "vscode-jsonrpc": "9.0.0-next.10", "vscode-languageserver-types": "3.17.6-next.6" } }, diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 6e6b43a2b53c2..50da0468e48b9 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -24,7 +24,8 @@ "capabilities": { "virtualWorkspaces": true, "untrustedWorkspaces": { - "supported": true + "supported": "limited", + "description": "%json.workspaceTrust%" } }, "scripts": { @@ -170,7 +171,7 @@ "dependencies": { "@vscode/extension-telemetry": "^0.9.8", "request-light": "^0.8.0", - "vscode-languageclient": "^10.0.0-next.16" + "vscode-languageclient": "^10.0.0-next.18" }, "devDependencies": { "@types/node": "22.x" diff --git a/extensions/json-language-features/package.nls.json b/extensions/json-language-features/package.nls.json index af6c9d87773ee..abc07c993dc80 100644 --- a/extensions/json-language-features/package.nls.json +++ b/extensions/json-language-features/package.nls.json @@ -18,5 +18,7 @@ "json.maxItemsExceededInformation.desc": "Show notification when exceeding the maximum number of outline symbols and folding regions.", "json.enableSchemaDownload.desc": "When enabled, JSON schemas can be fetched from http and https locations.", "json.command.clearCache": "Clear Schema Cache", - "json.command.sort": "Sort Document" + "json.command.sort": "Sort Document", + "json.workspaceTrust": "The extension requires workspace trust to load schemas from http and https." + } diff --git a/extensions/json-language-features/server/README.md b/extensions/json-language-features/server/README.md index 1c38291607229..8047006f4e3c1 100644 --- a/extensions/json-language-features/server/README.md +++ b/extensions/json-language-features/server/README.md @@ -17,7 +17,7 @@ The JSON language server supports requests on documents of language id `json` an The server implements the following capabilities of the language server protocol: -- [Code completion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) for JSON properties and values based on the document's [JSON schema](http://json-schema.org/) or based on existing properties and values used at other places in the document. JSON schemas are configured through the server configuration options. +- [Inline Suggestion](https://microsoft.github.io/language-server-protocol/specification#textDocument_completion) for JSON properties and values based on the document's [JSON schema](http://json-schema.org/) or based on existing properties and values used at other places in the document. JSON schemas are configured through the server configuration options. - [Hover](https://microsoft.github.io/language-server-protocol/specification#textDocument_hover) for values based on descriptions in the document's [JSON schema](http://json-schema.org/). - [Document Symbols](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentSymbol) for quick navigation to properties in the document. - [Document Colors](https://microsoft.github.io/language-server-protocol/specification#textDocument_documentColor) for showing color decorators on values representing colors and [Color Presentation](https://microsoft.github.io/language-server-protocol/specification#textDocument_colorPresentation) for color presentation information to support color pickers. The location of colors is defined by the document's [JSON schema](http://json-schema.org/). All values marked with `"format": "color-hex"` (VSCode specific, non-standard JSON Schema extension) are considered color values. The supported color formats are `#rgb[a]` and `#rrggbb[aa]`. @@ -37,7 +37,7 @@ The JSON language server expects the client to only send requests and notificati The JSON language server has the following dependencies on the client's capabilities: -- Code completion requires that the client capability has *snippetSupport*. If not supported by the client, the server will not offer the completion capability. +- Inline suggestion requires that the client capability has *snippetSupport*. If not supported by the client, the server will not offer the completion capability. - Formatting support requires the client to support *dynamicRegistration* for *rangeFormatting*. If not supported by the client, the server will not offer the format capability. ## Configuration diff --git a/extensions/json-language-features/server/extension-browser.webpack.config.js b/extensions/json-language-features/server/extension-browser.webpack.config.js index 31c3b9b4316dd..8b967a3de7183 100644 --- a/extensions/json-language-features/server/extension-browser.webpack.config.js +++ b/extensions/json-language-features/server/extension-browser.webpack.config.js @@ -2,22 +2,18 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../../shared.webpack.config.mjs'; +import path from 'path'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../../shared.webpack.config').browser; -const path = require('path'); - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, entry: { extension: './src/browser/jsonServerWorkerMain.ts', }, output: { filename: 'jsonServerMain.js', - path: path.join(__dirname, 'dist', 'browser'), + path: path.join(import.meta.dirname, 'dist', 'browser'), libraryTarget: 'var', library: 'serverExportVar' } diff --git a/extensions/json-language-features/server/extension.webpack.config.js b/extensions/json-language-features/server/extension.webpack.config.js index cda72a264d257..1880c1e7b827a 100644 --- a/extensions/json-language-features/server/extension.webpack.config.js +++ b/extensions/json-language-features/server/extension.webpack.config.js @@ -2,23 +2,19 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withDefaults = require('../../shared.webpack.config'); -const path = require('path'); +// @ts-check +import withDefaults from '../../shared.webpack.config.mjs'; +import path from 'path'; const config = withDefaults({ - context: path.join(__dirname), + context: path.join(import.meta.dirname), entry: { extension: './src/node/jsonServerNodeMain.ts', }, output: { filename: 'jsonServerMain.js', - path: path.join(__dirname, 'dist', 'node'), + path: path.join(import.meta.dirname, 'dist', 'node'), } }); -module.exports = config; +export default config; diff --git a/extensions/json-language-features/server/package-lock.json b/extensions/json-language-features/server/package-lock.json index acf3ef20ed78c..fc31206a0cdab 100644 --- a/extensions/json-language-features/server/package-lock.json +++ b/extensions/json-language-features/server/package-lock.json @@ -12,15 +12,15 @@ "@vscode/l10n": "^0.0.18", "jsonc-parser": "^3.3.1", "request-light": "^0.8.0", - "vscode-json-languageservice": "^5.6.1", - "vscode-languageserver": "^10.0.0-next.13", + "vscode-json-languageservice": "^5.6.4", + "vscode-languageserver": "^10.0.0-next.15", "vscode-uri": "^3.1.0" }, "bin": { "vscode-json-languageserver": "bin/vscode-json-languageserver" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x" }, "engines": { @@ -28,10 +28,11 @@ } }, "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true + "version": "10.0.10", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", + "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.13.10", @@ -66,9 +67,9 @@ "license": "MIT" }, "node_modules/vscode-json-languageservice": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-5.6.1.tgz", - "integrity": "sha512-IQIURBF2VMKBdWcMunbHSI3G2WmJ9H7613E1hRxIXX7YsAPSdBxnEiIUrTnsSW/3fk+QW1kfsvSigqgAFYIYtg==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-5.6.4.tgz", + "integrity": "sha512-i0MhkFmnQAbYr+PiE6Th067qa3rwvvAErCEUo0ql+ghFXHvxbwG3kLbwMaIUrrbCLUDEeULiLgROJjtuyYoIsA==", "license": "MIT", "dependencies": { "@vscode/l10n": "^0.0.18", @@ -79,33 +80,33 @@ } }, "node_modules/vscode-jsonrpc": { - "version": "9.0.0-next.8", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.8.tgz", - "integrity": "sha512-pN6L5eiNBvUpNFBJvudaZ83klir0T/wLFCDpYhpOEsKXyhsWyYsNMzoG7BK6zJoZLHGSSsaTJDjCcPwnLgUyPQ==", + "version": "9.0.0-next.10", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-9.0.0-next.10.tgz", + "integrity": "sha512-P+UOjuG/B1zkLM+bGIdmBwSkDejxtgo6EjG0pIkwnFBI0a2Mb7od36uUu8CPbECeQuh+n3zGcNwDl16DhuJ5IA==", "license": "MIT", "engines": { "node": ">=14.0.0" } }, "node_modules/vscode-languageserver": { - "version": "10.0.0-next.13", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-10.0.0-next.13.tgz", - "integrity": "sha512-4tSufM2XrNrrzBUGPcYh62qBYhm41yFwFZBgJ63I1dPHRh1aZPK65+TcVa3nG0/K62Q9phhk87TWdQFp+UnYFA==", + "version": "10.0.0-next.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-10.0.0-next.15.tgz", + "integrity": "sha512-vs+bwci/lM83ZhrR9t8DcZ2AgS2CKx4i6Yw86teKKkqlzlrYWTixuBd9w6H/UP9s8EGBvii0jnbjQd6wsKJ0ig==", "license": "MIT", "dependencies": { - "vscode-languageserver-protocol": "3.17.6-next.13" + "vscode-languageserver-protocol": "3.17.6-next.15" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.17.6-next.13", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.13.tgz", - "integrity": "sha512-IE+/j+OOqJ392KMhcexIGt9MVqcTZ4n7DVyaSp5txuC1kNUnfzxlkPzzDwo0p7hdINLCfWjbcjuW5tGYLof4Vw==", + "version": "3.17.6-next.15", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.6-next.15.tgz", + "integrity": "sha512-aoWX1wwGCndzfrTRhGKVpKAPVy9+WYhUtZW/PJQfHODmVwhVwb4we68CgsQZRTl36t8ZqlSOO2c2TdBPW7hrCw==", "license": "MIT", "dependencies": { - "vscode-jsonrpc": "9.0.0-next.8", + "vscode-jsonrpc": "9.0.0-next.10", "vscode-languageserver-types": "3.17.6-next.6" } }, diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 5e91799aa2db3..00fff97cbe702 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -15,12 +15,12 @@ "@vscode/l10n": "^0.0.18", "jsonc-parser": "^3.3.1", "request-light": "^0.8.0", - "vscode-json-languageservice": "^5.6.1", - "vscode-languageserver": "^10.0.0-next.13", + "vscode-json-languageservice": "^5.6.4", + "vscode-languageserver": "^10.0.0-next.15", "vscode-uri": "^3.1.0" }, "devDependencies": { - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.10", "@types/node": "22.x" }, "scripts": { diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index 830ee8c439369..cbe1e7d02b48a 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -141,7 +141,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { - const initializationOptions = params.initializationOptions as any || {}; + const initializationOptions = params.initializationOptions || {}; const handledProtocols = initializationOptions?.handledSchemaProtocols; diff --git a/extensions/json-language-features/server/tsconfig.json b/extensions/json-language-features/server/tsconfig.json index 424b140b4a074..07433e08b6274 100644 --- a/extensions/json-language-features/server/tsconfig.json +++ b/extensions/json-language-features/server/tsconfig.json @@ -9,6 +9,9 @@ "WebWorker" ], "module": "Node16", + "typeRoots": [ + "../node_modules/@types" + ] }, "include": [ "src/**/*" diff --git a/extensions/json/syntaxes/snippets.tmLanguage.json b/extensions/json/syntaxes/snippets.tmLanguage.json index 289bc18f8c6eb..fd22457a79789 100644 --- a/extensions/json/syntaxes/snippets.tmLanguage.json +++ b/extensions/json/syntaxes/snippets.tmLanguage.json @@ -46,7 +46,7 @@ "name": "constant.character.escape.json.comments.snippets" }, "bnf_any": { - "match": "(?:\\}|((?:(?:(?:(?:(?:(?:((?:(\\$)([0-9]+)))|((?:(?:(\\$)(\\{))([0-9]+)(\\}))))|((?:(?:(\\$)(\\{))([0-9]+)((?:(\\/)((?:(?:(?:(?:(\\\\)(\\\\\\/))|(?:(\\\\\\\\\\\\)(\\\\\\/)))|[^\\/\\n])+))(\\/)(((?:(?:(?:(?:(?:(?:(?:(?:\\$(?:(?)*?))(\\|)(\\}))))|((?:(?:(\\$)(\\{))([0-9]+)(:)(?:(?:(?:(?:(?:\\$(?:[0-9]+))|(?:(?:\\$\\{)(?:[0-9]+)\\}))|(?:(?:\\$\\{)(?:[0-9]+)(?:\\/((?:(?:(?:(?:\\\\(?:\\\\\\/))|(?:(?:\\\\\\\\\\\\)(?:\\\\\\/)))|[^\\/\\n])+))\\/((?:(?:(?:(?:(?:(?:(?:(?:(?:\\$(?:(?)+)(\\}))))|(?:(?:(?:((?:(\\$)((?+))(\\}))))|((?:(?:(\\$)(\\{))((?)*?))(\\|)(\\}))))|((?:(?:(\\$)(\\{))([0-9]+)(:)(?:(?:(?:(?:(?:\\$(?:[0-9]+))|(?:(?:\\$\\{)(?:[0-9]+)\\}))|(?:(?:\\$\\{)(?:[0-9]+)(?:\\/((?:(?:(?:(?:\\\\(?:\\\\\\/))|(?:(?:\\\\\\\\\\\\)(?:\\\\\\/)))|[^\\/\\n])+))\\/((?:(?:(?:(?:(?:(?:(?:(?:(?:\\$(?:(?)+)(\\}))))|(?:(?:(?:((?:(\\$)((?+))(\\}))))|((?:(?:(\\$)(\\{))((?]*>)?((?:\\[[^\\]]*\\])*)(\\{)", "captures": { "1": { - "name": "storage.type.function.latex" + "name": "keyword.control.cite.latex" }, "2": { - "name": "punctuation.definition.function.latex" + "name": "punctuation.definition.keyword.latex" }, "3": { - "name": "punctuation.definition.begin.latex" + "patterns": [ + { + "include": "#autocites-arg" + } + ] }, "4": { - "name": "support.function.general.latex" + "patterns": [ + { + "include": "#optional-arg-angle-no-highlight" + } + ] }, "5": { - "name": "punctuation.definition.function.latex" - }, - "6": { - "name": "punctuation.definition.end.latex" - } - }, - "match": "((\\\\)(?:newcommand|renewcommand|(?:re)?newrobustcmd|DeclareRobustCommand))\\*?({)((\\\\)[^}]*)(})" - }, - { - "begin": "((\\\\)marginpar)((?:\\[[^\\[]*?\\])*)(\\{)", - "beginCaptures": { - "1": { - "name": "support.function.marginpar.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { "patterns": [ { - "include": "#optional-arg-bracket" + "include": "#optional-arg-bracket-no-highlight" } ] }, - "4": { - "name": "punctuation.definition.marginpar.begin.latex" + "6": { + "name": "punctuation.definition.arguments.begin.latex" } }, - "contentName": "meta.paragraph.margin.latex", "end": "\\}", "endCaptures": { "0": { - "name": "punctuation.definition.marginpar.end.latex" + "name": "punctuation.definition.arguments.end.latex" } }, + "name": "meta.citation.latex", "patterns": [ { - "include": "text.tex#braces" + "match": "((%).*)$", + "captures": { + "1": { + "name": "comment.line.percentage.tex" + }, + "2": { + "name": "punctuation.definition.comment.tex" + } + } }, { - "include": "$self" + "match": "[\\p{Alphabetic}\\p{Number}:.-]+", + "name": "constant.other.reference.citation.latex" } ] }, - { - "begin": "((\\\\)footnote)((?:\\[[^\\[]*?\\])*)(\\{)", - "beginCaptures": { - "1": { - "name": "support.function.footnote.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { + "references-macro": { + "patterns": [ + { + "begin": "((\\\\)(?:\\w*[rR]ef\\*?))(?:\\[[^\\]]*\\])?(\\{)", + "beginCaptures": { + "1": { + "name": "keyword.control.ref.latex" + }, + "2": { + "name": "punctuation.definition.keyword.latex" + }, + "3": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "name": "meta.reference.label.latex", "patterns": [ { - "include": "#optional-arg-bracket" + "match": "[\\p{Alphabetic}\\p{Number}\\.,:/*!^_-]+", + "name": "constant.other.reference.label.latex" } ] }, - "4": { - "name": "punctuation.definition.footnote.begin.latex" - } - }, - "contentName": "entity.name.footnote.latex", - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.footnote.end.latex" - } - }, - "patterns": [ { - "include": "text.tex#braces" + "match": "((\\\\)(?:\\w*[rR]efrange\\*?))(?:\\[[^\\]]*\\])?(\\{)([\\p{Alphabetic}\\p{Number}\\.,:/*!^_-]+)(\\})(\\{)([\\p{Alphabetic}\\p{Number}\\.,:/*!^_-]+)(\\})", + "captures": { + "1": { + "name": "keyword.control.ref.latex" + }, + "2": { + "name": "punctuation.definition.keyword.latex" + }, + "3": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "4": { + "name": "constant.other.reference.label.latex" + }, + "5": { + "name": "punctuation.definition.arguments.end.latex" + }, + "6": { + "name": "punctuation.definition.arguments.begin.latex" + }, + "7": { + "name": "constant.other.reference.label.latex" + }, + "8": { + "name": "punctuation.definition.arguments.end.latex" + } + } }, { - "include": "$self" + "begin": "((\\\\)bibentry)(\\{)", + "captures": { + "1": { + "name": "keyword.control.cite.latex" + }, + "2": { + "name": "punctuation.definition.keyword.latex" + }, + "3": { + "name": "punctuation.definition.arguments.begin.latex" + } + }, + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.latex" + } + }, + "name": "meta.citation.latex", + "patterns": [ + { + "match": "[\\p{Alphabetic}\\p{Number}:.]+", + "name": "constant.other.reference.citation.latex" + } + ] } ] }, - { - "begin": "((\\\\)emph)(\\{)", - "beginCaptures": { - "1": { - "name": "support.function.emph.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.emph.begin.latex" - } - }, - "contentName": "markup.italic.emph.latex", - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.emph.end.latex" - } - }, - "name": "meta.function.emph.latex", + "display-math": { "patterns": [ { - "include": "text.tex#braces" + "begin": "\\\\\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.latex" + } + }, + "end": "\\\\\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.latex" + } + }, + "name": "meta.math.block.latex support.class.math.block.environment.latex", + "patterns": [ + { + "include": "text.tex#math-content" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" - } - ] - }, - { - "begin": "((\\\\)textit)(\\{)", - "captures": { - "1": { - "name": "support.function.textit.latex" - }, - "2": { - "name": "punctuation.definition.function.latex" - }, - "3": { - "name": "punctuation.definition.textit.begin.latex" - } - }, - "comment": "We put the keyword in a capture and name this capture, so that disabling spell checking for “keyword” won't be inherited by the argument to \\textit{...}.\n\nPut specific matches for particular LaTeX keyword.functions before the last two more general functions", - "contentName": "markup.italic.textit.latex", - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.textit.end.latex" + "begin": "\\$\\$", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.latex" + } + }, + "end": "\\$\\$", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.latex" + } + }, + "name": "meta.math.block.latex support.class.math.block.environment.latex", + "patterns": [ + { + "match": "\\\\\\$", + "name": "constant.character.escape.latex" + }, + { + "include": "text.tex#math-content" + }, + { + "include": "$self" + } + ] } - }, - "name": "meta.function.textit.latex", + ] + }, + "inline-math": { "patterns": [ { - "include": "text.tex#braces" + "begin": "\\\\\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.latex" + } + }, + "end": "\\\\\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.latex" + } + }, + "name": "meta.math.block.latex support.class.math.block.environment.latex", + "patterns": [ + { + "include": "text.tex#math-content" + }, + { + "include": "$self" + } + ] }, { - "include": "$self" + "begin": "\\$(?!\\$)", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.tex" + } + }, + "end": "(?]*>)?((?:\\[[^\\]]*\\])*)(\\{)", - "captures": { - "1": { - "name": "keyword.control.cite.latex" + { + "begin": "(\\s*\\\\begin\\{(tabular[xy*]?|xltabular|longtable|(?:long)?tabu|(?:long|tall)?tblr|NiceTabular[X*]?|booktabs)\\}(\\s*\\n)?)", + "captures": { + "1": { + "patterns": [ + { + "include": "#macro-with-args-tokenizer" + } + ] + } + }, + "contentName": "meta.data.environment.tabular.latex", + "end": "(\\s*\\\\end\\{(\\2)\\}(?:\\s*\\n)?)", + "name": "meta.function.environment.tabular.latex", + "patterns": [ + { + "match": "(?)(\\{)\\$(\\})", "captures": { "1": { "name": "punctuation.definition.column-specials.begin.latex" @@ -3861,14 +4082,8 @@ "name": "punctuation.definition.column-specials.end.latex" } }, - "match": "(?:<|>)(\\{)\\$(\\})", "name": "meta.column-specials.latex" }, - { - "include": "text.tex" - } - ], - "repository": { "autocites-arg": { "patterns": [ { @@ -3908,7 +4123,8 @@ } ] }, - "begin-env-tokenizer": { + "macro-with-args-tokenizer": { + "match": "\\s*((\\\\)(?:\\p{Alphabetic}+))(\\{)(\\\\?\\p{Alphabetic}+\\*?)(\\})(?:(\\[)([^\\]]*)(\\])){,2}(?:(\\{)([^{}]*)(\\}))?", "captures": { "1": { "name": "support.function.be.latex" @@ -3947,42 +4163,7 @@ "11": { "name": "punctuation.definition.arguments.end.latex" } - }, - "match": "\\s*((\\\\)(?:begin|end))(\\{)(\\p{Alphabetic}+\\*?)(\\})(?:(\\[)([^\\]]*)(\\])){,2}(?:(\\{)([^{}]*)(\\}))?" - }, - "definition-label": { - "begin": "((\\\\)z?label)((?:\\[[^\\[]*?\\])*)(\\{)", - "beginCaptures": { - "1": { - "name": "keyword.control.label.latex" - }, - "2": { - "name": "punctuation.definition.keyword.latex" - }, - "3": { - "patterns": [ - { - "include": "#optional-arg-bracket" - } - ] - }, - "4": { - "name": "punctuation.definition.arguments.begin.latex" - } - }, - "end": "\\}", - "endCaptures": { - "0": { - "name": "punctuation.definition.arguments.end.latex" - } - }, - "name": "meta.definition.label.latex", - "patterns": [ - { - "match": "[\\p{Alphabetic}\\p{Number}\\.,:/*!^_-]+", - "name": "variable.parameter.definition.label.latex" - } - ] + } }, "multiline-optional-arg": { "begin": "\\G\\[", @@ -4039,9 +4220,58 @@ } }, "name": "meta.parameter.latex", + "comment": "Do not look for balanced expressions, ie environments, inside a command argument", "patterns": [ { - "include": "$self" + "include": "#documentclass-usepackage-macro" + }, + { + "include": "#input-macro" + }, + { + "include": "#sections-macro" + }, + { + "include": "#hyperref-macro" + }, + { + "include": "#newcommand-macro" + }, + { + "include": "#text-font-macro" + }, + { + "include": "#citation-macro" + }, + { + "include": "#references-macro" + }, + { + "include": "#label-macro" + }, + { + "include": "#verb-macro" + }, + { + "include": "#inline-code-macro" + }, + { + "include": "#all-other-macro" + }, + { + "include": "#display-math" + }, + { + "include": "#inline-math" + }, + { + "include": "#column-specials" + }, + { + "include": "text.tex#braces" + }, + { + "include": "text.tex" } ] }, diff --git a/extensions/latex/syntaxes/TeX.tmLanguage.json b/extensions/latex/syntaxes/TeX.tmLanguage.json index b31ccccb631d6..1a2e3211ae6a7 100644 --- a/extensions/latex/syntaxes/TeX.tmLanguage.json +++ b/extensions/latex/syntaxes/TeX.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jlelong/vscode-latex-basics/commit/6bd99800f7b2cbd0e36cecb56fe1936da5affadb", + "version": "https://github.com/jlelong/vscode-latex-basics/commit/f40116471b3b479082937850c822a27208d6b054", "name": "TeX", "scopeName": "text.tex", "patterns": [ @@ -31,6 +31,9 @@ "match": "\\\\\\\\", "name": "keyword.control.newline.tex" }, + { + "include": "#ifnextchar" + }, { "include": "#macro-general" } @@ -86,6 +89,10 @@ } ] }, + "ifnextchar": { + "match": "\\\\@ifnextchar[({\\[]", + "name": "keyword.control.ifnextchar.tex" + }, "macro-control": { "match": "(\\\\)(backmatter|csname|else|endcsname|fi|frontmatter|mainmatter|unless|if(case|cat|csname|defined|dim|eof|false|fontchar|hbox|hmode|inner|mmode|num|odd|true|vbox|vmode|void|x)?)(?![a-zA-Z@])", "captures": { @@ -225,7 +232,7 @@ "name": "punctuation.math.bracket.pair.big.tex" }, { - "match": "(\\\\)(s(s(earrow|warrow|lash)|h(ort(downarrow|uparrow|parallel|leftarrow|rightarrow|mid)|arp)|tar|i(gma|m(eq)?)|u(cc(sim|n(sim|approx)|curlyeq|eq|approx)?|pset(neq(q)?|plus(eq)?|eq(q)?)?|rd|m|bset(neq(q)?|plus(eq)?|eq(q)?)?)|p(hericalangle|adesuit)|e(tminus|arrow)|q(su(pset(eq)?|bset(eq)?)|c(up|ap)|uare)|warrow|m(ile|all(s(etminus|mile)|frown)))|h(slash|ook(leftarrow|rightarrow)|eartsuit|bar)|R(sh|ightarrow|e|bag)|Gam(e|ma)|n(s(hort(parallel|mid)|im|u(cc(eq)?|pseteq(q)?|bseteq))|Rightarrow|n(earrow|warrow)|cong|triangle(left(eq(slant)?)?|right(eq(slant)?)?)|i(plus)?|u|p(lus|arallel|rec(eq)?)|e(q|arrow|g|xists)|v(dash|Dash)|warrow|le(ss|q(slant|q)?|ft(arrow|rightarrow))|a(tural|bla)|VDash|rightarrow|g(tr|eq(slant|q)?)|mid|Left(arrow|rightarrow))|c(hi|irc(eq|le(d(circ|S|dash|ast)|arrow(left|right)))?|o(ng|prod|lon|mplement)|dot(s|p)?|u(p|r(vearrow(left|right)|ly(eq(succ|prec)|vee(downarrow|uparrow)?|wedge(downarrow|uparrow)?)))|enterdot|lubsuit|ap)|Xi|Maps(to(char)?|from(char)?)|B(ox|umpeq|bbk)|t(h(ick(sim|approx)|e(ta|refore))|imes|op|wohead(leftarrow|rightarrow)|a(u|lloblong)|riangle(down|q|left(eq(slant)?)?|right(eq(slant)?)?)?)|i(n(t(er(cal|leave))?|plus|fty)?|ota|math)|S(igma|u(pset|bset))|zeta|o(slash|times|int|dot|plus|vee|wedge|lessthan|greaterthan|m(inus|ega)|b(slash|long|ar))|d(i(v(ideontimes)?|a(g(down|up)|mond(suit)?)|gamma)|o(t(plus|eq(dot)?)|ublebarwedge|wn(harpoon(left|right)|downarrows|arrow))|d(ots|agger)|elta|a(sh(v|leftarrow|rightarrow)|leth|gger))|Y(down|up|left|right)|C(up|ap)|u(n(lhd|rhd)|p(silon|harpoon(left|right)|downarrow|uparrows|lus|arrow)|lcorner|rcorner)|jmath|Theta|Im|p(si|hi|i(tchfork)?|erp|ar(tial|allel)|r(ime|o(d|pto)|ec(sim|n(sim|approx)|curlyeq|eq|approx)?)|m)|e(t(h|a)|psilon|q(slant(less|gtr)|circ|uiv)|ll|xists|mptyset)|Omega|D(iamond|ownarrow|elta)|v(d(ots|ash)|ee(bar)?|Dash|ar(s(igma|u(psetneq(q)?|bsetneq(q)?))|nothing|curly(vee|wedge)|t(heta|imes|riangle(left|right)?)|o(slash|circle|times|dot|plus|vee|wedge|lessthan|ast|greaterthan|minus|b(slash|ar))|p(hi|i|ropto)|epsilon|kappa|rho|bigcirc))|kappa|Up(silon|downarrow|arrow)|Join|f(orall|lat|a(t(s(emi|lash)|bslash)|llingdotseq)|rown)|P(si|hi|i)|w(p|edge|r)|l(hd|n(sim|eq(q)?|approx)|ceil|times|ightning|o(ng(left(arrow|rightarrow)|rightarrow|maps(to|from))|zenge|oparrow(left|right))|dot(s|p)|e(ss(sim|dot|eq(qgtr|gtr)|approx|gtr)|q(slant|q)?|ft(slice|harpoon(down|up)|threetimes|leftarrows|arrow(t(ail|riangle))?|right(squigarrow|harpoons|arrow(s|triangle|eq)?))|adsto)|vertneqq|floor|l(c(orner|eil)|floor|l|bracket)?|a(ngle|mbda)|rcorner|bag)|a(s(ymp|t)|ngle|pprox(eq)?|l(pha|eph)|rrownot|malg)|V(dash|vdash)|r(h(o|d)|ceil|times|i(singdotseq|ght(s(quigarrow|lice)|harpoon(down|up)|threetimes|left(harpoons|arrows)|arrow(t(ail|riangle))?|rightarrows))|floor|angle|r(ceil|parenthesis|floor|bracket)|bag)|g(n(sim|eq(q)?|approx)|tr(sim|dot|eq(qless|less)|less|approx)|imel|eq(slant|q)?|vertneqq|amma|g(g)?)|Finv|xi|m(ho|i(nuso|d)|o(o|dels)|u(ltimap)?|p|e(asuredangle|rge)|aps(to|from(char)?))|b(i(n(dnasrepma|ampersand)|g(s(tar|qc(up|ap))|nplus|c(irc|u(p|rly(vee|wedge))|ap)|triangle(down|up)|interleave|o(times|dot|plus)|uplus|parallel|vee|wedge|box))|o(t|wtie|x(slash|circle|times|dot|plus|empty|ast|minus|b(slash|ox|ar)))|u(llet|mpeq)|e(cause|t(h|ween|a))|lack(square|triangle(down|left|right)?|lozenge)|a(ck(s(im(eq)?|lash)|prime|epsilon)|r(o|wedge))|bslash)|L(sh|ong(left(arrow|rightarrow)|rightarrow|maps(to|from))|eft(arrow|rightarrow)|leftarrow|ambda|bag)|Arrownot)(?![a-zA-Z@])", + "match": "(\\\\)(s(s(earrow|warrow|lash)|h(ort(downarrow|uparrow|parallel|leftarrow|rightarrow|mid)|arp)|tar|i(gma|m(eq)?)|u(cc(sim|n(sim|approx)|curlyeq|eq|approx)?|pset(neq(q)?|plus(eq)?|eq(q)?)?|rd|m|bset(neq(q)?|plus(eq)?|eq(q)?)?)|p(hericalangle|adesuit)|e(tminus|arrow)|q(su(pset(eq)?|bset(eq)?)|c(up|ap)|uare)|warrow|m(ile|all(s(etminus|mile)|frown)))|h(slash|ook(leftarrow|rightarrow)|eartsuit|bar)|R(sh|ightarrow|e|bag)|Gam(e|ma)|n(s(hort(parallel|mid)|im|u(cc(eq)?|pseteq(q)?|bseteq))|Rightarrow|n(earrow|warrow)|cong|triangle(left(eq(slant)?)?|right(eq(slant)?)?)|i(plus)?|u|p(lus|arallel|rec(eq)?)|e(q|arrow|g|xists)|v(dash|Dash)|warrow|le(ss|q(slant|q)?|ft(arrow|rightarrow))|a(tural|bla)|VDash|rightarrow|g(tr|eq(slant|q)?)|mid|Left(arrow|rightarrow))|c(hi|irc(eq|le(d(circ|S|dash|ast)|arrow(left|right)))?|o(ng|prod|lon|mplement)|dot(s|p)?|u(p|r(vearrow(left|right)|ly(eq(succ|prec)|vee(downarrow|uparrow)?|wedge(downarrow|uparrow)?)))|enterdot|lubsuit|ap)|Xi|Maps(to(char)?|from(char)?)|B(ox|umpeq|bbk)|t(h(ick(sim|approx)|e(ta|refore))|imes|op|wohead(leftarrow|rightarrow)|a(u|lloblong)|riangle(down|q|left(eq(slant)?)?|right(eq(slant)?)?)?)|i(n(t(er(cal|leave))?|plus|fty)?|ota|math)|S(igma|u(pset|bset))|zeta|o(slash|times|int|dot|plus|vee|wedge|lessthan|greaterthan|m(inus|ega)|b(slash|long|ar))|d(i(v(ideontimes)?|a(g(down|up)|mond(suit)?)|gamma)|o(t(plus|eq(dot)?)|ublebarwedge|wn(harpoon(left|right)|downarrows|arrow))|d(ots|agger)|elta|a(sh(v|leftarrow|rightarrow)|leth|gger))|Y(down|up|left|right)|C(up|ap)|u(n(lhd|rhd)|p(silon|harpoon(left|right)|downarrow|uparrows|lus|arrow)|lcorner|rcorner)|jmath|Theta|Im|p(si|hi|i(tchfork)?|erp|ar(tial|allel)|r(ime|o(d|pto)|ec(sim|n(sim|approx)|curlyeq|eq|approx)?)|m)|e(t(h|a)|psilon|q(slant(less|gtr)|circ|uiv)|ll|xists|mptyset)|Omega|D(iamond|ownarrow|elta)|v(d(ots|ash)|ee(bar)?|Dash|ar(s(igma|u(psetneq(q)?|bsetneq(q)?))|nothing|curly(vee|wedge)|t(heta|imes|riangle(left|right)?)|o(slash|circle|times|dot|plus|vee|wedge|lessthan|ast|greaterthan|minus|b(slash|ar))|p(hi|i|ropto)|epsilon|kappa|rho|bigcirc))|kappa|Up(silon|downarrow|arrow)|Join|f(orall|lat|a(t(s(emi|lash)|bslash)|llingdotseq)|rown)|P(si|hi|i)|w(p|edge|r)|l(hd|n(sim|eq(q)?|approx)|ceil|times|ightning|o(ng(left(arrow|rightarrow)|rightarrow|maps(to|from))|zenge|oparrow(left|right))|dot(s|p)|e(ss(sim|dot|eq(qgtr|gtr)|approx|gtr)|q(slant|q)?|ft(slice|harpoon(down|up)|threetimes|leftarrows|arrow(t(ail|riangle))?|right(squigarrow|harpoons|arrow(s|triangle|eq)?))|adsto)|vertneqq|floor|l(c(orner|eil)|floor|l|bracket)?|a(ngle|mbda)|rcorner|bag)|a(s(ymp|t)|ngle|pprox(eq)?|l(pha|eph)|rrownot|malg)|V(dash|vdash)|r(h(o|d)|ceil|times|i(singdotseq|ght(s(quigarrow|lice)|harpoon(down|up)|threetimes|left(harpoons|arrows)|arrow(t(ail|riangle))?|rightarrows))|floor|angle|r(ceil|parenthesis|floor|bracket)|bag)|g(n(sim|eq(q)?|approx)|tr(sim|dot|eq(qless|less)|less|approx)|imel|eq(slant|q)?|vertneqq|amma|g(g)?)|Finv|xi|m(ho|i(nuso|d)|o(o|dels)|u(ltimap)?|p|e(asuredangle|rge)|aps(to|from(char)?))|b(i(n(dnasrepma|ampersand)|g(s(tar|qc(up|ap))|nplus|c(irc|u(p|rly(vee|wedge))|ap)|triangle(down|up)|interleave|o(times|dot|plus)|uplus|parallel|vee|wedge|box))|o(t|wtie|x(slash|circle|times|dot|plus|empty|ast|minus|b(slash|ox|ar)))|u(llet|mpeq)|e(cause|t(h|ween|a))|lack(square|triangle(down|left|right)?|lozenge)|a(ck(s(im(eq)?|lash)|prime|epsilon)|r(o|wedge))|bslash)|L(sh|ong(left(arrow|rightarrow)|rightarrow|maps(to|from))|eft(arrow|rightarrow)|leftarrow|ambda|bag)|ge|le|Arrownot)(?![a-zA-Z@])", "captures": { "1": { "name": "punctuation.definition.constant.math.tex" diff --git a/extensions/less/.vscodeignore b/extensions/less/.vscodeignore index 0a622e7e30046..2ff7df8f44cff 100644 --- a/extensions/less/.vscodeignore +++ b/extensions/less/.vscodeignore @@ -1,2 +1,3 @@ test/** cgmanifest.json +build/** diff --git a/extensions/mangle-loader.js b/extensions/mangle-loader.js index 016d0f6903310..ed32a85e633f3 100644 --- a/extensions/mangle-loader.js +++ b/extensions/mangle-loader.js @@ -8,7 +8,7 @@ const fs = require('fs'); const webpack = require('webpack'); const fancyLog = require('fancy-log'); const ansiColors = require('ansi-colors'); -const { Mangler } = require('../build/lib/mangle/index'); +const { Mangler } = require('../build/lib/mangle/index.js'); /** * Map of project paths to mangled file contents diff --git a/extensions/markdown-basics/cgmanifest.json b/extensions/markdown-basics/cgmanifest.json index 82e47b637e14a..91686a02c68d8 100644 --- a/extensions/markdown-basics/cgmanifest.json +++ b/extensions/markdown-basics/cgmanifest.json @@ -33,7 +33,7 @@ "git": { "name": "microsoft/vscode-markdown-tm-grammar", "repositoryUrl": "https://github.com/microsoft/vscode-markdown-tm-grammar", - "commitHash": "548ccb91ef58ba40ac745b400d889933ccd5eb4d" + "commitHash": "0812fc4b190efc17bfed0d5b4ff918eff8e4e377" } }, "license": "MIT", diff --git a/extensions/markdown-basics/package.json b/extensions/markdown-basics/package.json index 6eadec35592d7..cb1351a71cfa5 100644 --- a/extensions/markdown-basics/package.json +++ b/extensions/markdown-basics/package.json @@ -28,6 +28,9 @@ ".mdtext", ".workbook" ], + "filenamePatterns": [ + "**/.cursor/**/*.mdc" + ], "configuration": "./language-configuration.json" } ], @@ -65,9 +68,11 @@ "meta.embedded.block.go": "go", "meta.embedded.block.groovy": "groovy", "meta.embedded.block.pug": "jade", + "meta.embedded.block.ignore": "ignore", "meta.embedded.block.javascript": "javascript", "meta.embedded.block.json": "json", "meta.embedded.block.jsonc": "jsonc", + "meta.embedded.block.jsonl": "jsonl", "meta.embedded.block.latex": "latex", "meta.embedded.block.less": "less", "meta.embedded.block.objc": "objc", @@ -75,6 +80,7 @@ "meta.embedded.block.perl6": "perl6", "meta.embedded.block.powershell": "powershell", "meta.embedded.block.python": "python", + "meta.embedded.block.restructuredtext": "restructuredtext", "meta.embedded.block.rust": "rust", "meta.embedded.block.scala": "scala", "meta.embedded.block.shellscript": "shellscript", diff --git a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json index c6d5110bd02e4..ac6f7b5ee6a31 100644 --- a/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json +++ b/extensions/markdown-basics/syntaxes/markdown.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/548ccb91ef58ba40ac745b400d889933ccd5eb4d", + "version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/0812fc4b190efc17bfed0d5b4ff918eff8e4e377", "name": "Markdown", "scopeName": "text.html.markdown", "patterns": [ @@ -959,6 +959,39 @@ } ] }, + "fenced_code_block_ignore": { + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(gitignore|ignore)((\\s+|:|,|\\{|\\?)[^`]*)?$)", + "name": "markup.fenced_code.block.markdown", + "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", + "beginCaptures": { + "3": { + "name": "punctuation.definition.markdown" + }, + "4": { + "name": "fenced_code.block.language.markdown" + }, + "5": { + "name": "fenced_code.block.language.attributes.markdown" + } + }, + "endCaptures": { + "3": { + "name": "punctuation.definition.markdown" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", + "contentName": "meta.embedded.block.ignore", + "patterns": [ + { + "include": "source.ignore" + } + ] + } + ] + }, "fenced_code_block_js": { "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(js|jsx|javascript|es6|mjs|cjs|dataviewjs|\\{\\.js.+?\\})((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", @@ -1091,6 +1124,39 @@ } ] }, + "fenced_code_block_jsonl": { + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(jsonl|jsonlines)((\\s+|:|,|\\{|\\?)[^`]*)?$)", + "name": "markup.fenced_code.block.markdown", + "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", + "beginCaptures": { + "3": { + "name": "punctuation.definition.markdown" + }, + "4": { + "name": "fenced_code.block.language.markdown" + }, + "5": { + "name": "fenced_code.block.language.attributes.markdown" + } + }, + "endCaptures": { + "3": { + "name": "punctuation.definition.markdown" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", + "contentName": "meta.embedded.block.jsonl", + "patterns": [ + { + "include": "source.json.lines" + } + ] + } + ] + }, "fenced_code_block_less": { "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(less)((\\s+|:|,|\\{|\\?)[^`]*)?$)", "name": "markup.fenced_code.block.markdown", @@ -1916,6 +1982,105 @@ } ] }, + "fenced_code_block_yang": { + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(yang)((\\s+|:|,|\\{|\\?)[^`]*)?$)", + "name": "markup.fenced_code.block.markdown", + "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", + "beginCaptures": { + "3": { + "name": "punctuation.definition.markdown" + }, + "4": { + "name": "fenced_code.block.language.markdown" + }, + "5": { + "name": "fenced_code.block.language.attributes.markdown" + } + }, + "endCaptures": { + "3": { + "name": "punctuation.definition.markdown" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", + "contentName": "meta.embedded.block.yang", + "patterns": [ + { + "include": "source.yang" + } + ] + } + ] + }, + "fenced_code_block_abap": { + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(abap)((\\s+|:|,|\\{|\\?)[^`]*)?$)", + "name": "markup.fenced_code.block.markdown", + "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", + "beginCaptures": { + "3": { + "name": "punctuation.definition.markdown" + }, + "4": { + "name": "fenced_code.block.language.markdown" + }, + "5": { + "name": "fenced_code.block.language.attributes.markdown" + } + }, + "endCaptures": { + "3": { + "name": "punctuation.definition.markdown" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", + "contentName": "meta.embedded.block.abap", + "patterns": [ + { + "include": "source.abap" + } + ] + } + ] + }, + "fenced_code_block_restructuredtext": { + "begin": "(^|\\G)(\\s*)(`{3,}|~{3,})\\s*(?i:(restructuredtext|rst)((\\s+|:|,|\\{|\\?)[^`]*)?$)", + "name": "markup.fenced_code.block.markdown", + "end": "(^|\\G)(\\2|\\s{0,3})(\\3)\\s*$", + "beginCaptures": { + "3": { + "name": "punctuation.definition.markdown" + }, + "4": { + "name": "fenced_code.block.language.markdown" + }, + "5": { + "name": "fenced_code.block.language.attributes.markdown" + } + }, + "endCaptures": { + "3": { + "name": "punctuation.definition.markdown" + } + }, + "patterns": [ + { + "begin": "(^|\\G)(\\s*)(.*)", + "while": "(^|\\G)(?!\\s*([`~]{3,})\\s*$)", + "contentName": "meta.embedded.block.restructuredtext", + "patterns": [ + { + "include": "source.rst" + } + ] + } + ] + }, "fenced_code_block": { "patterns": [ { @@ -1999,6 +2164,9 @@ { "include": "#fenced_code_block_pug" }, + { + "include": "#fenced_code_block_ignore" + }, { "include": "#fenced_code_block_js" }, @@ -2011,6 +2179,9 @@ { "include": "#fenced_code_block_jsonc" }, + { + "include": "#fenced_code_block_jsonl" + }, { "include": "#fenced_code_block_less" }, @@ -2086,6 +2257,15 @@ { "include": "#fenced_code_block_twig" }, + { + "include": "#fenced_code_block_yang" + }, + { + "include": "#fenced_code_block_abap" + }, + { + "include": "#fenced_code_block_restructuredtext" + }, { "include": "#fenced_code_block_unknown" } diff --git a/extensions/markdown-language-features/.vscodeignore b/extensions/markdown-language-features/.vscodeignore index 0d35b62002d6d..a5b7a3ec72ca5 100644 --- a/extensions/markdown-language-features/.vscodeignore +++ b/extensions/markdown-language-features/.vscodeignore @@ -12,7 +12,6 @@ cgmanifest.json package-lock.json preview-src/** webpack.config.js -esbuild-notebook.js -esbuild-preview.js +esbuild-* .gitignore **/*.d.ts diff --git a/extensions/markdown-language-features/esbuild-notebook.js b/extensions/markdown-language-features/esbuild-notebook.js deleted file mode 100644 index 87275940f4ca6..0000000000000 --- a/extensions/markdown-language-features/esbuild-notebook.js +++ /dev/null @@ -1,17 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -const path = require('path'); - -const srcDir = path.join(__dirname, 'notebook'); -const outDir = path.join(__dirname, 'notebook-out'); - -require('../esbuild-webview-common').run({ - entryPoints: [ - path.join(srcDir, 'index.ts'), - ], - srcDir, - outdir: outDir, -}, process.argv); diff --git a/extensions/markdown-language-features/esbuild-notebook.mjs b/extensions/markdown-language-features/esbuild-notebook.mjs new file mode 100644 index 0000000000000..933e77d21a553 --- /dev/null +++ b/extensions/markdown-language-features/esbuild-notebook.mjs @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import path from 'path'; +import { run } from '../esbuild-webview-common.mjs'; + +const srcDir = path.join(import.meta.dirname, 'notebook'); +const outDir = path.join(import.meta.dirname, 'notebook-out'); + +run({ + entryPoints: [ + path.join(srcDir, 'index.ts'), + ], + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/markdown-language-features/esbuild-preview.js b/extensions/markdown-language-features/esbuild-preview.js deleted file mode 100644 index 5a2e51cca09e0..0000000000000 --- a/extensions/markdown-language-features/esbuild-preview.js +++ /dev/null @@ -1,18 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -const path = require('path'); - -const srcDir = path.join(__dirname, 'preview-src'); -const outDir = path.join(__dirname, 'media'); - -require('../esbuild-webview-common').run({ - entryPoints: [ - path.join(srcDir, 'index.ts'), - path.join(srcDir, 'pre'), - ], - srcDir, - outdir: outDir, -}, process.argv); diff --git a/extensions/markdown-language-features/esbuild-preview.mjs b/extensions/markdown-language-features/esbuild-preview.mjs new file mode 100644 index 0000000000000..1d3fc48b9bc66 --- /dev/null +++ b/extensions/markdown-language-features/esbuild-preview.mjs @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import path from 'path'; +import { run } from '../esbuild-webview-common.mjs'; + +const srcDir = path.join(import.meta.dirname, 'preview-src'); +const outDir = path.join(import.meta.dirname, 'media'); + +run({ + entryPoints: [ + path.join(srcDir, 'index.ts'), + path.join(srcDir, 'pre'), + ], + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/markdown-language-features/extension-browser.webpack.config.js b/extensions/markdown-language-features/extension-browser.webpack.config.js index 47f50aa5e8f8e..5471319a4c48e 100644 --- a/extensions/markdown-language-features/extension-browser.webpack.config.js +++ b/extensions/markdown-language-features/extension-browser.webpack.config.js @@ -2,21 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import CopyPlugin from 'copy-webpack-plugin'; +import { browser, browserPlugins } from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const CopyPlugin = require('copy-webpack-plugin'); -const { browserPlugins, browser } = require('../shared.webpack.config'); - -module.exports = browser({ - context: __dirname, +export default browser({ + context: import.meta.dirname, entry: { extension: './src/extension.browser.ts' }, plugins: [ - ...browserPlugins(__dirname), // add plugins, don't replace inherited + ...browserPlugins(import.meta.dirname), // add plugins, don't replace inherited new CopyPlugin({ patterns: [ { diff --git a/extensions/markdown-language-features/extension.webpack.config.js b/extensions/markdown-language-features/extension.webpack.config.js index 588d0632fd21b..51c9912f9afe0 100644 --- a/extensions/markdown-language-features/extension.webpack.config.js +++ b/extensions/markdown-language-features/extension.webpack.config.js @@ -2,16 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import CopyPlugin from 'copy-webpack-plugin'; +import withDefaults, { nodePlugins } from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const CopyPlugin = require('copy-webpack-plugin'); -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, resolve: { mainFields: ['module', 'main'] }, @@ -19,7 +15,7 @@ module.exports = withDefaults({ extension: './src/extension.ts', }, plugins: [ - ...withDefaults.nodePlugins(__dirname), // add plugins, don't replace inherited + ...nodePlugins(import.meta.dirname), // add plugins, don't replace inherited new CopyPlugin({ patterns: [ { diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index b7c1471c7dc5d..1c6e68f4de828 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -380,6 +380,7 @@ function addNamedHeaderRendering(md: InstanceType): void { const originalRender = md.render; md.render = function () { slugCounter.clear(); + // eslint-disable-next-line local/code-no-any-casts return originalRender.apply(this, arguments as any); }; } diff --git a/extensions/markdown-language-features/notebook/tsconfig.json b/extensions/markdown-language-features/notebook/tsconfig.json index 426411ea85773..90241aa7803a1 100644 --- a/extensions/markdown-language-features/notebook/tsconfig.json +++ b/extensions/markdown-language-features/notebook/tsconfig.json @@ -3,13 +3,16 @@ "compilerOptions": { "outDir": "./dist/", "jsx": "react", - "moduleResolution": "Node", - "allowSyntheticDefaultImports": true, - "module": "es2020", + "module": "esnext", "lib": [ "ES2024", "DOM", "DOM.Iterable" - ] + ], + "types": [], + "typeRoots": [ + "../node_modules/@types" + ], + "skipLibCheck": true } } diff --git a/extensions/markdown-language-features/package-lock.json b/extensions/markdown-language-features/package-lock.json index 4d17bbc1f6d48..bb129cc36240e 100644 --- a/extensions/markdown-language-features/package-lock.json +++ b/extensions/markdown-language-features/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", - "dompurify": "^3.2.4", + "dompurify": "^3.2.7", "highlight.js": "^11.8.0", "markdown-it": "^12.3.2", "markdown-it-front-matter": "^0.2.4", @@ -24,8 +24,9 @@ }, "devDependencies": { "@types/dompurify": "^3.0.5", - "@types/lodash.throttle": "^4.1.3", + "@types/lodash.throttle": "^4.1.9", "@types/markdown-it": "12.2.3", + "@types/node": "22.x", "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", "@types/vscode-webview": "^1.57.0", @@ -184,10 +185,11 @@ "dev": true }, "node_modules/@types/lodash.throttle": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.3.tgz", - "integrity": "sha512-FUm7uMuYRX7dzqmgX02bxdBwC75owUxGA4dDKtFePDLJ6N1ofXxkRX3NhJV8wOrNs/wCjaY6sDVJrD1lbyERoQ==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.9.tgz", + "integrity": "sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g==", "dev": true, + "license": "MIT", "dependencies": { "@types/lodash": "*" } @@ -208,6 +210,16 @@ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "dev": true }, + "node_modules/@types/node": { + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@types/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.0.tgz", @@ -279,9 +291,10 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -372,9 +385,9 @@ } }, "node_modules/dompurify": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz", - "integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -575,6 +588,13 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, + "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" + }, "node_modules/vscode-jsonrpc": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2.tgz", diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index da79dca2a4dd1..3abd4436b3d68 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -181,13 +181,13 @@ "command": "markdown.editor.insertLinkFromWorkspace", "title": "%markdown.editor.insertLinkFromWorkspace%", "category": "Markdown", - "enablement": "editorLangId == markdown && !activeEditorIsReadonly" + "enablement": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !activeEditorIsReadonly" }, { "command": "markdown.editor.insertImageFromWorkspace", "title": "%markdown.editor.insertImageFromWorkspace%", "category": "Markdown", - "enablement": "editorLangId == markdown && !activeEditorIsReadonly" + "enablement": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !activeEditorIsReadonly" } ], "menus": { @@ -204,7 +204,7 @@ "editor/title": [ { "command": "markdown.showPreviewToSide", - "when": "editorLangId == markdown && !notebookEditorFocused && !hasCustomMarkdownPreview", + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused && !hasCustomMarkdownPreview", "alt": "markdown.showPreview", "group": "navigation" }, @@ -232,24 +232,24 @@ "explorer/context": [ { "command": "markdown.showPreview", - "when": "resourceLangId == markdown && !hasCustomMarkdownPreview", + "when": "resourceLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !hasCustomMarkdownPreview", "group": "navigation" }, { "command": "markdown.findAllFileReferences", - "when": "resourceLangId == markdown", + "when": "resourceLangId =~ /^(markdown|prompt|instructions|chatmode)$/", "group": "4_search" } ], "editor/title/context": [ { "command": "markdown.showPreview", - "when": "resourceLangId == markdown && !hasCustomMarkdownPreview", + "when": "resourceLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !hasCustomMarkdownPreview", "group": "1_open" }, { "command": "markdown.findAllFileReferences", - "when": "resourceLangId == markdown" + "when": "resourceLangId =~ /^(markdown|prompt|instructions|chatmode)$/" } ], "commandPalette": [ @@ -263,17 +263,17 @@ }, { "command": "markdown.showPreview", - "when": "editorLangId == markdown && !notebookEditorFocused", + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused", "group": "navigation" }, { "command": "markdown.showPreviewToSide", - "when": "editorLangId == markdown && !notebookEditorFocused", + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused", "group": "navigation" }, { "command": "markdown.showLockedPreviewToSide", - "when": "editorLangId == markdown && !notebookEditorFocused", + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused", "group": "navigation" }, { @@ -283,7 +283,7 @@ }, { "command": "markdown.showPreviewSecuritySelector", - "when": "editorLangId == markdown && !notebookEditorFocused" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused" }, { "command": "markdown.showPreviewSecuritySelector", @@ -295,7 +295,7 @@ }, { "command": "markdown.preview.refresh", - "when": "editorLangId == markdown && !notebookEditorFocused" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused" }, { "command": "markdown.preview.refresh", @@ -303,7 +303,7 @@ }, { "command": "markdown.findAllFileReferences", - "when": "editorLangId == markdown" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/" } ] }, @@ -312,13 +312,13 @@ "command": "markdown.showPreview", "key": "shift+ctrl+v", "mac": "shift+cmd+v", - "when": "editorLangId == markdown && !notebookEditorFocused" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused" }, { "command": "markdown.showPreviewToSide", "key": "ctrl+k v", "mac": "cmd+k v", - "when": "editorLangId == markdown && !notebookEditorFocused" + "when": "editorLangId =~ /^(markdown|prompt|instructions|chatmode)$/ && !notebookEditorFocused" } ], "configuration": { @@ -759,15 +759,15 @@ "compile": "gulp compile-extension:markdown-language-features-languageService && gulp compile-extension:markdown-language-features && npm run build-preview && npm run build-notebook", "watch": "npm run build-preview && gulp watch-extension:markdown-language-features watch-extension:markdown-language-features-languageService", "vscode:prepublish": "npm run build-ext && npm run build-preview", - "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", - "build-notebook": "node ./esbuild-notebook", - "build-preview": "node ./esbuild-preview", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:markdown-language-features ./tsconfig.json", + "build-notebook": "node ./esbuild-notebook.mjs", + "build-preview": "node ./esbuild-preview.mjs", "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", - "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" + "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch" }, "dependencies": { "@vscode/extension-telemetry": "^0.9.8", - "dompurify": "^3.2.4", + "dompurify": "^3.2.7", "highlight.js": "^11.8.0", "markdown-it": "^12.3.2", "markdown-it-front-matter": "^0.2.4", @@ -781,8 +781,9 @@ }, "devDependencies": { "@types/dompurify": "^3.0.5", - "@types/lodash.throttle": "^4.1.3", + "@types/lodash.throttle": "^4.1.9", "@types/markdown-it": "12.2.3", + "@types/node": "22.x", "@types/picomatch": "^2.3.0", "@types/vscode-notebook-renderer": "^1.60.0", "@types/vscode-webview": "^1.57.0", diff --git a/extensions/markdown-language-features/preview-src/csp.ts b/extensions/markdown-language-features/preview-src/csp.ts index ea960dd4ad3ff..fcc38352da880 100644 --- a/extensions/markdown-language-features/preview-src/csp.ts +++ b/extensions/markdown-language-features/preview-src/csp.ts @@ -24,7 +24,7 @@ export class CspAlerter { }); window.addEventListener('message', (event) => { - if (event && event.data && event.data.name === 'vscode-did-block-svg') { + if (event?.data && event.data.name === 'vscode-did-block-svg') { this._onCspWarning(); } }); diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 6c70e58ee7e52..b6200b8ceb9ff 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -23,6 +23,7 @@ let documentResource = settings.settings.source; const vscode = acquireVsCodeApi(); +// eslint-disable-next-line local/code-no-any-casts const originalState = vscode.getState() ?? {} as any; const state = { ...originalState, @@ -249,6 +250,7 @@ window.addEventListener('message', async event => { } newRoot.prepend(...styles); + // eslint-disable-next-line local/code-no-any-casts morphdom(root, newRoot, { childrenOnly: true, onBeforeElUpdated: (fromEl: Element, toEl: Element) => { @@ -304,6 +306,11 @@ document.addEventListener('dblclick', event => { return; } + // Disable double-click to switch editor for .copilotmd files + if (documentResource.endsWith('.copilotmd')) { + return; + } + // Ignore clicks on links for (let node = event.target as HTMLElement; node; node = node.parentNode as HTMLElement) { if (node.tagName === 'A') { @@ -432,8 +439,9 @@ function domEval(el: Element): void { const trustedScript = node.innerText; scriptTag.text = trustedScript as string; for (const key of preservedScriptAttributes) { - const val = node.getAttribute && node.getAttribute(key); + const val = node.getAttribute?.(key); if (val) { + // eslint-disable-next-line local/code-no-any-casts scriptTag.setAttribute(key, val as any); } } diff --git a/extensions/markdown-language-features/preview-src/loading.ts b/extensions/markdown-language-features/preview-src/loading.ts index bebd440374d52..c6e6d27acd904 100644 --- a/extensions/markdown-language-features/preview-src/loading.ts +++ b/extensions/markdown-language-features/preview-src/loading.ts @@ -5,7 +5,7 @@ import { MessagePoster } from './messaging'; export class StyleLoadingMonitor { - private _unloadedStyles: string[] = []; + private readonly _unloadedStyles: string[] = []; private _finishedLoading: boolean = false; private _poster?: MessagePoster; diff --git a/extensions/markdown-language-features/preview-src/scroll-sync.ts b/extensions/markdown-language-features/preview-src/scroll-sync.ts index cba22fc48d5f9..33d81094cb59f 100644 --- a/extensions/markdown-language-features/preview-src/scroll-sync.ts +++ b/extensions/markdown-language-features/preview-src/scroll-sync.ts @@ -20,7 +20,21 @@ export class CodeLineElement { } get isVisible(): boolean { - return !this._detailParentElements.some(x => !x.open); + if (this._detailParentElements.some(x => !x.open)) { + return false; + } + + const style = window.getComputedStyle(this.element); + if (style.display === 'none' || style.visibility === 'hidden') { + return false; + } + + const bounds = this.element.getBoundingClientRect(); + if (bounds.height === 0 || bounds.width === 0) { + return false; + } + + return true; } } diff --git a/extensions/markdown-language-features/preview-src/tsconfig.json b/extensions/markdown-language-features/preview-src/tsconfig.json index e401dbe43fbcf..4001ffeb06886 100644 --- a/extensions/markdown-language-features/preview-src/tsconfig.json +++ b/extensions/markdown-language-features/preview-src/tsconfig.json @@ -8,7 +8,14 @@ "es2024", "DOM", "DOM.Iterable" - ] + ], + "types": [ + "vscode-webview" + ], + "typeRoots": [ + "../node_modules/@types" + ], + "skipLibCheck": true }, "typeAcquisition": { "include": [ diff --git a/extensions/markdown-language-features/src/client/workspace.ts b/extensions/markdown-language-features/src/client/workspace.ts index e690861158683..9ea3173c9cc1e 100644 --- a/extensions/markdown-language-features/src/client/workspace.ts +++ b/extensions/markdown-language-features/src/client/workspace.ts @@ -17,7 +17,7 @@ import { ResourceMap } from '../util/resourceMap'; */ export class VsCodeMdWorkspace extends Disposable { - private _watcher: vscode.FileSystemWatcher | undefined; + private readonly _watcher: vscode.FileSystemWatcher | undefined; private readonly _documentCache = new ResourceMap(); diff --git a/extensions/markdown-language-features/src/commands/showPreview.ts b/extensions/markdown-language-features/src/commands/showPreview.ts index c2933ff2edfc0..d5d430ade0ee6 100644 --- a/extensions/markdown-language-features/src/commands/showPreview.ts +++ b/extensions/markdown-language-features/src/commands/showPreview.ts @@ -37,7 +37,7 @@ async function showPreview( return; } - const resourceColumn = (vscode.window.activeTextEditor && vscode.window.activeTextEditor.viewColumn) || vscode.ViewColumn.One; + const resourceColumn = vscode.window.activeTextEditor?.viewColumn || vscode.ViewColumn.One; webviewManager.openDynamicPreview(resource, { resourceColumn: resourceColumn, previewColumn: previewSettings.sideBySide ? vscode.ViewColumn.Beside : resourceColumn, @@ -62,7 +62,7 @@ export class ShowPreviewCommand implements Command { for (const uri of Array.isArray(allUris) ? allUris : [mainUri]) { showPreview(this._webviewManager, this._telemetryReporter, uri, { sideBySide: false, - locked: previewSettings && previewSettings.locked + locked: previewSettings?.locked }); } } @@ -79,7 +79,7 @@ export class ShowPreviewToSideCommand implements Command { public execute(uri?: vscode.Uri, previewSettings?: DynamicPreviewSettings) { showPreview(this._webviewManager, this._telemetryReporter, uri, { sideBySide: true, - locked: previewSettings && previewSettings.locked + locked: previewSettings?.locked }); } } diff --git a/extensions/markdown-language-features/src/extension.shared.ts b/extensions/markdown-language-features/src/extension.shared.ts index e062666c748d4..6f245bd8ab708 100644 --- a/extensions/markdown-language-features/src/extension.shared.ts +++ b/extensions/markdown-language-features/src/extension.shared.ts @@ -21,6 +21,7 @@ import { ExtensionContentSecurityPolicyArbiter } from './preview/security'; import { loadDefaultTelemetryReporter } from './telemetryReporter'; import { MdLinkOpener } from './util/openDocumentLink'; import { registerUpdatePastedLinks } from './languageFeatures/updateLinksOnPaste'; +import { markdownLanguageIds } from './util/file'; export function activateShared( context: vscode.ExtensionContext, @@ -54,7 +55,7 @@ function registerMarkdownLanguageFeatures( commandManager: CommandManager, parser: IMdParser, ): vscode.Disposable { - const selector: vscode.DocumentSelector = { language: 'markdown', scheme: '*' }; + const selector: vscode.DocumentSelector = markdownLanguageIds; return vscode.Disposable.from( // Language features registerDiagnosticSupport(selector, commandManager), diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index 48e579da7fceb..095e8f25c1226 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { CommandManager } from '../commandManager'; +import { isMarkdownFile } from '../util/file'; // Copied from markdown language service @@ -50,6 +51,7 @@ class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { case DiagnosticCode.link_noSuchHeaderInOwnFile: case DiagnosticCode.link_noSuchFile: case DiagnosticCode.link_noSuchHeaderInFile: { + // eslint-disable-next-line local/code-no-any-casts const hrefText = (diagnostic as any).data?.hrefText; if (hrefText) { const fix = new vscode.CodeAction( @@ -87,7 +89,7 @@ function registerMarkdownStatusItem(selector: vscode.DocumentSelector, commandMa const update = () => { const activeDoc = vscode.window.activeTextEditor?.document; - const markdownDoc = activeDoc?.languageId === 'markdown' ? activeDoc : undefined; + const markdownDoc = activeDoc && isMarkdownFile(activeDoc) ? activeDoc : undefined; const enabled = vscode.workspace.getConfiguration('markdown', markdownDoc).get(enabledSettingId); if (enabled) { diff --git a/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts b/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts index 5d2cf053eb4c7..5d42a033842c0 100644 --- a/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts +++ b/extensions/markdown-language-features/src/languageFeatures/linkUpdater.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; import * as picomatch from 'picomatch'; import * as vscode from 'vscode'; import { TextDocumentEdit } from 'vscode-languageclient'; +import { Utils } from 'vscode-uri'; import { MdLanguageClient } from '../client/client'; import { Delayer } from '../util/async'; import { noopToken } from '../util/cancellation'; @@ -65,7 +65,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable { const result = await this._getEditsForFileRename(renames, noopToken); - if (result && result.edit.size) { + if (result?.edit.size) { if (await this._confirmActionWithUser(result.resourcesBeingRenamed)) { await vscode.workspace.applyEdit(result.edit); } @@ -137,7 +137,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable { const choice = await vscode.window.showInformationMessage( newResources.length === 1 - ? vscode.l10n.t("Update Markdown links for '{0}'?", path.basename(newResources[0].fsPath)) + ? vscode.l10n.t("Update Markdown links for '{0}'?", Utils.basename(newResources[0])) : this._getConfirmMessage(vscode.l10n.t("Update Markdown links for the following {0} files?", newResources.length), newResources), { modal: true, }, rejectItem, acceptItem, alwaysItem, neverItem); @@ -197,7 +197,7 @@ class UpdateLinksOnFileRenameHandler extends Disposable { const paths = [start]; paths.push(''); - paths.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => path.basename(r.fsPath))); + paths.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => Utils.basename(r))); if (resourcesToConfirm.length > MAX_CONFIRM_FILES) { if (resourcesToConfirm.length - MAX_CONFIRM_FILES === 1) { diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 89363a77a86d4..e2cea47e7183d 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -9,7 +9,7 @@ import * as vscode from 'vscode'; import { ILogger } from './logging'; import { MarkdownContributionProvider } from './markdownExtensions'; import { MarkdownPreviewConfiguration } from './preview/previewConfig'; -import { Slugifier } from './slugify'; +import { ISlugifier, SlugBuilder } from './slugify'; import { ITextDocument } from './types/textDocument'; import { WebviewResourceProvider } from './util/resources'; import { isOfScheme, Schemes } from './util/schemes'; @@ -85,13 +85,14 @@ export interface RenderOutput { } interface RenderEnv { - containingImages: Set; - currentDocument: vscode.Uri | undefined; - resourceProvider: WebviewResourceProvider | undefined; + readonly containingImages: Set; + readonly currentDocument: vscode.Uri | undefined; + readonly resourceProvider: WebviewResourceProvider | undefined; + readonly slugifier: SlugBuilder; } export interface IMdParser { - readonly slugifier: Slugifier; + readonly slugifier: ISlugifier; tokenize(document: ITextDocument): Promise; } @@ -100,14 +101,13 @@ export class MarkdownItEngine implements IMdParser { private _md?: Promise; - private _slugCount = new Map(); - private _tokenCache = new TokenCache(); + private readonly _tokenCache = new TokenCache(); - public readonly slugifier: Slugifier; + public readonly slugifier: ISlugifier; public constructor( private readonly _contributionProvider: MarkdownContributionProvider, - slugifier: Slugifier, + slugifier: ISlugifier, private readonly _logger: ILogger, ) { this.slugifier = slugifier; @@ -143,6 +143,7 @@ export class MarkdownItEngine implements IMdParser { const frontMatterPlugin = await import('markdown-it-front-matter'); // Extract rules from front matter plugin and apply at a lower precedence let fontMatterRule: any; + // eslint-disable-next-line local/code-no-any-casts frontMatterPlugin.default({ block: { ruler: { @@ -182,7 +183,6 @@ export class MarkdownItEngine implements IMdParser { ): Token[] { const cached = this._tokenCache.tryGetCached(document, config); if (cached) { - this._resetSlugCount(); return cached; } @@ -193,13 +193,13 @@ export class MarkdownItEngine implements IMdParser { } private _tokenizeString(text: string, engine: MarkdownIt) { - this._resetSlugCount(); - - return engine.parse(text, {}); - } - - private _resetSlugCount(): void { - this._slugCount = new Map(); + const env: RenderEnv = { + currentDocument: undefined, + containingImages: new Set(), + slugifier: this.slugifier.createBuilder(), + resourceProvider: undefined, + }; + return engine.parse(text, env); } public async render(input: ITextDocument | string, resourceProvider?: WebviewResourceProvider): Promise { @@ -214,6 +214,7 @@ export class MarkdownItEngine implements IMdParser { containingImages: new Set(), currentDocument: typeof input === 'string' ? undefined : input.uri, resourceProvider, + slugifier: this.slugifier.createBuilder(), }; const html = engine.renderer.render(tokens, { @@ -312,18 +313,9 @@ export class MarkdownItEngine implements IMdParser { private _addNamedHeaders(md: MarkdownIt): void { const original = md.renderer.rules.heading_open; - md.renderer.rules.heading_open = (tokens: Token[], idx: number, options, env, self) => { + md.renderer.rules.heading_open = (tokens: Token[], idx: number, options, env: unknown, self) => { const title = this._tokenToPlainText(tokens[idx + 1]); - let slug = this.slugifier.fromHeading(title); - - if (this._slugCount.has(slug.value)) { - const count = this._slugCount.get(slug.value)!; - this._slugCount.set(slug.value, count + 1); - slug = this.slugifier.fromHeading(slug.value + '-' + (count + 1)); - } else { - this._slugCount.set(slug.value, 0); - } - + const slug = (env as RenderEnv).slugifier ? (env as RenderEnv).slugifier.add(title) : this.slugifier.fromHeading(title); tokens[idx].attrSet('id', slug.value); if (original) { @@ -433,7 +425,7 @@ async function getMarkdownOptions(md: () => MarkdownIt): Promise any>>(); if (contributes['markdown.markdownItPlugins']) { map.set(extension.id, extension.activate().then(() => { - if (extension.exports && extension.exports.extendMarkdownIt) { + if (extension.exports?.extendMarkdownIt) { return (md: any) => extension.exports.extendMarkdownIt(md); } return (md: any) => md; diff --git a/extensions/markdown-language-features/src/preview/documentRenderer.ts b/extensions/markdown-language-features/src/preview/documentRenderer.ts index c7bc75b984907..61182a2443684 100644 --- a/extensions/markdown-language-features/src/preview/documentRenderer.ts +++ b/extensions/markdown-language-features/src/preview/documentRenderer.ts @@ -8,8 +8,9 @@ import * as uri from 'vscode-uri'; import { ILogger } from '../logging'; import { MarkdownItEngine } from '../markdownEngine'; import { MarkdownContributionProvider } from '../markdownExtensions'; -import { escapeAttribute, getNonce } from '../util/dom'; +import { escapeAttribute } from '../util/dom'; import { WebviewResourceProvider } from '../util/resources'; +import { generateUuid } from '../util/uuid'; import { MarkdownPreviewConfiguration, MarkdownPreviewConfigurationManager } from './previewConfig'; import { ContentSecurityPolicyArbiter, MarkdownPreviewSecurityLevel } from './security'; @@ -82,7 +83,7 @@ export class MdDocumentRenderer { this._logger.trace('DocumentRenderer', `provideTextDocumentContent - ${markdownDocument.uri}`, initialData); // Content Security Policy - const nonce = getNonce(); + const nonce = generateUuid(); const csp = this._getCsp(resourceProvider, sourceUri, nonce); const body = await this.renderBody(markdownDocument, resourceProvider); diff --git a/extensions/markdown-language-features/src/preview/preview.ts b/extensions/markdown-language-features/src/preview/preview.ts index b5f87d4c0902a..19d1755e7eb4f 100644 --- a/extensions/markdown-language-features/src/preview/preview.ts +++ b/extensions/markdown-language-features/src/preview/preview.ts @@ -53,7 +53,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { private readonly _webviewPanel: vscode.WebviewPanel; private _line: number | undefined; - private _scrollToFragment: string | undefined; + private readonly _scrollToFragment: string | undefined; private _firstUpdate = true; private _currentVersion?: PreviewDocumentVersion; private _isScrolling = false; @@ -110,15 +110,17 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { } })); - const watcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(resource, '*'))); - this._register(watcher.onDidChange(uri => { - if (this.isPreviewOf(uri)) { - // Only use the file system event when VS Code does not already know about the file - if (!vscode.workspace.textDocuments.some(doc => doc.uri.toString() === uri.toString())) { - this.refresh(); + if (vscode.workspace.fs.isWritableFileSystem(resource.scheme)) { + const watcher = this._register(vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(resource, '*'))); + this._register(watcher.onDidChange(uri => { + if (this.isPreviewOf(uri)) { + // Only use the file system event when VS Code does not already know about the file + if (!vscode.workspace.textDocuments.some(doc => doc.uri.toString() === uri.toString())) { + this.refresh(); + } } - } - })); + })); + } this._register(this._webviewPanel.webview.onDidReceiveMessage((e: FromWebviewMessage.Type) => { if (e.source !== this._resource.toString()) { diff --git a/extensions/markdown-language-features/src/preview/previewConfig.ts b/extensions/markdown-language-features/src/preview/previewConfig.ts index 01c9a66c32c9e..01d6340337763 100644 --- a/extensions/markdown-language-features/src/preview/previewConfig.ts +++ b/extensions/markdown-language-features/src/preview/previewConfig.ts @@ -36,7 +36,7 @@ export class MarkdownPreviewConfiguration { this.scrollBeyondLastLine = editorConfig.get('scrollBeyondLastLine', false); this.wordWrap = editorConfig.get('wordWrap', 'off') !== 'off'; - if (markdownEditorConfig && markdownEditorConfig['editor.wordWrap']) { + if (markdownEditorConfig?.['editor.wordWrap']) { this.wordWrap = markdownEditorConfig['editor.wordWrap'] !== 'off'; } @@ -83,13 +83,11 @@ export class MarkdownPreviewConfigurationManager { return config; } - public hasConfigurationChanged( - resource: vscode.Uri - ): boolean { + public hasConfigurationChanged(resource: vscode.Uri): boolean { const key = this._getKey(resource); const currentConfig = this._previewConfigurationsForWorkspaces.get(key); const newConfig = MarkdownPreviewConfiguration.getForResource(resource); - return (!currentConfig || !currentConfig.isEqualTo(newConfig)); + return !currentConfig?.isEqualTo(newConfig); } private _getKey( diff --git a/extensions/markdown-language-features/src/preview/topmostLineMonitor.ts b/extensions/markdown-language-features/src/preview/topmostLineMonitor.ts index 3db994514a465..d676fa9ffbcfa 100644 --- a/extensions/markdown-language-features/src/preview/topmostLineMonitor.ts +++ b/extensions/markdown-language-features/src/preview/topmostLineMonitor.ts @@ -17,8 +17,8 @@ export class TopmostLineMonitor extends Disposable { private readonly _pendingUpdates = new ResourceMap(); private readonly _throttle = 50; - private _previousTextEditorInfo = new ResourceMap(); - private _previousStaticEditorInfo = new ResourceMap(); + private readonly _previousTextEditorInfo = new ResourceMap(); + private readonly _previousStaticEditorInfo = new ResourceMap(); constructor() { super(); diff --git a/extensions/markdown-language-features/src/slugify.ts b/extensions/markdown-language-features/src/slugify.ts index 0d4b1896d8c3a..645d9ab6ae80e 100644 --- a/extensions/markdown-language-features/src/slugify.ts +++ b/extensions/markdown-language-features/src/slugify.ts @@ -3,31 +3,86 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export class Slug { +export interface ISlug { + readonly value: string; + equals(other: ISlug): boolean; +} + +export class GithubSlug implements ISlug { public constructor( public readonly value: string ) { } - public equals(other: Slug): boolean { - return this.value === other.value; + public equals(other: ISlug): boolean { + return other instanceof GithubSlug && this.value.toLowerCase() === other.value.toLowerCase(); } } -export interface Slugifier { - fromHeading(heading: string): Slug; +export interface SlugBuilder { + add(headingText: string): ISlug; } -export const githubSlugifier: Slugifier = new class implements Slugifier { - fromHeading(heading: string): Slug { - const slugifiedHeading = encodeURI( - heading.trim() - .toLowerCase() - .replace(/\s+/g, '-') // Replace whitespace with - - // allow-any-unicode-next-line - .replace(/[\]\[\!\/\'\"\#\$\%\&\(\)\*\+\,\.\/\:\;\<\=\>\?\@\\\^\{\|\}\~\`。,、;:?!…—·ˉ¨‘’“”々~‖∶"'`|〃〔〕〈〉《》「」『』.〖〗【】()[]{}]/g, '') // Remove known punctuators - .replace(/^\-+/, '') // Remove leading - - .replace(/\-+$/, '') // Remove trailing - - ); - return new Slug(slugifiedHeading); +/** + * Generates unique ids for headers in the Markdown. + */ +export interface ISlugifier { + /** + * Create a new slug from the text of a markdown heading. + * + * For a heading such as `# Header`, this will be called with `Header` + */ + fromHeading(headingText: string): ISlug; + + /** + * Create a slug from a link fragment. + * + * For a link such as `[text](#header)`, this will be called with `header` + */ + fromFragment(fragmentText: string): ISlug; + + /** + * Creates a stateful object that can be used to build slugs incrementally. + * + * This should be used when getting all slugs in a document as it handles duplicate headings + */ + createBuilder(): SlugBuilder; +} + +// Copied from https://github.com/Flet/github-slugger since we can't use esm yet. +// eslint-disable-next-line no-misleading-character-class +const githubSlugReplaceRegex = /[\0-\x1F!-,\.\/:-@\[-\^`\{-\xA9\xAB-\xB4\xB6-\xB9\xBB-\xBF\xD7\xF7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0378\u0379\u037E\u0380-\u0385\u0387\u038B\u038D\u03A2\u03F6\u0482\u0530\u0557\u0558\u055A-\u055F\u0589-\u0590\u05BE\u05C0\u05C3\u05C6\u05C8-\u05CF\u05EB-\u05EE\u05F3-\u060F\u061B-\u061F\u066A-\u066D\u06D4\u06DD\u06DE\u06E9\u06FD\u06FE\u0700-\u070F\u074B\u074C\u07B2-\u07BF\u07F6-\u07F9\u07FB\u07FC\u07FE\u07FF\u082E-\u083F\u085C-\u085F\u086B-\u089F\u08B5\u08C8-\u08D2\u08E2\u0964\u0965\u0970\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09F2-\u09FB\u09FD\u09FF\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF0-\u0AF8\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B54\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B70\u0B72-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BF0-\u0BFF\u0C0D\u0C11\u0C29\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B-\u0C5F\u0C64\u0C65\u0C70-\u0C7F\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0CFF\u0D0D\u0D11\u0D45\u0D49\u0D4F-\u0D53\u0D58-\u0D5E\u0D64\u0D65\u0D70-\u0D79\u0D80\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF4-\u0E00\u0E3B-\u0E3F\u0E4F\u0E5A-\u0E80\u0E83\u0E85\u0E8B\u0EA4\u0EA6\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F01-\u0F17\u0F1A-\u0F1F\u0F2A-\u0F34\u0F36\u0F38\u0F3A-\u0F3D\u0F48\u0F6D-\u0F70\u0F85\u0F98\u0FBD-\u0FC5\u0FC7-\u0FFF\u104A-\u104F\u109E\u109F\u10C6\u10C8-\u10CC\u10CE\u10CF\u10FB\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u1360-\u137F\u1390-\u139F\u13F6\u13F7\u13FE-\u1400\u166D\u166E\u1680\u169B-\u169F\u16EB-\u16ED\u16F9-\u16FF\u170D\u1715-\u171F\u1735-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17D4-\u17D6\u17D8-\u17DB\u17DE\u17DF\u17EA-\u180A\u180E\u180F\u181A-\u181F\u1879-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u1945\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DA-\u19FF\u1A1C-\u1A1F\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1AA6\u1AA8-\u1AAF\u1AC1-\u1AFF\u1B4C-\u1B4F\u1B5A-\u1B6A\u1B74-\u1B7F\u1BF4-\u1BFF\u1C38-\u1C3F\u1C4A-\u1C4C\u1C7E\u1C7F\u1C89-\u1C8F\u1CBB\u1CBC\u1CC0-\u1CCF\u1CD3\u1CFB-\u1CFF\u1DFA\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FBD\u1FBF-\u1FC1\u1FC5\u1FCD-\u1FCF\u1FD4\u1FD5\u1FDC-\u1FDF\u1FED-\u1FF1\u1FF5\u1FFD-\u203E\u2041-\u2053\u2055-\u2070\u2072-\u207E\u2080-\u208F\u209D-\u20CF\u20F1-\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F-\u215F\u2189-\u24B5\u24EA-\u2BFF\u2C2F\u2C5F\u2CE5-\u2CEA\u2CF4-\u2CFF\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D70-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E00-\u2E2E\u2E30-\u3004\u3008-\u3020\u3030\u3036\u3037\u303D-\u3040\u3097\u3098\u309B\u309C\u30A0\u30FB\u3100-\u3104\u3130\u318F-\u319F\u31C0-\u31EF\u3200-\u33FF\u4DC0-\u4DFF\u9FFD-\u9FFF\uA48D-\uA4CF\uA4FE\uA4FF\uA60D-\uA60F\uA62C-\uA63F\uA673\uA67E\uA6F2-\uA716\uA720\uA721\uA789\uA78A\uA7C0\uA7C1\uA7CB-\uA7F4\uA828-\uA82B\uA82D-\uA83F\uA874-\uA87F\uA8C6-\uA8CF\uA8DA-\uA8DF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA954-\uA95F\uA97D-\uA97F\uA9C1-\uA9CE\uA9DA-\uA9DF\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A-\uAA5F\uAA77-\uAA79\uAAC3-\uAADA\uAADE\uAADF\uAAF0\uAAF1\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB5B\uAB6A-\uAB6F\uABEB\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB29\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBB2-\uFBD2\uFD3E-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFC-\uFDFF\uFE10-\uFE1F\uFE30-\uFE32\uFE35-\uFE4C\uFE50-\uFE6F\uFE75\uFEFD-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF3E\uFF40\uFF5B-\uFF65\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFFF]|\uD800[\uDC0C\uDC27\uDC3B\uDC3E\uDC4E\uDC4F\uDC5E-\uDC7F\uDCFB-\uDD3F\uDD75-\uDDFC\uDDFE-\uDE7F\uDE9D-\uDE9F\uDED1-\uDEDF\uDEE1-\uDEFF\uDF20-\uDF2C\uDF4B-\uDF4F\uDF7B-\uDF7F\uDF9E\uDF9F\uDFC4-\uDFC7\uDFD0\uDFD6-\uDFFF]|\uD801[\uDC9E\uDC9F\uDCAA-\uDCAF\uDCD4-\uDCD7\uDCFC-\uDCFF\uDD28-\uDD2F\uDD64-\uDDFF\uDF37-\uDF3F\uDF56-\uDF5F\uDF68-\uDFFF]|\uD802[\uDC06\uDC07\uDC09\uDC36\uDC39-\uDC3B\uDC3D\uDC3E\uDC56-\uDC5F\uDC77-\uDC7F\uDC9F-\uDCDF\uDCF3\uDCF6-\uDCFF\uDD16-\uDD1F\uDD3A-\uDD7F\uDDB8-\uDDBD\uDDC0-\uDDFF\uDE04\uDE07-\uDE0B\uDE14\uDE18\uDE36\uDE37\uDE3B-\uDE3E\uDE40-\uDE5F\uDE7D-\uDE7F\uDE9D-\uDEBF\uDEC8\uDEE7-\uDEFF\uDF36-\uDF3F\uDF56-\uDF5F\uDF73-\uDF7F\uDF92-\uDFFF]|\uD803[\uDC49-\uDC7F\uDCB3-\uDCBF\uDCF3-\uDCFF\uDD28-\uDD2F\uDD3A-\uDE7F\uDEAA\uDEAD-\uDEAF\uDEB2-\uDEFF\uDF1D-\uDF26\uDF28-\uDF2F\uDF51-\uDFAF\uDFC5-\uDFDF\uDFF7-\uDFFF]|\uD804[\uDC47-\uDC65\uDC70-\uDC7E\uDCBB-\uDCCF\uDCE9-\uDCEF\uDCFA-\uDCFF\uDD35\uDD40-\uDD43\uDD48-\uDD4F\uDD74\uDD75\uDD77-\uDD7F\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDFF\uDE12\uDE38-\uDE3D\uDE3F-\uDE7F\uDE87\uDE89\uDE8E\uDE9E\uDEA9-\uDEAF\uDEEB-\uDEEF\uDEFA-\uDEFF\uDF04\uDF0D\uDF0E\uDF11\uDF12\uDF29\uDF31\uDF34\uDF3A\uDF45\uDF46\uDF49\uDF4A\uDF4E\uDF4F\uDF51-\uDF56\uDF58-\uDF5C\uDF64\uDF65\uDF6D-\uDF6F\uDF75-\uDFFF]|\uD805[\uDC4B-\uDC4F\uDC5A-\uDC5D\uDC62-\uDC7F\uDCC6\uDCC8-\uDCCF\uDCDA-\uDD7F\uDDB6\uDDB7\uDDC1-\uDDD7\uDDDE-\uDDFF\uDE41-\uDE43\uDE45-\uDE4F\uDE5A-\uDE7F\uDEB9-\uDEBF\uDECA-\uDEFF\uDF1B\uDF1C\uDF2C-\uDF2F\uDF3A-\uDFFF]|\uD806[\uDC3B-\uDC9F\uDCEA-\uDCFE\uDD07\uDD08\uDD0A\uDD0B\uDD14\uDD17\uDD36\uDD39\uDD3A\uDD44-\uDD4F\uDD5A-\uDD9F\uDDA8\uDDA9\uDDD8\uDDD9\uDDE2\uDDE5-\uDDFF\uDE3F-\uDE46\uDE48-\uDE4F\uDE9A-\uDE9C\uDE9E-\uDEBF\uDEF9-\uDFFF]|\uD807[\uDC09\uDC37\uDC41-\uDC4F\uDC5A-\uDC71\uDC90\uDC91\uDCA8\uDCB7-\uDCFF\uDD07\uDD0A\uDD37-\uDD39\uDD3B\uDD3E\uDD48-\uDD4F\uDD5A-\uDD5F\uDD66\uDD69\uDD8F\uDD92\uDD99-\uDD9F\uDDAA-\uDEDF\uDEF7-\uDFAF\uDFB1-\uDFFF]|\uD808[\uDF9A-\uDFFF]|\uD809[\uDC6F-\uDC7F\uDD44-\uDFFF]|[\uD80A\uD80B\uD80E-\uD810\uD812-\uD819\uD824-\uD82B\uD82D\uD82E\uD830-\uD833\uD837\uD839\uD83D\uD83F\uD87B-\uD87D\uD87F\uD885-\uDB3F\uDB41-\uDBFF][\uDC00-\uDFFF]|\uD80D[\uDC2F-\uDFFF]|\uD811[\uDE47-\uDFFF]|\uD81A[\uDE39-\uDE3F\uDE5F\uDE6A-\uDECF\uDEEE\uDEEF\uDEF5-\uDEFF\uDF37-\uDF3F\uDF44-\uDF4F\uDF5A-\uDF62\uDF78-\uDF7C\uDF90-\uDFFF]|\uD81B[\uDC00-\uDE3F\uDE80-\uDEFF\uDF4B-\uDF4E\uDF88-\uDF8E\uDFA0-\uDFDF\uDFE2\uDFE5-\uDFEF\uDFF2-\uDFFF]|\uD821[\uDFF8-\uDFFF]|\uD823[\uDCD6-\uDCFF\uDD09-\uDFFF]|\uD82C[\uDD1F-\uDD4F\uDD53-\uDD63\uDD68-\uDD6F\uDEFC-\uDFFF]|\uD82F[\uDC6B-\uDC6F\uDC7D-\uDC7F\uDC89-\uDC8F\uDC9A-\uDC9C\uDC9F-\uDFFF]|\uD834[\uDC00-\uDD64\uDD6A-\uDD6C\uDD73-\uDD7A\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDE41\uDE45-\uDFFF]|\uD835[\uDC55\uDC9D\uDCA0\uDCA1\uDCA3\uDCA4\uDCA7\uDCA8\uDCAD\uDCBA\uDCBC\uDCC4\uDD06\uDD0B\uDD0C\uDD15\uDD1D\uDD3A\uDD3F\uDD45\uDD47-\uDD49\uDD51\uDEA6\uDEA7\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3\uDFCC\uDFCD]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85-\uDE9A\uDEA0\uDEB0-\uDFFF]|\uD838[\uDC07\uDC19\uDC1A\uDC22\uDC25\uDC2B-\uDCFF\uDD2D-\uDD2F\uDD3E\uDD3F\uDD4A-\uDD4D\uDD4F-\uDEBF\uDEFA-\uDFFF]|\uD83A[\uDCC5-\uDCCF\uDCD7-\uDCFF\uDD4C-\uDD4F\uDD5A-\uDFFF]|\uD83B[\uDC00-\uDDFF\uDE04\uDE20\uDE23\uDE25\uDE26\uDE28\uDE33\uDE38\uDE3A\uDE3C-\uDE41\uDE43-\uDE46\uDE48\uDE4A\uDE4C\uDE50\uDE53\uDE55\uDE56\uDE58\uDE5A\uDE5C\uDE5E\uDE60\uDE63\uDE65\uDE66\uDE6B\uDE73\uDE78\uDE7D\uDE7F\uDE8A\uDE9C-\uDEA0\uDEA4\uDEAA\uDEBC-\uDFFF]|\uD83C[\uDC00-\uDD2F\uDD4A-\uDD4F\uDD6A-\uDD6F\uDD8A-\uDFFF]|\uD83E[\uDC00-\uDFEF\uDFFA-\uDFFF]|\uD869[\uDEDE-\uDEFF]|\uD86D[\uDF35-\uDF3F]|\uD86E[\uDC1E\uDC1F]|\uD873[\uDEA2-\uDEAF]|\uD87A[\uDFE1-\uDFFF]|\uD87E[\uDE1E-\uDFFF]|\uD884[\uDF4B-\uDFFF]|\uDB40[\uDC00-\uDCFF\uDDF0-\uDFFF]/g; + +/** + * A {@link ISlugifier slugifier} that approximates how GitHub's slugifier works. + */ +export const githubSlugifier: ISlugifier = new class implements ISlugifier { + fromHeading(heading: string): ISlug { + const slugifiedHeading = heading.trim() + .toLowerCase() + .replace(githubSlugReplaceRegex, '') + .replace(/\s/g, '-'); // Replace whitespace with - + + return new GithubSlug(slugifiedHeading); + } + + fromFragment(fragmentText: string): ISlug { + return new GithubSlug(fragmentText.toLowerCase()); + } + + createBuilder() { + const entries = new Map(); + return { + add: (heading: string): ISlug => { + const slug = this.fromHeading(heading); + const existingSlugEntry = entries.get(slug.value); + if (existingSlugEntry) { + ++existingSlugEntry.count; + return this.fromHeading(slug.value + '-' + existingSlugEntry.count); + } + + entries.set(slug.value, { count: 0 }); + return slug; + } + }; } }; diff --git a/extensions/markdown-language-features/src/telemetryReporter.ts b/extensions/markdown-language-features/src/telemetryReporter.ts index 79787624db99b..808acc4ceab99 100644 --- a/extensions/markdown-language-features/src/telemetryReporter.ts +++ b/extensions/markdown-language-features/src/telemetryReporter.ts @@ -49,7 +49,7 @@ export function loadDefaultTelemetryReporter(): TelemetryReporter { function getPackageInfo(): IPackageInfo | null { const extension = vscode.extensions.getExtension('Microsoft.vscode-markdown'); - if (extension && extension.packageJSON) { + if (extension?.packageJSON) { return { name: extension.packageJSON.name, version: extension.packageJSON.version, diff --git a/extensions/markdown-language-features/src/util/async.ts b/extensions/markdown-language-features/src/util/async.ts index b1e6b9f479609..49bcbe6402bd0 100644 --- a/extensions/markdown-language-features/src/util/async.ts +++ b/extensions/markdown-language-features/src/util/async.ts @@ -39,7 +39,7 @@ export class Delayer { }).then(() => { this._cancelTimeout = null; this._onSuccess = null; - const result = this._task && this._task?.(); + const result = this._task?.() ?? null; this._task = null; return result; }); diff --git a/extensions/markdown-language-features/src/util/dom.ts b/extensions/markdown-language-features/src/util/dom.ts index 8bbce79c30377..16c825c68ff57 100644 --- a/extensions/markdown-language-features/src/util/dom.ts +++ b/extensions/markdown-language-features/src/util/dom.ts @@ -11,11 +11,3 @@ export function escapeAttribute(value: string | vscode.Uri): string { .replace(/'/g, '''); } -export function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 64; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -} diff --git a/extensions/markdown-language-features/src/util/uuid.ts b/extensions/markdown-language-features/src/util/uuid.ts new file mode 100644 index 0000000000000..ca420b3b6afad --- /dev/null +++ b/extensions/markdown-language-features/src/util/uuid.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Copied from src/vs/base/common/uuid.ts + */ +export function generateUuid(): string { + // use `randomUUID` if possible + if (typeof crypto.randomUUID === 'function') { + // see https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto + // > Although crypto is available on all windows, the returned Crypto object only has one + // > usable feature in insecure contexts: the getRandomValues() method. + // > In general, you should use this API only in secure contexts. + + return crypto.randomUUID.bind(crypto)(); + } + + // prep-work + const _data = new Uint8Array(16); + const _hex: string[] = []; + for (let i = 0; i < 256; i++) { + _hex.push(i.toString(16).padStart(2, '0')); + } + + // get data + crypto.getRandomValues(_data); + + // set version bits + _data[6] = (_data[6] & 0x0f) | 0x40; + _data[8] = (_data[8] & 0x3f) | 0x80; + + // print as string + let i = 0; + let result = ''; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + return result; +} diff --git a/extensions/markdown-language-features/tsconfig.browser.json b/extensions/markdown-language-features/tsconfig.browser.json index e4c0db0fd10a4..dbacbb22fdff4 100644 --- a/extensions/markdown-language-features/tsconfig.browser.json +++ b/extensions/markdown-language-features/tsconfig.browser.json @@ -1,8 +1,6 @@ { "extends": "./tsconfig.json", - "compilerOptions": { - "types": [] - }, + "compilerOptions": {}, "exclude": [ "./src/test/**" ] diff --git a/extensions/markdown-language-features/tsconfig.json b/extensions/markdown-language-features/tsconfig.json index fcd79775de5f8..0e7a865e1f55b 100644 --- a/extensions/markdown-language-features/tsconfig.json +++ b/extensions/markdown-language-features/tsconfig.json @@ -1,7 +1,11 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "outDir": "./out" + "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ], + "skipLibCheck": true }, "include": [ "src/**/*", diff --git a/extensions/markdown-math/.vscodeignore b/extensions/markdown-math/.vscodeignore index 85f550b7d7b7f..5df4a1cb8abf1 100644 --- a/extensions/markdown-math/.vscodeignore +++ b/extensions/markdown-math/.vscodeignore @@ -2,7 +2,7 @@ src/** notebook/** extension-browser.webpack.config.js extension.webpack.config.js -esbuild.js +esbuild.* cgmanifest.json package-lock.json webpack.config.js diff --git a/extensions/markdown-math/esbuild.js b/extensions/markdown-math/esbuild.js deleted file mode 100644 index f8196075a69e2..0000000000000 --- a/extensions/markdown-math/esbuild.js +++ /dev/null @@ -1,38 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -//@ts-check - -const path = require('path'); -const fse = require('fs-extra'); - -const args = process.argv.slice(2); - -const srcDir = path.join(__dirname, 'notebook'); -const outDir = path.join(__dirname, 'notebook-out'); - -function postBuild(outDir) { - fse.copySync( - path.join(__dirname, 'node_modules', 'katex', 'dist', 'katex.min.css'), - path.join(outDir, 'katex.min.css')); - - const fontsDir = path.join(__dirname, 'node_modules', 'katex', 'dist', 'fonts'); - const fontsOutDir = path.join(outDir, 'fonts/'); - - fse.mkdirSync(fontsOutDir, { recursive: true }); - - for (const file of fse.readdirSync(fontsDir)) { - if (file.endsWith('.woff2')) { - fse.copyFileSync(path.join(fontsDir, file), path.join(fontsOutDir, file)); - } - } -} - -require('../esbuild-webview-common').run({ - entryPoints: [ - path.join(srcDir, 'katex.ts'), - ], - srcDir, - outdir: outDir, -}, process.argv, postBuild); diff --git a/extensions/markdown-math/esbuild.mjs b/extensions/markdown-math/esbuild.mjs new file mode 100644 index 0000000000000..910acbb06a844 --- /dev/null +++ b/extensions/markdown-math/esbuild.mjs @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +//@ts-check + +import path from 'path'; +import fse from 'fs-extra'; +import { run } from '../esbuild-webview-common.mjs'; + +const args = process.argv.slice(2); + +const srcDir = path.join(import.meta.dirname, 'notebook'); +const outDir = path.join(import.meta.dirname, 'notebook-out'); + +function postBuild(outDir) { + fse.copySync( + path.join(import.meta.dirname, 'node_modules', 'katex', 'dist', 'katex.min.css'), + path.join(outDir, 'katex.min.css')); + + const fontsDir = path.join(import.meta.dirname, 'node_modules', 'katex', 'dist', 'fonts'); + const fontsOutDir = path.join(outDir, 'fonts/'); + + fse.mkdirSync(fontsOutDir, { recursive: true }); + + for (const file of fse.readdirSync(fontsDir)) { + if (file.endsWith('.woff2')) { + fse.copyFileSync(path.join(fontsDir, file), path.join(fontsOutDir, file)); + } + } +} + +run({ + entryPoints: [ + path.join(srcDir, 'katex.ts'), + ], + srcDir, + outdir: outDir, +}, process.argv, postBuild); diff --git a/extensions/markdown-math/extension-browser.webpack.config.js b/extensions/markdown-math/extension-browser.webpack.config.js index 7fcc53a728b4f..b758f2d8155a3 100644 --- a/extensions/markdown-math/extension-browser.webpack.config.js +++ b/extensions/markdown-math/extension-browser.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, entry: { extension: './src/extension.ts' } diff --git a/extensions/markdown-math/extension.webpack.config.js b/extensions/markdown-math/extension.webpack.config.js index de88398eca0d3..4928186ae556c 100644 --- a/extensions/markdown-math/extension.webpack.config.js +++ b/extensions/markdown-math/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, resolve: { mainFields: ['module', 'main'] }, diff --git a/extensions/markdown-math/notebook/tsconfig.json b/extensions/markdown-math/notebook/tsconfig.json index ae9e837683c73..def3077d238ac 100644 --- a/extensions/markdown-math/notebook/tsconfig.json +++ b/extensions/markdown-math/notebook/tsconfig.json @@ -8,6 +8,12 @@ "ES2024", "DOM", "DOM.Iterable" + ], + "types": [ + "node" + ], + "typeRoots": [ + "../node_modules/@types" ] } } diff --git a/extensions/markdown-math/package.json b/extensions/markdown-math/package.json index 27b5047527fa7..5af72e0b51331 100644 --- a/extensions/markdown-math/package.json +++ b/extensions/markdown-math/package.json @@ -108,7 +108,7 @@ "scripts": { "compile": "npm run build-notebook", "watch": "npm run build-notebook", - "build-notebook": "node ./esbuild" + "build-notebook": "node ./esbuild.mjs" }, "devDependencies": { "@types/markdown-it": "^0.0.0", diff --git a/extensions/markdown-math/tsconfig.json b/extensions/markdown-math/tsconfig.json index c5194e2e33c81..40e645a1ed645 100644 --- a/extensions/markdown-math/tsconfig.json +++ b/extensions/markdown-math/tsconfig.json @@ -2,7 +2,10 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "types": [] + "types": [], + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/media-preview/extension-browser.webpack.config.js b/extensions/media-preview/extension-browser.webpack.config.js index 9a1bb4d3c8e8c..6c86474b4e52f 100644 --- a/extensions/media-preview/extension-browser.webpack.config.js +++ b/extensions/media-preview/extension-browser.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, entry: { extension: './src/extension.ts' }, diff --git a/extensions/media-preview/extension.webpack.config.js b/extensions/media-preview/extension.webpack.config.js index de88398eca0d3..4928186ae556c 100644 --- a/extensions/media-preview/extension.webpack.config.js +++ b/extensions/media-preview/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, resolve: { mainFields: ['module', 'main'] }, diff --git a/extensions/media-preview/media/imagePreview.js b/extensions/media-preview/media/imagePreview.js index ab8ad542a2d9e..d31728e76bcaa 100644 --- a/extensions/media-preview/media/imagePreview.js +++ b/extensions/media-preview/media/imagePreview.js @@ -306,6 +306,8 @@ return; } + console.error('Error loading image', e); + hasLoadedImage = true; document.body.classList.add('error'); document.body.classList.remove('loading'); diff --git a/extensions/media-preview/package-lock.json b/extensions/media-preview/package-lock.json index d26855f3ad250..fcd827cb0c3fa 100644 --- a/extensions/media-preview/package-lock.json +++ b/extensions/media-preview/package-lock.json @@ -12,6 +12,9 @@ "@vscode/extension-telemetry": "^0.9.8", "vscode-uri": "^3.0.6" }, + "devDependencies": { + "@types/node": "22.x" + }, "engines": { "vscode": "^1.70.0" } @@ -140,6 +143,16 @@ "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.18.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.13.tgz", + "integrity": "sha512-Bo45YKIjnmFtv6I1TuC8AaHBbqXtIo+Om5fE4QiU1Tj8QR/qt+8O3BAtOimG5IFmwaWiPmB3Mv3jtYzBA4Us2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@vscode/extension-telemetry": { "version": "0.9.8", "resolved": "https://registry.npmjs.org/@vscode/extension-telemetry/-/extension-telemetry-0.9.8.tgz", @@ -154,6 +167,13 @@ "vscode": "^1.75.0" } }, + "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" + }, "node_modules/vscode-uri": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.6.tgz", diff --git a/extensions/media-preview/package.json b/extensions/media-preview/package.json index 02b0134e4cf81..3f7e1c0165353 100644 --- a/extensions/media-preview/package.json +++ b/extensions/media-preview/package.json @@ -155,7 +155,7 @@ "compile": "gulp compile-extension:media-preview", "watch": "npm run build-preview && gulp watch-extension:media-preview", "vscode:prepublish": "npm run build-ext", - "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:media-preview ./tsconfig.json", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:media-preview ./tsconfig.json", "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, @@ -163,6 +163,9 @@ "@vscode/extension-telemetry": "^0.9.8", "vscode-uri": "^3.0.6" }, + "devDependencies": { + "@types/node": "22.x" + }, "repository": { "type": "git", "url": "https://github.com/microsoft/vscode.git" diff --git a/extensions/media-preview/src/audioPreview.ts b/extensions/media-preview/src/audioPreview.ts index 5058f7e978e80..282d579b380dd 100644 --- a/extensions/media-preview/src/audioPreview.ts +++ b/extensions/media-preview/src/audioPreview.ts @@ -6,7 +6,8 @@ import * as vscode from 'vscode'; import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; import { MediaPreview, reopenAsText } from './mediaPreview'; -import { escapeAttribute, getNonce } from './util/dom'; +import { escapeAttribute } from './util/dom'; +import { generateUuid } from './util/uuid'; class AudioPreviewProvider implements vscode.CustomReadonlyEditorProvider { @@ -57,7 +58,7 @@ class AudioPreview extends MediaPreview { src: await this.getResourcePath(this._webviewEditor, this._resource, version), }; - const nonce = getNonce(); + const nonce = generateUuid(); const cspSource = this._webviewEditor.webview.cspSource; return /* html */` diff --git a/extensions/media-preview/src/imagePreview/index.ts b/extensions/media-preview/src/imagePreview/index.ts index b405cd652c48b..6c2c8a73f66f0 100644 --- a/extensions/media-preview/src/imagePreview/index.ts +++ b/extensions/media-preview/src/imagePreview/index.ts @@ -6,7 +6,8 @@ import * as vscode from 'vscode'; import { BinarySizeStatusBarEntry } from '../binarySizeStatusBarEntry'; import { MediaPreview, PreviewState, reopenAsText } from '../mediaPreview'; -import { escapeAttribute, getNonce } from '../util/dom'; +import { escapeAttribute } from '../util/dom'; +import { generateUuid } from '../util/uuid'; import { SizeStatusBarEntry } from './sizeStatusBarEntry'; import { Scale, ZoomStatusBarEntry } from './zoomStatusBarEntry'; @@ -184,7 +185,7 @@ class ImagePreview extends MediaPreview { src: await this.getResourcePath(this._webviewEditor, this._resource, version), }; - const nonce = getNonce(); + const nonce = generateUuid(); const cspSource = this._webviewEditor.webview.cspSource; return /* html */` diff --git a/extensions/media-preview/src/util/dom.ts b/extensions/media-preview/src/util/dom.ts index 0f6c00da9daf7..f89d668c74dd9 100644 --- a/extensions/media-preview/src/util/dom.ts +++ b/extensions/media-preview/src/util/dom.ts @@ -7,12 +7,3 @@ import * as vscode from 'vscode'; export function escapeAttribute(value: string | vscode.Uri): string { return value.toString().replace(/"/g, '"'); } - -export function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 64; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -} diff --git a/extensions/media-preview/src/util/uuid.ts b/extensions/media-preview/src/util/uuid.ts new file mode 100644 index 0000000000000..ca420b3b6afad --- /dev/null +++ b/extensions/media-preview/src/util/uuid.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Copied from src/vs/base/common/uuid.ts + */ +export function generateUuid(): string { + // use `randomUUID` if possible + if (typeof crypto.randomUUID === 'function') { + // see https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto + // > Although crypto is available on all windows, the returned Crypto object only has one + // > usable feature in insecure contexts: the getRandomValues() method. + // > In general, you should use this API only in secure contexts. + + return crypto.randomUUID.bind(crypto)(); + } + + // prep-work + const _data = new Uint8Array(16); + const _hex: string[] = []; + for (let i = 0; i < 256; i++) { + _hex.push(i.toString(16).padStart(2, '0')); + } + + // get data + crypto.getRandomValues(_data); + + // set version bits + _data[6] = (_data[6] & 0x0f) | 0x40; + _data[8] = (_data[8] & 0x3f) | 0x80; + + // print as string + let i = 0; + let result = ''; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + return result; +} diff --git a/extensions/media-preview/src/videoPreview.ts b/extensions/media-preview/src/videoPreview.ts index 67012128cf7fc..1cb74c58426d2 100644 --- a/extensions/media-preview/src/videoPreview.ts +++ b/extensions/media-preview/src/videoPreview.ts @@ -6,7 +6,8 @@ import * as vscode from 'vscode'; import { BinarySizeStatusBarEntry } from './binarySizeStatusBarEntry'; import { MediaPreview, reopenAsText } from './mediaPreview'; -import { escapeAttribute, getNonce } from './util/dom'; +import { escapeAttribute } from './util/dom'; +import { generateUuid } from './util/uuid'; class VideoPreviewProvider implements vscode.CustomReadonlyEditorProvider { @@ -61,7 +62,7 @@ class VideoPreview extends MediaPreview { loop: configurations.get('loop'), }; - const nonce = getNonce(); + const nonce = generateUuid(); const cspSource = this._webviewEditor.webview.cspSource; return /* html */` diff --git a/extensions/media-preview/tsconfig.json b/extensions/media-preview/tsconfig.json index fcd79775de5f8..796a159a61c21 100644 --- a/extensions/media-preview/tsconfig.json +++ b/extensions/media-preview/tsconfig.json @@ -1,7 +1,10 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "outDir": "./out" + "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/merge-conflict/extension-browser.webpack.config.js b/extensions/merge-conflict/extension-browser.webpack.config.js index e4171bed927ea..7054f22b86803 100644 --- a/extensions/merge-conflict/extension-browser.webpack.config.js +++ b/extensions/merge-conflict/extension-browser.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, entry: { extension: './src/mergeConflictMain.ts' }, diff --git a/extensions/merge-conflict/extension.webpack.config.js b/extensions/merge-conflict/extension.webpack.config.js index 7a04ca98e973b..c927dcaf3719e 100644 --- a/extensions/merge-conflict/extension.webpack.config.js +++ b/extensions/merge-conflict/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { extension: './src/mergeConflictMain.ts' }, diff --git a/extensions/merge-conflict/tsconfig.json b/extensions/merge-conflict/tsconfig.json index 7234fdfeb9757..22c47de77dbe1 100644 --- a/extensions/merge-conflict/tsconfig.json +++ b/extensions/merge-conflict/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/mermaid-chat-features/.gitignore b/extensions/mermaid-chat-features/.gitignore new file mode 100644 index 0000000000000..2877bd189bba9 --- /dev/null +++ b/extensions/mermaid-chat-features/.gitignore @@ -0,0 +1 @@ +chat-webview-out diff --git a/extensions/mermaid-chat-features/.npmrc b/extensions/mermaid-chat-features/.npmrc new file mode 100644 index 0000000000000..a9c57709666b2 --- /dev/null +++ b/extensions/mermaid-chat-features/.npmrc @@ -0,0 +1,2 @@ +legacy-peer-deps="true" +timeout=180000 diff --git a/extensions/mermaid-chat-features/.vscodeignore b/extensions/mermaid-chat-features/.vscodeignore new file mode 100644 index 0000000000000..4722e5869901e --- /dev/null +++ b/extensions/mermaid-chat-features/.vscodeignore @@ -0,0 +1,8 @@ +src/** +extension.webpack.config.js +esbuild.* +cgmanifest.json +package-lock.json +webpack.config.js +tsconfig*.json +.gitignore diff --git a/extensions/mermaid-chat-features/README.md b/extensions/mermaid-chat-features/README.md new file mode 100644 index 0000000000000..4df5d17d156a7 --- /dev/null +++ b/extensions/mermaid-chat-features/README.md @@ -0,0 +1,5 @@ +# Mermaid Chat Features + +**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled. + +Adds basic [Mermaid.js](https://mermaid.js.org) diagram rendering to build-in chat. diff --git a/extensions/mermaid-chat-features/cgmanifest.json b/extensions/mermaid-chat-features/cgmanifest.json new file mode 100644 index 0000000000000..0c39c97297b72 --- /dev/null +++ b/extensions/mermaid-chat-features/cgmanifest.json @@ -0,0 +1,4 @@ +{ + "registrations": [], + "version": 1 +} diff --git a/extensions/mermaid-chat-features/chat-webview-src/index.ts b/extensions/mermaid-chat-features/chat-webview-src/index.ts new file mode 100644 index 0000000000000..9b3c9df71b67a --- /dev/null +++ b/extensions/mermaid-chat-features/chat-webview-src/index.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import mermaid, { MermaidConfig } from 'mermaid'; + +function getMermaidTheme() { + return document.body.classList.contains('vscode-dark') || document.body.classList.contains('vscode-high-contrast') + ? 'dark' + : 'default'; +} + +type State = { + readonly diagramText: string; + readonly theme: 'dark' | 'default'; +}; + +let state: State | undefined = undefined; + +function init() { + const diagram = document.querySelector('.mermaid'); + if (!diagram) { + return; + } + + const theme = getMermaidTheme(); + state = { + diagramText: diagram.textContent ?? '', + theme + }; + + const config: MermaidConfig = { + startOnLoad: true, + theme, + }; + mermaid.initialize(config); +} + +function tryUpdate() { + const newTheme = getMermaidTheme(); + if (state?.theme === newTheme) { + return; + } + + const diagramNode = document.querySelector('.mermaid'); + if (!diagramNode || !(diagramNode instanceof HTMLElement)) { + return; + } + + state = { + diagramText: state?.diagramText ?? '', + theme: newTheme + }; + + // Re-render + diagramNode.textContent = state?.diagramText ?? ''; + delete diagramNode.dataset.processed; + + mermaid.initialize({ + theme: newTheme, + }); + mermaid.run({ + nodes: [diagramNode] + }); +} + +// Update when theme changes +new MutationObserver(() => { + tryUpdate(); +}).observe(document.body, { attributes: true, attributeFilter: ['class'] }); + +init(); + diff --git a/extensions/mermaid-chat-features/chat-webview-src/tsconfig.json b/extensions/mermaid-chat-features/chat-webview-src/tsconfig.json new file mode 100644 index 0000000000000..a57ffcaeba049 --- /dev/null +++ b/extensions/mermaid-chat-features/chat-webview-src/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist/", + "jsx": "react", + "lib": [ + "ES2024", + "DOM", + "DOM.Iterable" + ], + "types": [ + "node" + ], + "typeRoots": [ + "../node_modules/@types" + ] + } +} diff --git a/extensions/mermaid-chat-features/esbuild-chat-webview.mjs b/extensions/mermaid-chat-features/esbuild-chat-webview.mjs new file mode 100644 index 0000000000000..b23de5746fa83 --- /dev/null +++ b/extensions/mermaid-chat-features/esbuild-chat-webview.mjs @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import path from 'path'; +import { run } from '../esbuild-webview-common.mjs'; + +const srcDir = path.join(import.meta.dirname, 'chat-webview-src'); +const outDir = path.join(import.meta.dirname, 'chat-webview-out'); + +run({ + entryPoints: [ + path.join(srcDir, 'index.ts'), + ], + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/mermaid-chat-features/extension-browser.webpack.config.js b/extensions/mermaid-chat-features/extension-browser.webpack.config.js new file mode 100644 index 0000000000000..b758f2d8155a3 --- /dev/null +++ b/extensions/mermaid-chat-features/extension-browser.webpack.config.js @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; + +export default withBrowserDefaults({ + context: import.meta.dirname, + entry: { + extension: './src/extension.ts' + } +}); diff --git a/extensions/mermaid-chat-features/extension.webpack.config.js b/extensions/mermaid-chat-features/extension.webpack.config.js new file mode 100644 index 0000000000000..4928186ae556c --- /dev/null +++ b/extensions/mermaid-chat-features/extension.webpack.config.js @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; + +export default withDefaults({ + context: import.meta.dirname, + resolve: { + mainFields: ['module', 'main'] + }, + entry: { + extension: './src/extension.ts', + } +}); diff --git a/extensions/mermaid-chat-features/package-lock.json b/extensions/mermaid-chat-features/package-lock.json new file mode 100644 index 0000000000000..185afcde64667 --- /dev/null +++ b/extensions/mermaid-chat-features/package-lock.json @@ -0,0 +1,1387 @@ +{ + "name": "mermaid-chat-features", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mermaid-chat-features", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "dompurify": "^3.2.7", + "mermaid": "^11.11.0" + }, + "devDependencies": { + "@types/node": "^22.18.10" + }, + "engines": { + "vscode": "^1.104.0" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.2.0.tgz", + "integrity": "sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", + "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", + "license": "MIT" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "license": "Apache-2.0" + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.1.tgz", + "integrity": "sha512-A78CUEnFGX8I/WlILxJCuIJXloL0j/OJ9PSchPAfCargEIKmUBWvvEMmKWB5oONwiUqlNt+5eRufdkLxeHIWYw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@antfu/utils": "^9.2.0", + "@iconify/types": "^2.0.0", + "debug": "^4.4.1", + "globals": "^15.15.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.1.1", + "mlly": "^1.7.4" + } + }, + "node_modules/@iconify/utils/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mermaid-js/parser": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.2.tgz", + "integrity": "sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ==", + "license": "MIT", + "dependencies": { + "langium": "3.3.1" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "license": "MIT" + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cytoscape": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz", + "integrity": "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "license": "MIT" + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "license": "MIT" + }, + "node_modules/langium": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", + "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "license": "MIT", + "dependencies": { + "chevrotain": "~11.0.3", + "chevrotain-allstar": "~0.3.0", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.0.8" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mermaid": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.11.0.tgz", + "integrity": "sha512-9lb/VNkZqWTRjVgCV+l1N+t4kyi94y+l5xrmBmbbxZYkfRl5hEDaTPMOcaWKCl1McG8nBEaMlWwkcAEEgjhBgg==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.0.4", + "@iconify/utils": "^3.0.1", + "@mermaid-js/parser": "^0.6.2", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.11", + "dayjs": "^1.11.13", + "dompurify": "^3.2.5", + "katex": "^0.16.22", + "khroma": "^2.1.0", + "lodash-es": "^4.17.21", + "marked": "^15.0.7", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/package-manager-detector": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", + "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", + "license": "MIT" + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "license": "MIT" + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, + "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" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" + } + } +} diff --git a/extensions/mermaid-chat-features/package.json b/extensions/mermaid-chat-features/package.json new file mode 100644 index 0000000000000..2311521c9b13e --- /dev/null +++ b/extensions/mermaid-chat-features/package.json @@ -0,0 +1,88 @@ +{ + "name": "mermaid-chat-features", + "displayName": "%displayName%", + "description": "%description%", + "version": "1.0.0", + "publisher": "vscode", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode.git" + }, + "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", + "engines": { + "vscode": "^1.104.0" + }, + "enabledApiProposals": [ + "chatOutputRenderer" + ], + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, + "main": "./out/extension", + "browser": "./dist/browser/extension", + "activationEvents": [], + "contributes": { + "configuration": { + "title": "Mermaid Chat Features", + "properties": { + "mermaid-chat.enabled": { + "type": "boolean", + "default": false, + "description": "%config.enabled.description%", + "scope": "application", + "tags": [ + "experimental" + ] + } + } + }, + "chatOutputRenderers": [ + { + "viewType": "vscode.chatMermaidDiagram", + "mimeTypes": [ + "text/vnd.mermaid" + ] + } + ], + "languageModelTools": [ + { + "name": "renderMermaidDiagram", + "displayName": "Mermaid Renderer", + "toolReferenceName": "renderMermaidDiagram", + "canBeReferencedInPrompt": true, + "modelDescription": "Renders a Mermaid diagram from Mermaid.js markup.", + "userDescription": "Render a Mermaid.js diagrams from markup.", + "when": "config.mermaid-chat.enabled", + "inputSchema": { + "type": "object", + "properties": { + "markup": { + "type": "string", + "description": "The mermaid diagram markup to render as a Mermaid diagram. This should only be the markup of the diagram. Do not include a wrapping code block." + } + } + } + } + ] + }, + "scripts": { + "compile": "gulp compile-extension:mermaid-chat-features && npm run build-chat-webview", + "watch": "npm run build-chat-webview && gulp watch-extension:mermaid-chat-features", + "vscode:prepublish": "npm run build-ext && npm run build-chat-webview", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:mermaid-chat-features", + "build-chat-webview": "node ./esbuild-chat-webview.mjs", + "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", + "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" + }, + "devDependencies": { + "@types/node": "^22.18.10" + }, + "dependencies": { + "dompurify": "^3.2.7", + "mermaid": "^11.11.0" + } +} diff --git a/extensions/mermaid-chat-features/package.nls.json b/extensions/mermaid-chat-features/package.nls.json new file mode 100644 index 0000000000000..d2fe3d44c3091 --- /dev/null +++ b/extensions/mermaid-chat-features/package.nls.json @@ -0,0 +1,5 @@ +{ + "displayName": "Mermaid Chat Features", + "description": "Adds Mermaid diagram support to built-in chats.", + "config.enabled.description": "Enable a tool for experimental Mermaid diagram rendering in chat responses." +} diff --git a/extensions/mermaid-chat-features/src/extension.ts b/extensions/mermaid-chat-features/src/extension.ts new file mode 100644 index 0000000000000..51294649f4f1d --- /dev/null +++ b/extensions/mermaid-chat-features/src/extension.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import { generateUuid } from './uuid'; + +/** + * View type that uniquely identifies the Mermaid chat output renderer. + */ +const viewType = 'vscode.chatMermaidDiagram'; + +/** + * Mime type used to identify Mermaid diagram data in chat output. + */ +const mime = 'text/vnd.mermaid'; + +export function activate(context: vscode.ExtensionContext) { + + // Register tools + context.subscriptions.push( + vscode.lm.registerTool<{ markup: string }>('renderMermaidDiagram', { + invoke: async (options, _token) => { + const sourceCode = options.input.markup; + return writeMermaidToolOutput(sourceCode); + }, + }) + ); + + // Register the chat output renderer for Mermaid diagrams. + // This will be invoked with the data generated by the tools. + // It can also be invoked when rendering old Mermaid diagrams in the chat history. + context.subscriptions.push( + vscode.chat.registerChatOutputRenderer(viewType, { + async renderChatOutput({ value }, webview, _ctx, _token) { + const mermaidSource = new TextDecoder().decode(value); + + // Set the options for the webview + const mediaRoot = vscode.Uri.joinPath(context.extensionUri, 'chat-webview-out'); + webview.options = { + enableScripts: true, + localResourceRoots: [mediaRoot], + }; + + // Set the HTML content for the webview + const nonce = generateUuid(); + const mermaidScript = vscode.Uri.joinPath(mediaRoot, 'index.js'); + + webview.html = ` + + + + + + + Mermaid Diagram + + + + +
+							${escapeHtmlText(mermaidSource)}
+						
+ + + + `; + }, + })); +} + + +function writeMermaidToolOutput(sourceCode: string): vscode.LanguageModelToolResult { + // Expose the source code as a tool result for the LM + const result = new vscode.LanguageModelToolResult([ + new vscode.LanguageModelTextPart(sourceCode) + ]); + + // And store custom data in the tool result details to indicate that a custom renderer should be used for it. + // In this case we just store the source code as binary data. + + // Add cast to use proposed API + (result as vscode.ExtendedLanguageModelToolResult2).toolResultDetails2 = { + mime, + value: new TextEncoder().encode(sourceCode), + }; + + return result; +} + +function escapeHtmlText(str: string): string { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + + diff --git a/extensions/mermaid-chat-features/src/uuid.ts b/extensions/mermaid-chat-features/src/uuid.ts new file mode 100644 index 0000000000000..ca420b3b6afad --- /dev/null +++ b/extensions/mermaid-chat-features/src/uuid.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Copied from src/vs/base/common/uuid.ts + */ +export function generateUuid(): string { + // use `randomUUID` if possible + if (typeof crypto.randomUUID === 'function') { + // see https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto + // > Although crypto is available on all windows, the returned Crypto object only has one + // > usable feature in insecure contexts: the getRandomValues() method. + // > In general, you should use this API only in secure contexts. + + return crypto.randomUUID.bind(crypto)(); + } + + // prep-work + const _data = new Uint8Array(16); + const _hex: string[] = []; + for (let i = 0; i < 256; i++) { + _hex.push(i.toString(16).padStart(2, '0')); + } + + // get data + crypto.getRandomValues(_data); + + // set version bits + _data[6] = (_data[6] & 0x0f) | 0x40; + _data[8] = (_data[8] & 0x3f) | 0x80; + + // print as string + let i = 0; + let result = ''; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + return result; +} diff --git a/extensions/mermaid-chat-features/tsconfig.json b/extensions/mermaid-chat-features/tsconfig.json new file mode 100644 index 0000000000000..35a9a9ad8a03f --- /dev/null +++ b/extensions/mermaid-chat-features/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out", + "types": [ + "node" + ], + "typeRoots": [ + "./node_modules/@types" + ] + }, + "include": [ + "src/**/*", + "../../src/vscode-dts/vscode.d.ts", + "../../src/vscode-dts/vscode.proposed.chatOutputRenderer.d.ts", + "../../src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts", + "../../src/vscode-dts/vscode.proposed.languageModelThinkingPart.d.ts", + "../../src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts", + "../../src/vscode-dts/vscode.proposed.languageModelProxy.d.ts" + ] +} diff --git a/extensions/microsoft-authentication/.vscodeignore b/extensions/microsoft-authentication/.vscodeignore index e7feddb5da862..e2daf4b8a89ba 100644 --- a/extensions/microsoft-authentication/.vscodeignore +++ b/extensions/microsoft-authentication/.vscodeignore @@ -3,7 +3,6 @@ out/test/** out/** extension.webpack.config.js -extension-browser.webpack.config.js package-lock.json src/** .gitignore diff --git a/extensions/microsoft-authentication/extension-browser.webpack.config.js b/extensions/microsoft-authentication/extension-browser.webpack.config.js deleted file mode 100644 index 1590cef5e5f12..0000000000000 --- a/extensions/microsoft-authentication/extension-browser.webpack.config.js +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const path = require('path'); -const withBrowserDefaults = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, - node: { - global: true, - __filename: false, - __dirname: false, - }, - entry: { - extension: './src/extension.ts', - }, - resolve: { - alias: { - './node/authServer': path.resolve(__dirname, 'src/browser/authServer'), - './node/buffer': path.resolve(__dirname, 'src/browser/buffer'), - './node/fetch': path.resolve(__dirname, 'src/browser/fetch'), - './node/authProvider': path.resolve(__dirname, 'src/browser/authProvider'), - } - } -}); diff --git a/extensions/microsoft-authentication/extension.webpack.config.js b/extensions/microsoft-authentication/extension.webpack.config.js index 395c011b1dba1..a46d5a527dfec 100644 --- a/extensions/microsoft-authentication/extension.webpack.config.js +++ b/extensions/microsoft-authentication/extension.webpack.config.js @@ -2,19 +2,55 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults, { nodePlugins } from '../shared.webpack.config.mjs'; +import CopyWebpackPlugin from 'copy-webpack-plugin'; +import path from 'path'; -//@ts-check +const isWindows = process.platform === 'win32'; +const isMacOS = process.platform === 'darwin'; +const isLinux = !isWindows && !isMacOS; -'use strict'; +const windowsArches = ['x64']; +const linuxArches = ['x64']; -const withDefaults = require('../shared.webpack.config'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const path = require('path'); +let platformFolder; +switch (process.platform) { + case 'win32': + platformFolder = 'windows'; + break; + case 'darwin': + platformFolder = 'macos'; + break; + case 'linux': + platformFolder = 'linux'; + break; + default: + throw new Error(`Unsupported platform: ${process.platform}`); +} -const isWindows = process.platform === 'win32'; +const arch = process.env.VSCODE_ARCH || process.arch; +console.log(`Building Microsoft Authentication Extension for ${process.platform} (${arch})`); + +const plugins = [...nodePlugins(import.meta.dirname)]; +if ( + (isWindows && windowsArches.includes(arch)) || + isMacOS || + (isLinux && linuxArches.includes(arch)) +) { + plugins.push(new CopyWebpackPlugin({ + patterns: [ + { + // The native files we need to ship with the extension + from: `**/dist/${platformFolder}/${arch}/(lib|)msal*.(node|dll|dylib|so)`, + to: '[name][ext]' + } + ] + })); +} -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { extension: './src/extension.ts' }, @@ -26,21 +62,8 @@ module.exports = withDefaults({ }, resolve: { alias: { - 'keytar': path.resolve(__dirname, 'packageMocks', 'keytar', 'index.js') + 'keytar': path.resolve(import.meta.dirname, 'packageMocks', 'keytar', 'index.js') } }, - plugins: [ - ...withDefaults.nodePlugins(__dirname), - new CopyWebpackPlugin({ - patterns: [ - { - // The native files we need to ship with the extension - from: '**/dist/msal*.(node|dll)', - to: '[name][ext]', - // These will only be present on Windows for now - noErrorOnMissing: !isWindows - } - ] - }) - ] + plugins }); diff --git a/extensions/microsoft-authentication/package-lock.json b/extensions/microsoft-authentication/package-lock.json index 43600731960ec..850b8b9277a2f 100644 --- a/extensions/microsoft-authentication/package-lock.json +++ b/extensions/microsoft-authentication/package-lock.json @@ -10,8 +10,8 @@ "license": "MIT", "dependencies": { "@azure/ms-rest-azure-env": "^2.0.0", - "@azure/msal-node": "^2.16.2", - "@azure/msal-node-extensions": "^1.5.0", + "@azure/msal-node": "^3.8.3", + "@azure/msal-node-extensions": "^1.5.25", "@vscode/extension-telemetry": "^0.9.8", "keytar": "file:./packageMocks/keytar", "vscode-tas-client": "^0.1.84" @@ -33,21 +33,21 @@ "integrity": "sha512-dG76W7ElfLi+fbTjnZVGj+M9e0BIEJmRxU6fHaUQ12bZBe8EJKYb2GV50YWNaP2uJiVQ5+7nXEVj1VN1UQtaEw==" }, "node_modules/@azure/msal-common": { - "version": "14.16.0", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.16.0.tgz", - "integrity": "sha512-1KOZj9IpcDSwpNiQNjt0jDYZpQvNZay7QAEi/5DLubay40iGYtLzya/jbjRPLyOTZhEKyL1MzPuw2HqBCjceYA==", + "version": "15.13.2", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.13.2.tgz", + "integrity": "sha512-cNwUoCk3FF8VQ7Ln/MdcJVIv3sF73/OT86cRH81ECsydh7F4CNfIo2OAx6Cegtg8Yv75x4506wN4q+Emo6erOA==", "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/@azure/msal-node": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.16.2.tgz", - "integrity": "sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.8.3.tgz", + "integrity": "sha512-Ul7A4gwmaHzYWj2Z5xBDly/W8JSC1vnKgJ898zPMZr0oSf1ah0tiL15sytjycU/PMhDZAlkWtEL1+MzNMU6uww==", "license": "MIT", "dependencies": { - "@azure/msal-common": "14.16.0", + "@azure/msal-common": "15.13.2", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" }, @@ -56,14 +56,14 @@ } }, "node_modules/@azure/msal-node-extensions": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@azure/msal-node-extensions/-/msal-node-extensions-1.5.0.tgz", - "integrity": "sha512-UfEyh2xmJHKH64zPS/SbN1bd9adV4ZWGp1j2OSwIuhVraqpUXyXZ1LpDpiUqg/peTgLLtx20qrHOzYT0kKzmxQ==", + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/@azure/msal-node-extensions/-/msal-node-extensions-1.5.25.tgz", + "integrity": "sha512-8UtOy6McoHQUbvi75Cx+ftpbTuOB471j4V4yZJmRM3KJ30bMO7forXrVV+/xArvWdgZ9VkBvq26OclFstJUo8Q==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@azure/msal-common": "14.16.0", - "@azure/msal-node-runtime": "^0.17.1", + "@azure/msal-common": "15.13.2", + "@azure/msal-node-runtime": "^0.20.0", "keytar": "^7.8.0" }, "engines": { @@ -71,9 +71,9 @@ } }, "node_modules/@azure/msal-node-runtime": { - "version": "0.18.2", - "resolved": "https://registry.npmjs.org/@azure/msal-node-runtime/-/msal-node-runtime-0.18.2.tgz", - "integrity": "sha512-v45fyBQp80BrjZAeGJXl+qggHcbylQiFBihr0ijO2eniDCW9tz5TZBKYsqzH06VuiRaVG/Sa0Hcn4pjhJqFSTw==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@azure/msal-node-runtime/-/msal-node-runtime-0.20.1.tgz", + "integrity": "sha512-WVbMedbJHjt9M+qeZMH/6U1UmjXsKaMB6fN8OZUtGY7UVNYofrowZNx4nVvWN/ajPKBQCEW4Rr/MwcRuA8HGcQ==", "hasInstallScript": true, "license": "MIT" }, @@ -268,7 +268,22 @@ "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } }, "node_modules/combined-stream": { "version": "1.0.8", @@ -291,28 +306,200 @@ "node": ">=0.4.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -335,21 +522,23 @@ } }, "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", + "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, @@ -392,22 +581,34 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, + "license": "MIT", "dependencies": { - "mime-db": "1.44.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -438,7 +639,8 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/semver": { "version": "7.6.2", diff --git a/extensions/microsoft-authentication/package.json b/extensions/microsoft-authentication/package.json index 3bbe3bcc2333d..e30ddcd319c79 100644 --- a/extensions/microsoft-authentication/package.json +++ b/extensions/microsoft-authentication/package.json @@ -14,9 +14,9 @@ ], "activationEvents": [], "enabledApiProposals": [ - "idToken", "nativeWindowHandle", - "authIssuers" + "authIssuers", + "authenticationChallenges" ], "capabilities": { "virtualWorkspaces": true, @@ -34,6 +34,7 @@ "label": "Microsoft", "id": "microsoft", "authorizationServerGlobs": [ + "https://login.microsoftonline.com/*", "https://login.microsoftonline.com/*/v2.0" ] }, @@ -109,11 +110,11 @@ "default": "msal", "enum": [ "msal", - "classic" + "msal-no-broker" ], "enumDescriptions": [ "%microsoft-authentication.implementation.enumDescriptions.msal%", - "%microsoft-authentication.implementation.enumDescriptions.classic%" + "%microsoft-authentication.implementation.enumDescriptions.msal-no-broker%" ], "markdownDescription": "%microsoft-authentication.implementation.description%", "tags": [ @@ -126,13 +127,10 @@ }, "aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255", "main": "./out/extension.js", - "browser": "./dist/browser/extension.js", "scripts": { "vscode:prepublish": "npm run compile", "compile": "gulp compile-extension:microsoft-authentication", - "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", - "watch": "gulp watch-extension:microsoft-authentication", - "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" + "watch": "gulp watch-extension:microsoft-authentication" }, "devDependencies": { "@types/node": "22.x", @@ -143,15 +141,12 @@ }, "dependencies": { "@azure/ms-rest-azure-env": "^2.0.0", - "@azure/msal-node": "^2.16.2", - "@azure/msal-node-extensions": "^1.5.0", + "@azure/msal-node": "^3.8.3", + "@azure/msal-node-extensions": "^1.5.25", "@vscode/extension-telemetry": "^0.9.8", "keytar": "file:./packageMocks/keytar", "vscode-tas-client": "^0.1.84" }, - "overrides": { - "@azure/msal-node-runtime": "^0.18.2" - }, "repository": { "type": "git", "url": "https://github.com/microsoft/vscode.git" diff --git a/extensions/microsoft-authentication/package.nls.json b/extensions/microsoft-authentication/package.nls.json index c8e0189c08f9e..4fcd2d27b740b 100644 --- a/extensions/microsoft-authentication/package.nls.json +++ b/extensions/microsoft-authentication/package.nls.json @@ -3,15 +3,9 @@ "description": "Microsoft authentication provider", "signIn": "Sign In", "signOut": "Sign Out", - "microsoft-authentication.implementation.description": { - "message": "The authentication implementation to use for signing in with a Microsoft account.\n\n*NOTE: The `classic` implementation is deprecated and will be removed, along with this setting, in a future release. If only the `classic` implementation works for you, please [open an issue](command:workbench.action.openIssueReporter) and explain what you are trying to log in to.*", - "comment": [ - "{Locked='[(command:workbench.action.openIssueReporter)]'}", - "The `command:` syntax will turn into a link. Do not translate it." - ] - }, + "microsoft-authentication.implementation.description": "The authentication implementation to use for signing in with a Microsoft account.", "microsoft-authentication.implementation.enumDescriptions.msal": "Use the Microsoft Authentication Library (MSAL) to sign in with a Microsoft account.", - "microsoft-authentication.implementation.enumDescriptions.classic": "(deprecated) Use the classic authentication flow to sign in with a Microsoft account.", + "microsoft-authentication.implementation.enumDescriptions.msal-no-broker": "Use the Microsoft Authentication Library (MSAL) to sign in with a Microsoft account using a browser. This is useful if you are having issues with the native broker.", "microsoft-sovereign-cloud.environment.description": { "message": "The Sovereign Cloud to use for authentication. If you select `custom`, you must also set the `#microsoft-sovereign-cloud.customEnvironment#` setting.", "comment": [ diff --git a/extensions/microsoft-authentication/src/AADHelper.ts b/extensions/microsoft-authentication/src/AADHelper.ts deleted file mode 100644 index 8117b0b0f5ba8..0000000000000 --- a/extensions/microsoft-authentication/src/AADHelper.ts +++ /dev/null @@ -1,991 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import * as path from 'path'; -import { isSupportedEnvironment } from './common/uri'; -import { IntervalTimer, raceCancellationAndTimeoutError, SequencerByKey } from './common/async'; -import { generateCodeChallenge, generateCodeVerifier, randomUUID } from './cryptoUtils'; -import { BetterTokenStorage, IDidChangeInOtherWindowEvent } from './betterSecretStorage'; -import { LoopbackAuthServer } from './node/authServer'; -import { base64Decode } from './node/buffer'; -import fetch from './node/fetch'; -import { UriEventHandler } from './UriEventHandler'; -import TelemetryReporter from '@vscode/extension-telemetry'; -import { Environment } from '@azure/ms-rest-azure-env'; - -const redirectUrl = 'https://vscode.dev/redirect'; -const defaultActiveDirectoryEndpointUrl = Environment.AzureCloud.activeDirectoryEndpointUrl; -const DEFAULT_CLIENT_ID = 'aebc6443-996d-45c2-90f0-388ff96faa56'; -const DEFAULT_TENANT = 'organizations'; -const MSA_TID = '9188040d-6c67-4c5b-b112-36a304b66dad'; -const MSA_PASSTHRU_TID = 'f8cdef31-a31e-4b4a-93e4-5f571e91255a'; - -const enum MicrosoftAccountType { - AAD = 'aad', - MSA = 'msa', - Unknown = 'unknown' -} - -interface IToken { - accessToken?: string; // When unable to refresh due to network problems, the access token becomes undefined - idToken?: string; // depending on the scopes can be either supplied or empty - - expiresIn?: number; // How long access token is valid, in seconds - expiresAt?: number; // UNIX epoch time at which token will expire - refreshToken: string; - - account: { - label: string; - id: string; - type: MicrosoftAccountType; - }; - scope: string; - sessionId: string; // The account id + the scope -} - -export interface IStoredSession { - id: string; - refreshToken: string; - scope: string; // Scopes are alphabetized and joined with a space - account: { - label: string; - id: string; - }; - endpoint: string | undefined; -} - -export interface ITokenResponse { - access_token: string; - expires_in: number; - ext_expires_in: number; - refresh_token: string; - scope: string; - token_type: string; - id_token?: string; -} - -export interface IMicrosoftTokens { - accessToken: string; - idToken?: string; -} - -interface IScopeData { - originalScopes?: string[]; - scopes: string[]; - scopeStr: string; - scopesToSend: string; - clientId: string; - tenant: string; -} - -export const REFRESH_NETWORK_FAILURE = 'Network failure'; - -export class AzureActiveDirectoryService { - // For details on why this is set to 2/3... see https://github.com/microsoft/vscode/issues/133201#issuecomment-966668197 - private static REFRESH_TIMEOUT_MODIFIER = 1000 * 2 / 3; - private static POLLING_CONSTANT = 1000 * 60 * 30; - - private _tokens: IToken[] = []; - private _refreshTimeouts: Map = new Map(); - private _sessionChangeEmitter: vscode.EventEmitter = new vscode.EventEmitter(); - - // Used to keep track of current requests when not using the local server approach. - private _pendingNonces = new Map(); - private _codeExchangePromises = new Map>(); - private _codeVerfifiers = new Map(); - - // Used to keep track of tokens that we need to store but can't because we aren't the focused window. - private _pendingTokensToStore: Map = new Map(); - - // Used to sequence requests to the same scope. - private _sequencer = new SequencerByKey(); - - constructor( - private readonly _logger: vscode.LogOutputChannel, - _context: vscode.ExtensionContext, - private readonly _uriHandler: UriEventHandler, - private readonly _tokenStorage: BetterTokenStorage, - private readonly _telemetryReporter: TelemetryReporter, - private readonly _env: Environment - ) { - _context.subscriptions.push(this._tokenStorage.onDidChangeInOtherWindow((e) => this.checkForUpdates(e))); - _context.subscriptions.push(vscode.window.onDidChangeWindowState(async (e) => e.focused && await this.storePendingTokens())); - - // In the event that a window isn't focused for a long time, we should still try to store the tokens at some point. - const timer = new IntervalTimer(); - timer.cancelAndSet( - () => !vscode.window.state.focused && this.storePendingTokens(), - // 5 hours + random extra 0-30 seconds so that each window doesn't try to store at the same time - (18000000) + Math.floor(Math.random() * 30000)); - _context.subscriptions.push(timer); - } - - public async initialize(): Promise { - this._logger.trace('Reading sessions from secret storage...'); - const sessions = await this._tokenStorage.getAll(item => this.sessionMatchesEndpoint(item)); - this._logger.trace(`Got ${sessions.length} stored sessions`); - - const refreshes = sessions.map(async session => { - this._logger.trace(`[${session.scope}] '${session.id}' Read stored session`); - const scopes = session.scope.split(' '); - const scopeData: IScopeData = { - scopes, - scopeStr: session.scope, - // filter our special scopes - scopesToSend: scopes.filter(s => !s.startsWith('VSCODE_')).join(' '), - clientId: this.getClientId(scopes), - tenant: this.getTenantId(scopes), - }; - try { - await this.refreshToken(session.refreshToken, scopeData, session.id); - } catch (e) { - // If we aren't connected to the internet, then wait and try to refresh again later. - if (e.message === REFRESH_NETWORK_FAILURE) { - this._tokens.push({ - accessToken: undefined, - refreshToken: session.refreshToken, - account: { - ...session.account, - type: MicrosoftAccountType.Unknown - }, - scope: session.scope, - sessionId: session.id - }); - } else { - vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.')); - this._logger.error(e); - await this.removeSessionByIToken({ - accessToken: undefined, - refreshToken: session.refreshToken, - account: { - ...session.account, - type: MicrosoftAccountType.Unknown - }, - scope: session.scope, - sessionId: session.id - }); - } - } - }); - - const result = await Promise.allSettled(refreshes); - for (const res of result) { - if (res.status === 'rejected') { - this._logger.error(`Failed to initialize stored data: ${res.reason}`); - this.clearSessions(); - break; - } - } - - for (const token of this._tokens) { - /* __GDPR__ - "account" : { - "owner": "TylerLeonhardt", - "comment": "Used to determine the usage of the Microsoft Auth Provider.", - "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }, - "accountType": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what account types are being used." } - } - */ - this._telemetryReporter.sendTelemetryEvent('account', { - // Get rid of guids from telemetry. - scopes: JSON.stringify(token.scope.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}').split(' ')), - accountType: token.account.type - }); - } - } - - //#region session operations - - public get onDidChangeSessions(): vscode.Event { - return this._sessionChangeEmitter.event; - } - - public getSessions(scopes: string[] | undefined, { account, authorizationServer }: vscode.AuthenticationProviderSessionOptions = {}): Promise { - if (!scopes) { - this._logger.info('Getting sessions for all scopes...'); - const sessions = this._tokens - .filter(token => !account?.label || token.account.label === account.label) - .map(token => this.convertToSessionSync(token)); - this._logger.info(`Got ${sessions.length} sessions for all scopes${account ? ` for account '${account.label}'` : ''}...`); - return Promise.resolve(sessions); - } - - let modifiedScopes = [...scopes]; - if (!modifiedScopes.includes('openid')) { - modifiedScopes.push('openid'); - } - if (!modifiedScopes.includes('email')) { - modifiedScopes.push('email'); - } - if (!modifiedScopes.includes('profile')) { - modifiedScopes.push('profile'); - } - if (!modifiedScopes.includes('offline_access')) { - modifiedScopes.push('offline_access'); - } - if (authorizationServer) { - const tenant = authorizationServer.path.split('/')[1]; - if (tenant) { - modifiedScopes.push(`VSCODE_TENANT:${tenant}`); - } - } - modifiedScopes = modifiedScopes.sort(); - - const modifiedScopesStr = modifiedScopes.join(' '); - const clientId = this.getClientId(scopes); - const scopeData: IScopeData = { - clientId, - originalScopes: scopes, - scopes: modifiedScopes, - scopeStr: modifiedScopesStr, - // filter our special scopes - scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '), - tenant: this.getTenantId(modifiedScopes), - }; - - this._logger.trace(`[${scopeData.scopeStr}] Queued getting sessions` + account ? ` for ${account?.label}` : ''); - return this._sequencer.queue(modifiedScopesStr, () => this.doGetSessions(scopeData, account)); - } - - private async doGetSessions(scopeData: IScopeData, account?: vscode.AuthenticationSessionAccountInformation): Promise { - this._logger.info(`[${scopeData.scopeStr}] Getting sessions` + account ? ` for ${account?.label}` : ''); - - const matchingTokens = this._tokens - .filter(token => token.scope === scopeData.scopeStr) - .filter(token => !account?.label || token.account.label === account.label); - // If we still don't have a matching token try to get a new token from an existing token by using - // the refreshToken. This is documented here: - // https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#refresh-the-access-token - // "Refresh tokens are valid for all permissions that your client has already received consent for." - if (!matchingTokens.length) { - // Get a token with the correct client id and account. - let token: IToken | undefined; - for (const t of this._tokens) { - // No refresh token, so we can't make a new token from this session - if (!t.refreshToken) { - continue; - } - // Need to make sure the account matches if we were provided one - if (account?.label && t.account.label !== account.label) { - continue; - } - // If the client id is the default client id, then check for the absence of the VSCODE_CLIENT_ID scope - if (scopeData.clientId === DEFAULT_CLIENT_ID && !t.scope.includes('VSCODE_CLIENT_ID')) { - token = t; - break; - } - // If the client id is not the default client id, then check for the matching VSCODE_CLIENT_ID scope - if (scopeData.clientId !== DEFAULT_CLIENT_ID && t.scope.includes(`VSCODE_CLIENT_ID:${scopeData.clientId}`)) { - token = t; - break; - } - } - - if (token) { - this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Found a matching token with a different scopes '${token.scope}'. Attempting to get a new session using the existing session.`); - try { - const itoken = await this.doRefreshToken(token.refreshToken, scopeData); - this._sessionChangeEmitter.fire({ added: [this.convertToSessionSync(itoken)], removed: [], changed: [] }); - matchingTokens.push(itoken); - } catch (err) { - this._logger.error(`[${scopeData.scopeStr}] Attempted to get a new session using the existing session with scopes '${token.scope}' but it failed due to: ${err.message ?? err}`); - } - } - } - - this._logger.info(`[${scopeData.scopeStr}] Got ${matchingTokens.length} sessions`); - const results = await Promise.allSettled(matchingTokens.map(token => this.convertToSession(token, scopeData))); - return results - .filter(result => result.status === 'fulfilled') - .map(result => (result as PromiseFulfilledResult).value); - } - - public createSession(scopes: string[], { account, authorizationServer }: vscode.AuthenticationProviderSessionOptions = {}): Promise { - let modifiedScopes = [...scopes]; - if (!modifiedScopes.includes('openid')) { - modifiedScopes.push('openid'); - } - if (!modifiedScopes.includes('email')) { - modifiedScopes.push('email'); - } - if (!modifiedScopes.includes('profile')) { - modifiedScopes.push('profile'); - } - if (!modifiedScopes.includes('offline_access')) { - modifiedScopes.push('offline_access'); - } - if (authorizationServer) { - const tenant = authorizationServer.path.split('/')[1]; - if (tenant) { - modifiedScopes.push(`VSCODE_TENANT:${tenant}`); - } - } - modifiedScopes = modifiedScopes.sort(); - const scopeData: IScopeData = { - originalScopes: scopes, - scopes: modifiedScopes, - scopeStr: modifiedScopes.join(' '), - // filter our special scopes - scopesToSend: modifiedScopes.filter(s => !s.startsWith('VSCODE_')).join(' '), - clientId: this.getClientId(scopes), - tenant: this.getTenantId(modifiedScopes), - }; - - this._logger.trace(`[${scopeData.scopeStr}] Queued creating session`); - return this._sequencer.queue(scopeData.scopeStr, () => this.doCreateSession(scopeData, account)); - } - - private async doCreateSession(scopeData: IScopeData, account?: vscode.AuthenticationSessionAccountInformation): Promise { - this._logger.info(`[${scopeData.scopeStr}] Creating session` + account ? ` for ${account?.label}` : ''); - - const runsRemote = vscode.env.remoteName !== undefined; - const runsServerless = vscode.env.remoteName === undefined && vscode.env.uiKind === vscode.UIKind.Web; - - if (runsServerless && this._env.activeDirectoryEndpointUrl !== defaultActiveDirectoryEndpointUrl) { - throw new Error('Sign in to non-public clouds is not supported on the web.'); - } - - return await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: vscode.l10n.t('Signing in to your account...'), cancellable: true }, async (_progress, token) => { - if (runsRemote || runsServerless) { - return await this.createSessionWithoutLocalServer(scopeData, account?.label, token); - } - - try { - return await this.createSessionWithLocalServer(scopeData, account?.label, token); - } catch (e) { - this._logger.error(`[${scopeData.scopeStr}] Error creating session: ${e}`); - - // If the error was about starting the server, try directly hitting the login endpoint instead - if (e.message === 'Error listening to server' || e.message === 'Closed' || e.message === 'Timeout waiting for port') { - return this.createSessionWithoutLocalServer(scopeData, account?.label, token); - } - - throw e; - } - }); - } - - private async createSessionWithLocalServer(scopeData: IScopeData, loginHint: string | undefined, token: vscode.CancellationToken): Promise { - this._logger.trace(`[${scopeData.scopeStr}] Starting login flow with local server`); - const codeVerifier = generateCodeVerifier(); - const codeChallenge = await generateCodeChallenge(codeVerifier); - const qs = new URLSearchParams({ - response_type: 'code', - response_mode: 'query', - client_id: scopeData.clientId, - redirect_uri: redirectUrl, - scope: scopeData.scopesToSend, - code_challenge_method: 'S256', - code_challenge: codeChallenge, - }); - if (loginHint) { - qs.set('login_hint', loginHint); - } else { - qs.set('prompt', 'select_account'); - } - const loginUrl = new URL(`${scopeData.tenant}/oauth2/v2.0/authorize?${qs.toString()}`, this._env.activeDirectoryEndpointUrl).toString(); - const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl); - await server.start(); - - let codeToExchange; - try { - vscode.env.openExternal(vscode.Uri.parse(`http://127.0.0.1:${server.port}/signin?nonce=${encodeURIComponent(server.nonce)}`)); - const { code } = await raceCancellationAndTimeoutError(server.waitForOAuthResponse(), token, 1000 * 60 * 5); // 5 minutes - codeToExchange = code; - } finally { - setTimeout(() => { - void server.stop(); - }, 5000); - } - - const session = await this.exchangeCodeForSession(codeToExchange, codeVerifier, scopeData); - this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' Sending change event for added session`); - this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] }); - this._logger.info(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`); - return session; - } - - private async createSessionWithoutLocalServer(scopeData: IScopeData, loginHint: string | undefined, token: vscode.CancellationToken): Promise { - this._logger.trace(`[${scopeData.scopeStr}] Starting login flow without local server`); - let callbackUri = await vscode.env.asExternalUri(vscode.Uri.parse(`${vscode.env.uriScheme}://vscode.microsoft-authentication`)); - const nonce = generateCodeVerifier(); - const callbackQuery = new URLSearchParams(callbackUri.query); - callbackQuery.set('nonce', encodeURIComponent(nonce)); - callbackUri = callbackUri.with({ - query: callbackQuery.toString() - }); - const state = encodeURIComponent(callbackUri.toString(true)); - const codeVerifier = generateCodeVerifier(); - const codeChallenge = await generateCodeChallenge(codeVerifier); - const signInUrl = new URL(`${scopeData.tenant}/oauth2/v2.0/authorize`, this._env.activeDirectoryEndpointUrl); - const qs = new URLSearchParams({ - response_type: 'code', - client_id: encodeURIComponent(scopeData.clientId), - response_mode: 'query', - redirect_uri: redirectUrl, - state, - scope: scopeData.scopesToSend, - code_challenge_method: 'S256', - code_challenge: codeChallenge, - }); - if (loginHint) { - qs.append('login_hint', loginHint); - } else { - qs.append('prompt', 'select_account'); - } - signInUrl.search = qs.toString(); - const uri = vscode.Uri.parse(signInUrl.toString()); - vscode.env.openExternal(uri); - - - const existingNonces = this._pendingNonces.get(scopeData.scopeStr) || []; - this._pendingNonces.set(scopeData.scopeStr, [...existingNonces, nonce]); - - // Register a single listener for the URI callback, in case the user starts the login process multiple times - // before completing it. - let existingPromise = this._codeExchangePromises.get(scopeData.scopeStr); - let inputBox: vscode.InputBox | undefined; - if (!existingPromise) { - if (isSupportedEnvironment(callbackUri)) { - existingPromise = this.handleCodeResponse(scopeData); - } else { - inputBox = vscode.window.createInputBox(); - existingPromise = this.handleCodeInputBox(inputBox, codeVerifier, scopeData); - } - this._codeExchangePromises.set(scopeData.scopeStr, existingPromise); - } - - this._codeVerfifiers.set(nonce, codeVerifier); - - return await raceCancellationAndTimeoutError(existingPromise, token, 1000 * 60 * 5) // 5 minutes - .finally(() => { - this._pendingNonces.delete(scopeData.scopeStr); - this._codeExchangePromises.delete(scopeData.scopeStr); - this._codeVerfifiers.delete(nonce); - inputBox?.dispose(); - }); - } - - public async removeSessionById(sessionId: string, writeToDisk: boolean = true): Promise { - const tokenIndex = this._tokens.findIndex(token => token.sessionId === sessionId); - if (tokenIndex === -1) { - this._logger.warn(`'${sessionId}' Session not found to remove`); - return Promise.resolve(undefined); - } - - const token = this._tokens.splice(tokenIndex, 1)[0]; - this._logger.trace(`[${token.scope}] '${sessionId}' Queued removing session`); - return this._sequencer.queue(token.scope, () => this.removeSessionByIToken(token, writeToDisk)); - } - - public async clearSessions() { - this._logger.trace('Logging out of all sessions'); - this._tokens = []; - await this._tokenStorage.deleteAll(item => this.sessionMatchesEndpoint(item)); - - this._refreshTimeouts.forEach(timeout => { - clearTimeout(timeout); - }); - - this._refreshTimeouts.clear(); - this._logger.trace('All sessions logged out'); - } - - private async removeSessionByIToken(token: IToken, writeToDisk: boolean = true): Promise { - this._logger.info(`[${token.scope}] '${token.sessionId}' Logging out of session`); - this.removeSessionTimeout(token.sessionId); - - if (writeToDisk) { - await this._tokenStorage.delete(token.sessionId); - } - - const tokenIndex = this._tokens.findIndex(t => t.sessionId === token.sessionId); - if (tokenIndex !== -1) { - this._tokens.splice(tokenIndex, 1); - } - - const session = this.convertToSessionSync(token); - this._logger.trace(`[${token.scope}] '${token.sessionId}' Sending change event for session that was removed`); - this._sessionChangeEmitter.fire({ added: [], removed: [session], changed: [] }); - this._logger.info(`[${token.scope}] '${token.sessionId}' Logged out of session successfully!`); - return session; - } - - //#endregion - - //#region timeout - - private setSessionTimeout(sessionId: string, refreshToken: string, scopeData: IScopeData, timeout: number) { - this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Setting refresh timeout for ${timeout} milliseconds`); - this.removeSessionTimeout(sessionId); - this._refreshTimeouts.set(sessionId, setTimeout(async () => { - try { - const refreshedToken = await this.refreshToken(refreshToken, scopeData, sessionId); - this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Sending change event for session that was refreshed`); - this._sessionChangeEmitter.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] }); - this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' refresh timeout complete`); - } catch (e) { - if (e.message !== REFRESH_NETWORK_FAILURE) { - vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.')); - await this.removeSessionById(sessionId); - } - } - }, timeout)); - } - - private removeSessionTimeout(sessionId: string): void { - const timeout = this._refreshTimeouts.get(sessionId); - if (timeout) { - clearTimeout(timeout); - this._refreshTimeouts.delete(sessionId); - } - } - - //#endregion - - //#region convert operations - - private convertToTokenSync(json: ITokenResponse, scopeData: IScopeData, existingId?: string): IToken { - let claims = undefined; - this._logger.trace(`[${scopeData.scopeStr}] '${existingId ?? 'new'}' Attempting to parse token response.`); - - try { - if (json.id_token) { - claims = JSON.parse(base64Decode(json.id_token.split('.')[1])); - } else { - this._logger.warn(`[${scopeData.scopeStr}] '${existingId ?? 'new'}' Attempting to parse access_token instead since no id_token was included in the response.`); - claims = JSON.parse(base64Decode(json.access_token.split('.')[1])); - } - } catch (e) { - throw e; - } - - const id = `${claims.tid}/${(claims.oid ?? (claims.altsecid ?? '' + claims.ipd))}`; - const sessionId = existingId || `${id}/${randomUUID()}`; - this._logger.trace(`[${scopeData.scopeStr}] '${sessionId}' Token response parsed successfully.`); - return { - expiresIn: json.expires_in, - expiresAt: json.expires_in ? Date.now() + json.expires_in * 1000 : undefined, - accessToken: json.access_token, - idToken: json.id_token, - refreshToken: json.refresh_token, - scope: scopeData.scopeStr, - sessionId, - account: { - label: claims.preferred_username ?? claims.email ?? claims.unique_name ?? 'user@example.com', - id, - type: claims.tid === MSA_TID || claims.tid === MSA_PASSTHRU_TID ? MicrosoftAccountType.MSA : MicrosoftAccountType.AAD - } - }; - } - - /** - * Return a session object without checking for expiry and potentially refreshing. - * @param token The token information. - */ - private convertToSessionSync(token: IToken): vscode.AuthenticationSession { - return { - id: token.sessionId, - accessToken: token.accessToken!, - idToken: token.idToken, - account: token.account, - scopes: token.scope.split(' ') - }; - } - - private async convertToSession(token: IToken, scopeData: IScopeData): Promise { - if (token.accessToken && (!token.expiresAt || token.expiresAt > Date.now())) { - this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Token available from cache${token.expiresAt ? `, expires in ${token.expiresAt - Date.now()} milliseconds` : ''}.`); - return { - id: token.sessionId, - accessToken: token.accessToken, - idToken: token.idToken, - account: token.account, - scopes: scopeData.originalScopes ?? scopeData.scopes - }; - } - - try { - this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Token expired or unavailable, trying refresh`); - const refreshedToken = await this.refreshToken(token.refreshToken, scopeData, token.sessionId); - if (refreshedToken.accessToken) { - return { - id: token.sessionId, - accessToken: refreshedToken.accessToken, - idToken: refreshedToken.idToken, - account: token.account, - // We always prefer the original scopes requested since that array is used as a key in the AuthService - scopes: scopeData.originalScopes ?? scopeData.scopes - }; - } else { - throw new Error(); - } - } catch (e) { - throw new Error('Unavailable due to network problems'); - } - } - - //#endregion - - //#region refresh logic - - private refreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise { - this._logger.trace(`[${scopeData.scopeStr}] '${sessionId ?? 'new'}' Queued refreshing token`); - return this._sequencer.queue(scopeData.scopeStr, () => this.doRefreshToken(refreshToken, scopeData, sessionId)); - } - - private async doRefreshToken(refreshToken: string, scopeData: IScopeData, sessionId?: string): Promise { - this._logger.trace(`[${scopeData.scopeStr}] '${sessionId ?? 'new'}' Refreshing token`); - const postData = new URLSearchParams({ - refresh_token: refreshToken, - client_id: scopeData.clientId, - grant_type: 'refresh_token', - scope: scopeData.scopesToSend - }).toString(); - - try { - const json = await this.fetchTokenResponse(postData, scopeData); - const token = this.convertToTokenSync(json, scopeData, sessionId); - if (token.expiresIn) { - this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); - } - this.setToken(token, scopeData); - this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Token refresh success`); - return token; - } catch (e) { - if (e.message === REFRESH_NETWORK_FAILURE) { - // We were unable to refresh because of a network failure (i.e. the user lost internet access). - // so set up a timeout to try again later. We only do this if we have a session id to reference later. - if (sessionId) { - this.setSessionTimeout(sessionId, refreshToken, scopeData, AzureActiveDirectoryService.POLLING_CONSTANT); - } - throw e; - } - this._logger.error(`[${scopeData.scopeStr}] '${sessionId ?? 'new'}' Refreshing token failed: ${e.message}`); - throw e; - } - } - - //#endregion - - //#region scope parsers - - private getClientId(scopes: string[]) { - return scopes.reduce((prev, current) => { - if (current.startsWith('VSCODE_CLIENT_ID:')) { - return current.split('VSCODE_CLIENT_ID:')[1]; - } - return prev; - }, undefined) ?? DEFAULT_CLIENT_ID; - } - - private getTenantId(scopes: string[]) { - return scopes.reduce((prev, current) => { - if (current.startsWith('VSCODE_TENANT:')) { - return current.split('VSCODE_TENANT:')[1]; - } - return prev; - }, undefined) ?? DEFAULT_TENANT; - } - - //#endregion - - //#region oauth flow - - private async handleCodeResponse(scopeData: IScopeData): Promise { - let uriEventListener: vscode.Disposable; - return new Promise((resolve: (value: vscode.AuthenticationSession) => void, reject) => { - uriEventListener = this._uriHandler.event(async (uri: vscode.Uri) => { - try { - const query = new URLSearchParams(uri.query); - let code = query.get('code'); - let nonce = query.get('nonce'); - if (Array.isArray(code)) { - code = code[0]; - } - if (!code) { - throw new Error('No code included in query'); - } - if (Array.isArray(nonce)) { - nonce = nonce[0]; - } - if (!nonce) { - throw new Error('No nonce included in query'); - } - - const acceptedStates = this._pendingNonces.get(scopeData.scopeStr) || []; - // Workaround double encoding issues of state in web - if (!acceptedStates.includes(nonce) && !acceptedStates.includes(decodeURIComponent(nonce))) { - throw new Error('Nonce does not match.'); - } - - const verifier = this._codeVerfifiers.get(nonce) ?? this._codeVerfifiers.get(decodeURIComponent(nonce)); - if (!verifier) { - throw new Error('No available code verifier'); - } - - const session = await this.exchangeCodeForSession(code, verifier, scopeData); - this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] }); - this._logger.info(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`); - resolve(session); - } catch (err) { - reject(err); - } - }); - }).then(result => { - uriEventListener.dispose(); - return result; - }).catch(err => { - uriEventListener.dispose(); - throw err; - }); - } - - private async handleCodeInputBox(inputBox: vscode.InputBox, verifier: string, scopeData: IScopeData): Promise { - this._logger.trace(`[${scopeData.scopeStr}] Starting login flow with input box`); - inputBox.ignoreFocusOut = true; - inputBox.title = vscode.l10n.t('Microsoft Authentication'); - inputBox.prompt = vscode.l10n.t('Provide the authorization code to complete the sign in flow.'); - inputBox.placeholder = vscode.l10n.t('Paste authorization code here...'); - return new Promise((resolve: (value: vscode.AuthenticationSession) => void, reject) => { - inputBox.show(); - inputBox.onDidAccept(async () => { - const code = inputBox.value; - if (code) { - inputBox.dispose(); - const session = await this.exchangeCodeForSession(code, verifier, scopeData); - this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' sending session changed event because session was added.`); - this._sessionChangeEmitter.fire({ added: [session], removed: [], changed: [] }); - this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' session successfully created!`); - resolve(session); - } - }); - inputBox.onDidHide(() => { - if (!inputBox.value) { - inputBox.dispose(); - reject('Cancelled'); - } - }); - }); - } - - private async exchangeCodeForSession(code: string, codeVerifier: string, scopeData: IScopeData): Promise { - this._logger.trace(`[${scopeData.scopeStr}] Exchanging login code for session`); - let token: IToken | undefined; - try { - const postData = new URLSearchParams({ - grant_type: 'authorization_code', - code: code, - client_id: scopeData.clientId, - scope: scopeData.scopesToSend, - code_verifier: codeVerifier, - redirect_uri: redirectUrl - }).toString(); - - const json = await this.fetchTokenResponse(postData, scopeData); - this._logger.trace(`[${scopeData.scopeStr}] Exchanging code for token succeeded!`); - token = this.convertToTokenSync(json, scopeData); - } catch (e) { - this._logger.error(`[${scopeData.scopeStr}] Error exchanging code for token: ${e}`); - throw e; - } - - if (token.expiresIn) { - this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER); - } - this.setToken(token, scopeData); - this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Exchanging login code for session succeeded!`); - return await this.convertToSession(token, scopeData); - } - - private async fetchTokenResponse(postData: string, scopeData: IScopeData): Promise { - let endpointUrl: string; - if (this._env.activeDirectoryEndpointUrl !== defaultActiveDirectoryEndpointUrl) { - // If this is for sovereign clouds, don't try using the proxy endpoint, which supports only public cloud - endpointUrl = this._env.activeDirectoryEndpointUrl; - } else { - const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints'); - endpointUrl = proxyEndpoints?.microsoft || this._env.activeDirectoryEndpointUrl; - } - const endpoint = new URL(`${scopeData.tenant}/oauth2/v2.0/token`, endpointUrl); - - let attempts = 0; - while (attempts <= 3) { - attempts++; - let result; - let errorMessage: string | undefined; - try { - result = await fetch(endpoint.toString(), { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: postData - }); - } catch (e) { - errorMessage = e.message ?? e; - } - - if (!result || result.status > 499) { - if (attempts > 3) { - this._logger.error(`[${scopeData.scopeStr}] Fetching token failed: ${result ? await result.text() : errorMessage}`); - break; - } - // Exponential backoff - await new Promise(resolve => setTimeout(resolve, 5 * attempts * attempts * 1000)); - continue; - } else if (!result.ok) { - // For 4XX errors, the user may actually have an expired token or have changed - // their password recently which is throwing a 4XX. For this, we throw an error - // so that the user can be prompted to sign in again. - throw new Error(await result.text()); - } - - return await result.json() as ITokenResponse; - } - - throw new Error(REFRESH_NETWORK_FAILURE); - } - - //#endregion - - //#region storage operations - - private setToken(token: IToken, scopeData: IScopeData): void { - this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Setting token`); - - const existingTokenIndex = this._tokens.findIndex(t => t.sessionId === token.sessionId); - if (existingTokenIndex > -1) { - this._tokens.splice(existingTokenIndex, 1, token); - } else { - this._tokens.push(token); - } - - // Don't await because setting the token is only useful for any new windows that open. - void this.storeToken(token, scopeData); - } - - private async storeToken(token: IToken, scopeData: IScopeData): Promise { - if (!vscode.window.state.focused) { - if (this._pendingTokensToStore.has(token.sessionId)) { - this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Window is not focused, replacing token to be stored`); - } else { - this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Window is not focused, pending storage of token`); - } - this._pendingTokensToStore.set(token.sessionId, token); - return; - } - - await this._tokenStorage.store(token.sessionId, { - id: token.sessionId, - refreshToken: token.refreshToken, - scope: token.scope, - account: token.account, - endpoint: this._env.activeDirectoryEndpointUrl, - }); - this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Stored token`); - } - - private async storePendingTokens(): Promise { - if (this._pendingTokensToStore.size === 0) { - this._logger.trace('No pending tokens to store'); - return; - } - - const tokens = [...this._pendingTokensToStore.values()]; - this._pendingTokensToStore.clear(); - - this._logger.trace(`Storing ${tokens.length} pending tokens...`); - await Promise.allSettled(tokens.map(async token => { - this._logger.trace(`[${token.scope}] '${token.sessionId}' Storing pending token`); - await this._tokenStorage.store(token.sessionId, { - id: token.sessionId, - refreshToken: token.refreshToken, - scope: token.scope, - account: token.account, - endpoint: this._env.activeDirectoryEndpointUrl, - }); - this._logger.trace(`[${token.scope}] '${token.sessionId}' Stored pending token`); - })); - this._logger.trace('Done storing pending tokens'); - } - - private async checkForUpdates(e: IDidChangeInOtherWindowEvent): Promise { - for (const key of e.added) { - const session = await this._tokenStorage.get(key); - if (!session) { - this._logger.error('session not found that was apparently just added'); - continue; - } - - if (!this.sessionMatchesEndpoint(session)) { - // If the session wasn't made for this login endpoint, ignore this update - continue; - } - - const matchesExisting = this._tokens.some(token => token.scope === session.scope && token.sessionId === session.id); - if (!matchesExisting && session.refreshToken) { - try { - const scopes = session.scope.split(' '); - const scopeData: IScopeData = { - scopes, - scopeStr: session.scope, - // filter our special scopes - scopesToSend: scopes.filter(s => !s.startsWith('VSCODE_')).join(' '), - clientId: this.getClientId(scopes), - tenant: this.getTenantId(scopes), - }; - this._logger.trace(`[${scopeData.scopeStr}] '${session.id}' Session added in another window`); - const token = await this.refreshToken(session.refreshToken, scopeData, session.id); - this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Sending change event for session that was added`); - this._sessionChangeEmitter.fire({ added: [this.convertToSessionSync(token)], removed: [], changed: [] }); - this._logger.trace(`[${scopeData.scopeStr}] '${token.sessionId}' Session added in another window added here`); - continue; - } catch (e) { - // Network failures will automatically retry on next poll. - if (e.message !== REFRESH_NETWORK_FAILURE) { - vscode.window.showErrorMessage(vscode.l10n.t('You have been signed out because reading stored authentication information failed.')); - await this.removeSessionById(session.id); - } - continue; - } - } - } - - for (const { value } of e.removed) { - this._logger.trace(`[${value.scope}] '${value.id}' Session removed in another window`); - if (!this.sessionMatchesEndpoint(value)) { - // If the session wasn't made for this login endpoint, ignore this update - this._logger.trace(`[${value.scope}] '${value.id}' Session doesn't match endpoint. Skipping...`); - continue; - } - - await this.removeSessionById(value.id, false); - this._logger.trace(`[${value.scope}] '${value.id}' Session removed in another window removed here`); - } - - // NOTE: We don't need to handle changed sessions because all that really would give us is a new refresh token - // because access tokens are not stored in Secret Storage due to their short lifespan. This new refresh token - // is not useful in this window because we really only care about the lifetime of the _access_ token which we - // are already managing (see usages of `setSessionTimeout`). - // However, in order to minimize the amount of times we store tokens, if a token was stored via another window, - // we cancel any pending token storage operations. - for (const sessionId of e.updated) { - if (this._pendingTokensToStore.delete(sessionId)) { - this._logger.trace(`'${sessionId}' Cancelled pending token storage because token was updated in another window`); - } - } - } - - private sessionMatchesEndpoint(session: IStoredSession): boolean { - // For older sessions with no endpoint set, it can be assumed to be the default endpoint - session.endpoint ||= defaultActiveDirectoryEndpointUrl; - - return session.endpoint === this._env.activeDirectoryEndpointUrl; - } - - //#endregion -} diff --git a/extensions/microsoft-authentication/src/browser/authProvider.ts b/extensions/microsoft-authentication/src/browser/authProvider.ts deleted file mode 100644 index 3b4da5b18fa6a..0000000000000 --- a/extensions/microsoft-authentication/src/browser/authProvider.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { AuthenticationProvider, AuthenticationProviderAuthenticationSessionsChangeEvent, AuthenticationSession, EventEmitter } from 'vscode'; - -export class MsalAuthProvider implements AuthenticationProvider { - private _onDidChangeSessions = new EventEmitter(); - onDidChangeSessions = this._onDidChangeSessions.event; - - initialize(): Thenable { - throw new Error('Method not implemented.'); - } - - getSessions(): Thenable { - throw new Error('Method not implemented.'); - } - createSession(): Thenable { - throw new Error('Method not implemented.'); - } - removeSession(): Thenable { - throw new Error('Method not implemented.'); - } - - dispose() { - this._onDidChangeSessions.dispose(); - } -} diff --git a/extensions/microsoft-authentication/src/browser/authServer.ts b/extensions/microsoft-authentication/src/browser/authServer.ts deleted file mode 100644 index 60b53c713a85e..0000000000000 --- a/extensions/microsoft-authentication/src/browser/authServer.ts +++ /dev/null @@ -1,12 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export function startServer(_: any): any { - throw new Error('Not implemented'); -} - -export function createServer(_: any): any { - throw new Error('Not implemented'); -} diff --git a/extensions/microsoft-authentication/src/browser/buffer.ts b/extensions/microsoft-authentication/src/browser/buffer.ts deleted file mode 100644 index 794bb19f57937..0000000000000 --- a/extensions/microsoft-authentication/src/browser/buffer.ts +++ /dev/null @@ -1,17 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export function base64Encode(text: string): string { - return btoa(text); -} - -export function base64Decode(text: string): string { - // modification of https://stackoverflow.com/a/38552302 - const replacedCharacters = text.replace(/-/g, '+').replace(/_/g, '/'); - const decodedText = decodeURIComponent(atob(replacedCharacters).split('').map(function (c) { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join('')); - return decodedText; -} diff --git a/extensions/microsoft-authentication/src/browser/fetch.ts b/extensions/microsoft-authentication/src/browser/fetch.ts deleted file mode 100644 index c61281ca8f882..0000000000000 --- a/extensions/microsoft-authentication/src/browser/fetch.ts +++ /dev/null @@ -1,6 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export default fetch; diff --git a/extensions/microsoft-authentication/src/common/config.ts b/extensions/microsoft-authentication/src/common/config.ts new file mode 100644 index 0000000000000..ac5daf1783fc1 --- /dev/null +++ b/extensions/microsoft-authentication/src/common/config.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +export interface IConfig { + // The macOS broker redirect URI which is dependent on the bundle identifier of the signed app. + // Other platforms do not require a redirect URI to be set. For unsigned apps, the unsigned + // format can be used. + // Example formats: + // msauth.com.msauth.unsignedapp://auth or msauth.://auth + macOSBrokerRedirectUri: string; +} + +export const Config: IConfig = { + // This is replaced in the build with the correct bundle id for that distro. + macOSBrokerRedirectUri: 'msauth.com.msauth.unsignedapp://auth' +}; diff --git a/extensions/microsoft-authentication/src/common/env.ts b/extensions/microsoft-authentication/src/common/env.ts index 5d19183e70cb3..9460a27d2fea9 100644 --- a/extensions/microsoft-authentication/src/common/env.ts +++ b/extensions/microsoft-authentication/src/common/env.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Uri } from 'vscode'; +export const DEFAULT_REDIRECT_URI = 'https://vscode.dev/redirect'; + const VALID_DESKTOP_CALLBACK_SCHEMES = [ 'vscode', 'vscode-insiders', diff --git a/extensions/microsoft-authentication/src/common/loggerOptions.ts b/extensions/microsoft-authentication/src/common/loggerOptions.ts index af5c1644a276e..495a03df289bf 100644 --- a/extensions/microsoft-authentication/src/common/loggerOptions.ts +++ b/extensions/microsoft-authentication/src/common/loggerOptions.ts @@ -19,13 +19,7 @@ export class MsalLoggerOptions { return this._toMsalLogLevel(env.logLevel); } - loggerCallback(level: MsalLogLevel, message: string, containsPii: boolean): void { - if (containsPii) { - // TODO: Should we still log the message if it contains PII? It's just going to - // an output channel that doesn't leave the machine. - this._output.debug('Skipped logging message because it may contain PII'); - return; - } + loggerCallback(level: MsalLogLevel, message: string, _containsPii: boolean): void { // Log to output channel one level lower than the MSAL log level switch (level) { diff --git a/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts b/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts index e68663efe43a2..f7e41805d7094 100644 --- a/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts +++ b/extensions/microsoft-authentication/src/common/loopbackClientAndOpener.ts @@ -5,32 +5,32 @@ import type { ILoopbackClient, ServerAuthorizationCodeResponse } from '@azure/msal-node'; import type { UriEventHandler } from '../UriEventHandler'; -import { Disposable, env, l10n, LogOutputChannel, Uri, window } from 'vscode'; -import { DeferredPromise, toPromise } from './async'; -import { isSupportedClient } from './env'; +import { env, LogOutputChannel, Uri } from 'vscode'; +import { toPromise } from './async'; export interface ILoopbackClientAndOpener extends ILoopbackClient { openBrowser(url: string): Promise; } export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener { - private _responseDeferred: DeferredPromise | undefined; - constructor( private readonly _uriHandler: UriEventHandler, private readonly _redirectUri: string, + private readonly _callbackUri: Uri, private readonly _logger: LogOutputChannel ) { } async listenForAuthCode(): Promise { - await this._responseDeferred?.cancel(); - this._responseDeferred = new DeferredPromise(); - const result = await this._responseDeferred.p; - this._responseDeferred = undefined; - if (result) { - return result; - } - throw new Error('No valid response received for authorization code.'); + const url = await toPromise(this._uriHandler.event); + this._logger.debug(`Received URL event. Authority: ${url.authority}`); + const result = new URL(url.toString(true)); + return { + code: result.searchParams.get('code') ?? undefined, + state: result.searchParams.get('state') ?? undefined, + error: result.searchParams.get('error') ?? undefined, + error_description: result.searchParams.get('error_description') ?? undefined, + error_uri: result.searchParams.get('error_uri') ?? undefined, + }; } getRedirectUri(): string { @@ -44,95 +44,7 @@ export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener { } async openBrowser(url: string): Promise { - const callbackUri = await env.asExternalUri(Uri.parse(`${env.uriScheme}://vscode.microsoft-authentication`)); - - if (isSupportedClient(callbackUri)) { - void this._getCodeResponseFromUriHandler(); - } else { - // Unsupported clients will be shown the code in the browser, but it will not redirect back since this - // isn't a supported client. Instead, they will copy that code in the browser and paste it in an input box - // that will be shown to them by the extension. - void this._getCodeResponseFromQuickPick(); - } - - const uri = Uri.parse(url + `&state=${encodeURI(callbackUri.toString(true))}`); + const uri = Uri.parse(url + `&state=${encodeURI(this._callbackUri.toString(true))}`); await env.openExternal(uri); } - - private async _getCodeResponseFromUriHandler(): Promise { - if (!this._responseDeferred) { - throw new Error('No listener for auth code'); - } - const url = await toPromise(this._uriHandler.event); - this._logger.debug(`Received URL event. Authority: ${url.authority}`); - const result = new URL(url.toString(true)); - - this._responseDeferred?.complete({ - code: result.searchParams.get('code') ?? undefined, - state: result.searchParams.get('state') ?? undefined, - error: result.searchParams.get('error') ?? undefined, - error_description: result.searchParams.get('error_description') ?? undefined, - error_uri: result.searchParams.get('error_uri') ?? undefined, - }); - } - - private async _getCodeResponseFromQuickPick(): Promise { - if (!this._responseDeferred) { - throw new Error('No listener for auth code'); - } - const inputBox = window.createInputBox(); - inputBox.ignoreFocusOut = true; - inputBox.title = l10n.t('Microsoft Authentication'); - inputBox.prompt = l10n.t('Provide the authorization code to complete the sign in flow.'); - inputBox.placeholder = l10n.t('Paste authorization code here...'); - inputBox.show(); - const code = await new Promise((resolve) => { - let resolvedValue: string | undefined = undefined; - const disposable = Disposable.from( - inputBox, - inputBox.onDidAccept(async () => { - if (!inputBox.value) { - inputBox.validationMessage = l10n.t('Authorization code is required.'); - return; - } - const code = inputBox.value; - resolvedValue = code; - resolve(code); - inputBox.hide(); - }), - inputBox.onDidChangeValue(() => { - inputBox.validationMessage = undefined; - }), - inputBox.onDidHide(() => { - disposable.dispose(); - if (!resolvedValue) { - resolve(undefined); - } - }) - ); - Promise.allSettled([this._responseDeferred?.p]).then(() => disposable.dispose()); - }); - // Something canceled the original deferred promise, so just return. - if (this._responseDeferred.isSettled) { - return; - } - if (code) { - this._logger.debug('Received auth code from quick pick'); - this._responseDeferred.complete({ - code, - state: undefined, - error: undefined, - error_description: undefined, - error_uri: undefined - }); - return; - } - this._responseDeferred.complete({ - code: undefined, - state: undefined, - error: 'User cancelled', - error_description: 'User cancelled', - error_uri: undefined - }); - } } diff --git a/extensions/microsoft-authentication/src/common/publicClientCache.ts b/extensions/microsoft-authentication/src/common/publicClientCache.ts index acc8ba0d30774..f39ae8297f9c9 100644 --- a/extensions/microsoft-authentication/src/common/publicClientCache.ts +++ b/extensions/microsoft-authentication/src/common/publicClientCache.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { AccountInfo, AuthenticationResult, InteractiveRequest, RefreshTokenRequest, SilentFlowRequest } from '@azure/msal-node'; +import type { AccountInfo, AuthenticationResult, InteractiveRequest, RefreshTokenRequest, SilentFlowRequest, DeviceCodeRequest } from '@azure/msal-node'; import type { Disposable, Event } from 'vscode'; export interface ICachedPublicClientApplication { @@ -10,14 +10,16 @@ export interface ICachedPublicClientApplication { onDidRemoveLastAccount: Event; acquireTokenSilent(request: SilentFlowRequest): Promise; acquireTokenInteractive(request: InteractiveRequest): Promise; + acquireTokenByDeviceCode(request: Omit): Promise; acquireTokenByRefreshToken(request: RefreshTokenRequest): Promise; removeAccount(account: AccountInfo): Promise; accounts: AccountInfo[]; clientId: string; + isBrokerAvailable: Readonly; } export interface ICachedPublicClientApplicationManager { onDidAccountsChange: Event<{ added: AccountInfo[]; changed: AccountInfo[]; deleted: AccountInfo[] }>; - getOrCreate(clientId: string, refreshTokensToMigrate?: string[]): Promise; + getOrCreate(clientId: string, migrate?: { refreshTokensToMigrate?: string[]; tenant: string }): Promise; getAll(): ICachedPublicClientApplication[]; } diff --git a/extensions/microsoft-authentication/src/common/scopeData.ts b/extensions/microsoft-authentication/src/common/scopeData.ts index c9c36f4a87e0a..17549f5db1b7d 100644 --- a/extensions/microsoft-authentication/src/common/scopeData.ts +++ b/extensions/microsoft-authentication/src/common/scopeData.ts @@ -45,11 +45,17 @@ export class ScopeData { */ readonly tenantId: string | undefined; - constructor(readonly originalScopes: readonly string[] = [], authorizationServer?: Uri) { + /** + * The claims to include in the token request. + */ + readonly claims?: string; + + constructor(readonly originalScopes: readonly string[] = [], claims?: string, authorizationServer?: Uri) { const modifiedScopes = [...originalScopes]; modifiedScopes.sort(); this.allScopes = modifiedScopes; this.scopeStr = modifiedScopes.join(' '); + this.claims = claims; this.scopesToSend = this.getScopesToSend(modifiedScopes); this.clientId = this.getClientId(this.allScopes); this.tenant = this.getTenant(this.allScopes, authorizationServer); diff --git a/extensions/microsoft-authentication/src/common/telemetryReporter.ts b/extensions/microsoft-authentication/src/common/telemetryReporter.ts index c9fde4a972c39..5fe773a877ebe 100644 --- a/extensions/microsoft-authentication/src/common/telemetryReporter.ts +++ b/extensions/microsoft-authentication/src/common/telemetryReporter.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { AuthError, ClientAuthError } from '@azure/msal-node'; import TelemetryReporter, { TelemetryEventProperties } from '@vscode/extension-telemetry'; import { IExperimentationTelemetry } from 'vscode-tas-client'; @@ -35,11 +36,11 @@ export class MicrosoftAuthenticationTelemetryReporter implements IExperimentatio ); } - sendActivatedWithClassicImplementationEvent(): void { + sendActivatedWithMsalNoBrokerEvent(): void { /* __GDPR__ - "activatingClassic" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users use the classic login flow." } + "activatingMsalNoBroker" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users use the msal-no-broker login flow. This only fires if the user explictly opts in to this." } */ - this._telemetryReporter.sendTelemetryEvent('activatingClassic'); + this._telemetryReporter.sendTelemetryEvent('activatingmsalnobroker'); } sendLoginEvent(scopes: readonly string[]): void { @@ -74,21 +75,74 @@ export class MicrosoftAuthenticationTelemetryReporter implements IExperimentatio this._telemetryReporter.sendTelemetryEvent('logoutFailed'); } - sendTelemetryErrorEvent(error: unknown): void { - const errorMessage = error instanceof Error ? error.message : String(error); - const errorStack = error instanceof Error ? error.stack : undefined; - const errorName = error instanceof Error ? error.name : undefined; + sendTelemetryErrorEvent(error: Error | string): void { + let errorMessage: string | undefined; + let errorName: string | undefined; + let errorCode: string | undefined; + let errorCorrelationId: string | undefined; + if (typeof error === 'string') { + errorMessage = error; + } else { + const authError: AuthError = error as AuthError; + // don't set error message or stack because it contains PII + errorCode = authError.errorCode; + errorCorrelationId = authError.correlationId; + errorName = authError.name; + } /* __GDPR__ "msalError" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow.", - "errorMessage": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The error message from the exception." }, - "errorStack": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The stack trace from the exception." }, - "errorName": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The name of the error." } + "errorMessage": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The error message." }, + "errorName": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The name of the error." }, + "errorCode": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The error code." }, + "errorCorrelationId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The error correlation id." } } */ - this._telemetryReporter.sendTelemetryErrorEvent('msalError', { errorMessage, errorStack, errorName }); + this._telemetryReporter.sendTelemetryErrorEvent('msalError', { + errorMessage, + errorName, + errorCode, + errorCorrelationId, + }); + } + + sendTelemetryClientAuthErrorEvent(error: AuthError): void { + const errorCode = error.errorCode; + const correlationId = error.correlationId; + const errorName = error.name; + let brokerErrorCode: string | undefined; + let brokerStatusCode: string | undefined; + let brokerTag: string | undefined; + + // Extract platform broker error information if available + if (error.platformBrokerError) { + brokerErrorCode = error.platformBrokerError.errorCode; + brokerStatusCode = `${error.platformBrokerError.statusCode}`; + brokerTag = error.platformBrokerError.tag; + } + + /* __GDPR__ + "msalClientAuthError" : { + "owner": "TylerLeonhardt", + "comment": "Used to determine how often users run into client auth errors during the login flow.", + "errorName": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The name of the client auth error." }, + "errorCode": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The client auth error code." }, + "correlationId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The client auth error correlation id." }, + "brokerErrorCode": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The broker error code." }, + "brokerStatusCode": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The broker error status code." }, + "brokerTag": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The broker error tag." } + } + */ + this._telemetryReporter.sendTelemetryErrorEvent('msalClientAuthError', { + errorName, + errorCode, + correlationId, + brokerErrorCode, + brokerStatusCode, + brokerTag + }); } /** diff --git a/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts b/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts index 69d7afaa38a03..31375af860f36 100644 --- a/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts +++ b/extensions/microsoft-authentication/src/common/test/loopbackClientAndOpener.test.ts @@ -22,7 +22,7 @@ suite('UriHandlerLoopbackClient', () => { envStub.openExternal.resolves(true); envStub.asExternalUri.callThrough(); uriHandler = new UriEventHandler(); - client = new UriHandlerLoopbackClient(uriHandler, redirectUri, window.createOutputChannel('test', { log: true })); + client = new UriHandlerLoopbackClient(uriHandler, redirectUri, callbackUri, window.createOutputChannel('test', { log: true })); }); teardown(() => { @@ -35,8 +35,6 @@ suite('UriHandlerLoopbackClient', () => { const testUrl = 'http://example.com?foo=5'; await client.openBrowser(testUrl); - - assert.ok(envStub.asExternalUri.calledOnce); assert.ok(envStub.openExternal.calledOnce); const expectedUri = Uri.parse(testUrl + `&state=${encodeURI(callbackUri.toString(true))}`); @@ -52,6 +50,7 @@ suite('UriHandlerLoopbackClient', () => { }); }); + // Skipped for now until `listenForAuthCode` is refactored to not show quick pick suite('listenForAuthCode', () => { test('should return auth code from URL', async () => { const code = '1234'; diff --git a/extensions/microsoft-authentication/src/common/test/scopeData.test.ts b/extensions/microsoft-authentication/src/common/test/scopeData.test.ts index e30d0b7ed18cf..3dc9d95aa146e 100644 --- a/extensions/microsoft-authentication/src/common/test/scopeData.test.ts +++ b/extensions/microsoft-authentication/src/common/test/scopeData.test.ts @@ -75,21 +75,37 @@ suite('ScopeData', () => { assert.strictEqual(scopeData.tenantId, 'some_guid'); }); + test('should not return claims', () => { + const scopeData = new ScopeData(['custom_scope']); + assert.strictEqual(scopeData.claims, undefined); + }); + + test('should return claims', () => { + const scopeData = new ScopeData(['custom_scope'], 'test'); + assert.strictEqual(scopeData.claims, 'test'); + }); + test('should extract tenant from authorization server URL path', () => { const authorizationServer = Uri.parse('https://login.microsoftonline.com/tenant123/oauth2/v2.0'); - const scopeData = new ScopeData(['custom_scope'], authorizationServer); + const scopeData = new ScopeData(['custom_scope'], undefined, authorizationServer); assert.strictEqual(scopeData.tenant, 'tenant123'); }); test('should fallback to default tenant if authorization server URL has no path segments', () => { const authorizationServer = Uri.parse('https://login.microsoftonline.com'); - const scopeData = new ScopeData(['custom_scope'], authorizationServer); + const scopeData = new ScopeData(['custom_scope'], undefined, authorizationServer); assert.strictEqual(scopeData.tenant, 'organizations'); }); test('should prioritize authorization server URL over VSCODE_TENANT scope', () => { const authorizationServer = Uri.parse('https://login.microsoftonline.com/url_tenant/oauth2/v2.0'); - const scopeData = new ScopeData(['custom_scope', 'VSCODE_TENANT:scope_tenant'], authorizationServer); + const scopeData = new ScopeData(['custom_scope', 'VSCODE_TENANT:scope_tenant'], undefined, authorizationServer); assert.strictEqual(scopeData.tenant, 'url_tenant'); }); + + test('should extract tenant from v1.0 authorization server URL path', () => { + const authorizationServer = Uri.parse('https://login.microsoftonline.com/tenant123'); + const scopeData = new ScopeData(['custom_scope'], undefined, authorizationServer); + assert.strictEqual(scopeData.tenant, 'tenant123'); + }); }); diff --git a/extensions/microsoft-authentication/src/extension.ts b/extensions/microsoft-authentication/src/extension.ts index 487f76c2cdc07..c7cf62b15e337 100644 --- a/extensions/microsoft-authentication/src/extension.ts +++ b/extensions/microsoft-authentication/src/extension.ts @@ -3,60 +3,86 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { commands, env, ExtensionContext, l10n, window, workspace } from 'vscode'; -import * as extensionV1 from './extensionV1'; -import * as extensionV2 from './extensionV2'; -import { createExperimentationService } from './common/experimentation'; -import { MicrosoftAuthenticationTelemetryReporter } from './common/telemetryReporter'; -import { IExperimentationService } from 'vscode-tas-client'; +import { Environment, EnvironmentParameters } from '@azure/ms-rest-azure-env'; import Logger from './logger'; +import { MsalAuthProvider } from './node/authProvider'; +import { UriEventHandler } from './UriEventHandler'; +import { authentication, commands, ExtensionContext, l10n, window, workspace, Disposable, Uri } from 'vscode'; +import { MicrosoftAuthenticationTelemetryReporter, MicrosoftSovereignCloudAuthenticationTelemetryReporter } from './common/telemetryReporter'; -function shouldUseMsal(expService: IExperimentationService): boolean { - // First check if there is a setting value to allow user to override the default - const inspect = workspace.getConfiguration('microsoft-authentication').inspect<'msal' | 'classic'>('implementation'); - if (inspect?.workspaceFolderValue !== undefined) { - Logger.info(`Acquired MSAL enablement value from 'workspaceFolderValue'. Value: ${inspect.workspaceFolderValue}`); - return inspect.workspaceFolderValue === 'msal'; - } - if (inspect?.workspaceValue !== undefined) { - Logger.info(`Acquired MSAL enablement value from 'workspaceValue'. Value: ${inspect.workspaceValue}`); - return inspect.workspaceValue === 'msal'; +let implementation: 'msal' | 'msal-no-broker' = 'msal'; +const getImplementation = () => workspace.getConfiguration('microsoft-authentication').get<'msal' | 'msal-no-broker'>('implementation') ?? 'msal'; + +async function initMicrosoftSovereignCloudAuthProvider( + context: ExtensionContext, + uriHandler: UriEventHandler +): Promise { + const environment = workspace.getConfiguration('microsoft-sovereign-cloud').get('environment'); + let authProviderName: string | undefined; + if (!environment) { + return undefined; } - if (inspect?.globalValue !== undefined) { - Logger.info(`Acquired MSAL enablement value from 'globalValue'. Value: ${inspect.globalValue}`); - return inspect.globalValue === 'msal'; + + if (environment === 'custom') { + const customEnv = workspace.getConfiguration('microsoft-sovereign-cloud').get('customEnvironment'); + if (!customEnv) { + const res = await window.showErrorMessage(l10n.t('You must also specify a custom environment in order to use the custom environment auth provider.'), l10n.t('Open settings')); + if (res) { + await commands.executeCommand('workbench.action.openSettingsJson', 'microsoft-sovereign-cloud.customEnvironment'); + } + return undefined; + } + try { + Environment.add(customEnv); + } catch (e) { + const res = await window.showErrorMessage(l10n.t('Error validating custom environment setting: {0}', e.message), l10n.t('Open settings')); + if (res) { + await commands.executeCommand('workbench.action.openSettings', 'microsoft-sovereign-cloud.customEnvironment'); + } + return undefined; + } + authProviderName = customEnv.name; + } else { + authProviderName = environment; } - // Then check if the experiment value - const expValue = expService.getTreatmentVariable('vscode', 'microsoft.useMsal'); - if (expValue !== undefined) { - Logger.info(`Acquired MSAL enablement value from 'exp'. Value: ${expValue}`); - return expValue; + const env = Environment.get(authProviderName); + if (!env) { + await window.showErrorMessage(l10n.t('The environment `{0}` is not a valid environment.', authProviderName), l10n.t('Open settings')); + return undefined; } - Logger.info('Acquired MSAL enablement value from default. Value: false'); - // If no setting or experiment value is found, default to true - return true; + const authProvider = await MsalAuthProvider.create( + context, + new MicrosoftSovereignCloudAuthenticationTelemetryReporter(context.extension.packageJSON.aiKey), + window.createOutputChannel(l10n.t('Microsoft Sovereign Cloud Authentication'), { log: true }), + uriHandler, + env + ); + const disposable = authentication.registerAuthenticationProvider( + 'microsoft-sovereign-cloud', + authProviderName, + authProvider, + { supportsMultipleAccounts: true, supportsChallenges: true } + ); + context.subscriptions.push(disposable); + return disposable; } -let useMsal: boolean | undefined; export async function activate(context: ExtensionContext) { const mainTelemetryReporter = new MicrosoftAuthenticationTelemetryReporter(context.extension.packageJSON.aiKey); - const expService = await createExperimentationService( - context, - mainTelemetryReporter, - env.uriScheme !== 'vscode', // isPreRelease - ); - useMsal = shouldUseMsal(expService); + implementation = getImplementation(); context.subscriptions.push(workspace.onDidChangeConfiguration(async e => { if (!e.affectsConfiguration('microsoft-authentication')) { return; } - - if (useMsal === shouldUseMsal(expService)) { + if (implementation === getImplementation()) { return; } + // Allow for the migration to be re-attempted if the user switches back to the MSAL implementation + context.globalState.update('msalMigration', undefined); + const reload = l10n.t('Reload'); const result = await window.showInformationMessage( 'Reload required', @@ -71,20 +97,48 @@ export async function activate(context: ExtensionContext) { commands.executeCommand('workbench.action.reloadWindow'); } })); - const isNodeEnvironment = typeof process !== 'undefined' && typeof process?.versions?.node === 'string'; - // Only activate the new extension if we are not running in a browser environment - if (useMsal && isNodeEnvironment) { - await extensionV2.activate(context, mainTelemetryReporter); - } else { - mainTelemetryReporter.sendActivatedWithClassicImplementationEvent(); - await extensionV1.activate(context, mainTelemetryReporter.telemetryReporter); + + switch (implementation) { + case 'msal-no-broker': + mainTelemetryReporter.sendActivatedWithMsalNoBrokerEvent(); + break; + case 'msal': + default: + break; } + + const uriHandler = new UriEventHandler(); + context.subscriptions.push(uriHandler); + const authProvider = await MsalAuthProvider.create( + context, + mainTelemetryReporter, + Logger, + uriHandler + ); + context.subscriptions.push(authentication.registerAuthenticationProvider( + 'microsoft', + 'Microsoft', + authProvider, + { + supportsMultipleAccounts: true, + supportsChallenges: true, + supportedAuthorizationServers: [ + Uri.parse('https://login.microsoftonline.com/*'), + Uri.parse('https://login.microsoftonline.com/*/v2.0') + ] + } + )); + + let microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, uriHandler); + + context.subscriptions.push(workspace.onDidChangeConfiguration(async e => { + if (e.affectsConfiguration('microsoft-sovereign-cloud')) { + microsoftSovereignCloudAuthProviderDisposable?.dispose(); + microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, uriHandler); + } + })); } export function deactivate() { - if (useMsal) { - extensionV2.deactivate(); - } else { - extensionV1.deactivate(); - } + Logger.info('Microsoft Authentication is deactivating...'); } diff --git a/extensions/microsoft-authentication/src/extensionV1.ts b/extensions/microsoft-authentication/src/extensionV1.ts deleted file mode 100644 index 9956ede25e1d0..0000000000000 --- a/extensions/microsoft-authentication/src/extensionV1.ts +++ /dev/null @@ -1,192 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import { Environment, EnvironmentParameters } from '@azure/ms-rest-azure-env'; -import { AzureActiveDirectoryService, IStoredSession } from './AADHelper'; -import { BetterTokenStorage } from './betterSecretStorage'; -import { UriEventHandler } from './UriEventHandler'; -import TelemetryReporter from '@vscode/extension-telemetry'; -import Logger from './logger'; - -async function initMicrosoftSovereignCloudAuthProvider(context: vscode.ExtensionContext, telemetryReporter: TelemetryReporter, uriHandler: UriEventHandler, tokenStorage: BetterTokenStorage): Promise { - const environment = vscode.workspace.getConfiguration('microsoft-sovereign-cloud').get('environment'); - let authProviderName: string | undefined; - if (!environment) { - return undefined; - } - - if (environment === 'custom') { - const customEnv = vscode.workspace.getConfiguration('microsoft-sovereign-cloud').get('customEnvironment'); - if (!customEnv) { - const res = await vscode.window.showErrorMessage(vscode.l10n.t('You must also specify a custom environment in order to use the custom environment auth provider.'), vscode.l10n.t('Open settings')); - if (res) { - await vscode.commands.executeCommand('workbench.action.openSettingsJson', 'microsoft-sovereign-cloud.customEnvironment'); - } - return undefined; - } - try { - Environment.add(customEnv); - } catch (e) { - const res = await vscode.window.showErrorMessage(vscode.l10n.t('Error validating custom environment setting: {0}', e.message), vscode.l10n.t('Open settings')); - if (res) { - await vscode.commands.executeCommand('workbench.action.openSettings', 'microsoft-sovereign-cloud.customEnvironment'); - } - return undefined; - } - authProviderName = customEnv.name; - } else { - authProviderName = environment; - } - - const env = Environment.get(authProviderName); - if (!env) { - const res = await vscode.window.showErrorMessage(vscode.l10n.t('The environment `{0}` is not a valid environment.', authProviderName), vscode.l10n.t('Open settings')); - return undefined; - } - - const aadService = new AzureActiveDirectoryService( - vscode.window.createOutputChannel(vscode.l10n.t('Microsoft Sovereign Cloud Authentication'), { log: true }), - context, - uriHandler, - tokenStorage, - telemetryReporter, - env); - await aadService.initialize(); - - const disposable = vscode.authentication.registerAuthenticationProvider('microsoft-sovereign-cloud', authProviderName, { - onDidChangeSessions: aadService.onDidChangeSessions, - getSessions: (scopes: string[]) => aadService.getSessions(scopes), - createSession: async (scopes: string[]) => { - try { - /* __GDPR__ - "loginMicrosoftSovereignCloud" : { - "owner": "TylerLeonhardt", - "comment": "Used to determine the usage of the Microsoft Sovereign Cloud Auth Provider.", - "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." } - } - */ - telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloud', { - // Get rid of guids from telemetry. - scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))), - }); - - return await aadService.createSession(scopes); - } catch (e) { - /* __GDPR__ - "loginMicrosoftSovereignCloudFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." } - */ - telemetryReporter.sendTelemetryEvent('loginMicrosoftSovereignCloudFailed'); - - throw e; - } - }, - removeSession: async (id: string) => { - try { - /* __GDPR__ - "logoutMicrosoftSovereignCloud" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." } - */ - telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloud'); - - await aadService.removeSessionById(id); - } catch (e) { - /* __GDPR__ - "logoutMicrosoftSovereignCloudFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." } - */ - telemetryReporter.sendTelemetryEvent('logoutMicrosoftSovereignCloudFailed'); - } - } - }, { supportsMultipleAccounts: true }); - - context.subscriptions.push(disposable); - return disposable; -} - -export async function activate(context: vscode.ExtensionContext, telemetryReporter: TelemetryReporter) { - // If we ever activate the old flow, then mark that we will need to migrate when the user upgrades to v2. - // TODO: MSAL Migration. Remove this when we remove the old flow. - context.globalState.update('msalMigration', false); - - const uriHandler = new UriEventHandler(); - context.subscriptions.push(uriHandler); - const betterSecretStorage = new BetterTokenStorage('microsoft.login.keylist', context); - - const loginService = new AzureActiveDirectoryService( - Logger, - context, - uriHandler, - betterSecretStorage, - telemetryReporter, - Environment.AzureCloud); - await loginService.initialize(); - - context.subscriptions.push(vscode.authentication.registerAuthenticationProvider( - 'microsoft', - 'Microsoft', - { - onDidChangeSessions: loginService.onDidChangeSessions, - getSessions: (scopes: string[], options?: vscode.AuthenticationProviderSessionOptions) => loginService.getSessions(scopes, options), - createSession: async (scopes: string[], options?: vscode.AuthenticationProviderSessionOptions) => { - try { - /* __GDPR__ - "login" : { - "owner": "TylerLeonhardt", - "comment": "Used to determine the usage of the Microsoft Auth Provider.", - "scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." } - } - */ - telemetryReporter.sendTelemetryEvent('login', { - // Get rid of guids from telemetry. - scopes: JSON.stringify(scopes.map(s => s.replace(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i, '{guid}'))), - }); - - return await loginService.createSession(scopes, options); - } catch (e) { - /* __GDPR__ - "loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into issues with the login flow." } - */ - telemetryReporter.sendTelemetryEvent('loginFailed'); - - throw e; - } - }, - removeSession: async (id: string) => { - try { - /* __GDPR__ - "logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out." } - */ - telemetryReporter.sendTelemetryEvent('logout'); - - await loginService.removeSessionById(id); - } catch (e) { - /* __GDPR__ - "logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often fail to log out." } - */ - telemetryReporter.sendTelemetryEvent('logoutFailed'); - } - } - }, - { - supportsMultipleAccounts: true, - supportedAuthorizationServers: [ - vscode.Uri.parse('https://login.microsoftonline.com/*/v2.0') - ] - } - )); - - let microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, telemetryReporter, uriHandler, betterSecretStorage); - - context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(async e => { - if (e.affectsConfiguration('microsoft-sovereign-cloud')) { - microsoftSovereignCloudAuthProviderDisposable?.dispose(); - microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, telemetryReporter, uriHandler, betterSecretStorage); - } - })); - - return; -} - -// this method is called when your extension is deactivated -export function deactivate() { } diff --git a/extensions/microsoft-authentication/src/extensionV2.ts b/extensions/microsoft-authentication/src/extensionV2.ts deleted file mode 100644 index 3760f0f6ede9a..0000000000000 --- a/extensions/microsoft-authentication/src/extensionV2.ts +++ /dev/null @@ -1,100 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Environment, EnvironmentParameters } from '@azure/ms-rest-azure-env'; -import Logger from './logger'; -import { MsalAuthProvider } from './node/authProvider'; -import { UriEventHandler } from './UriEventHandler'; -import { authentication, commands, ExtensionContext, l10n, window, workspace, Disposable, Uri } from 'vscode'; -import { MicrosoftAuthenticationTelemetryReporter, MicrosoftSovereignCloudAuthenticationTelemetryReporter } from './common/telemetryReporter'; - -async function initMicrosoftSovereignCloudAuthProvider( - context: ExtensionContext, - uriHandler: UriEventHandler -): Promise { - const environment = workspace.getConfiguration('microsoft-sovereign-cloud').get('environment'); - let authProviderName: string | undefined; - if (!environment) { - return undefined; - } - - if (environment === 'custom') { - const customEnv = workspace.getConfiguration('microsoft-sovereign-cloud').get('customEnvironment'); - if (!customEnv) { - const res = await window.showErrorMessage(l10n.t('You must also specify a custom environment in order to use the custom environment auth provider.'), l10n.t('Open settings')); - if (res) { - await commands.executeCommand('workbench.action.openSettingsJson', 'microsoft-sovereign-cloud.customEnvironment'); - } - return undefined; - } - try { - Environment.add(customEnv); - } catch (e) { - const res = await window.showErrorMessage(l10n.t('Error validating custom environment setting: {0}', e.message), l10n.t('Open settings')); - if (res) { - await commands.executeCommand('workbench.action.openSettings', 'microsoft-sovereign-cloud.customEnvironment'); - } - return undefined; - } - authProviderName = customEnv.name; - } else { - authProviderName = environment; - } - - const env = Environment.get(authProviderName); - if (!env) { - await window.showErrorMessage(l10n.t('The environment `{0}` is not a valid environment.', authProviderName), l10n.t('Open settings')); - return undefined; - } - - const authProvider = await MsalAuthProvider.create( - context, - new MicrosoftSovereignCloudAuthenticationTelemetryReporter(context.extension.packageJSON.aiKey), - window.createOutputChannel(l10n.t('Microsoft Sovereign Cloud Authentication'), { log: true }), - uriHandler, - env - ); - const disposable = authentication.registerAuthenticationProvider( - 'microsoft-sovereign-cloud', - authProviderName, - authProvider, - { supportsMultipleAccounts: true } - ); - context.subscriptions.push(disposable); - return disposable; -} - -export async function activate(context: ExtensionContext, mainTelemetryReporter: MicrosoftAuthenticationTelemetryReporter) { - const uriHandler = new UriEventHandler(); - context.subscriptions.push(uriHandler); - const authProvider = await MsalAuthProvider.create( - context, - mainTelemetryReporter, - Logger, - uriHandler - ); - context.subscriptions.push(authentication.registerAuthenticationProvider( - 'microsoft', - 'Microsoft', - authProvider, - { - supportsMultipleAccounts: true, - supportedAuthorizationServers: [ - Uri.parse('https://login.microsoftonline.com/*/v2.0') - ] - } - )); - - let microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, uriHandler); - - context.subscriptions.push(workspace.onDidChangeConfiguration(async e => { - if (e.affectsConfiguration('microsoft-sovereign-cloud')) { - microsoftSovereignCloudAuthProviderDisposable?.dispose(); - microsoftSovereignCloudAuthProviderDisposable = await initMicrosoftSovereignCloudAuthProvider(context, uriHandler); - } - })); -} - -export function deactivate() { } diff --git a/extensions/microsoft-authentication/src/node/authProvider.ts b/extensions/microsoft-authentication/src/node/authProvider.ts index 22356295be060..6b980dc7641b9 100644 --- a/extensions/microsoft-authentication/src/node/authProvider.ts +++ b/extensions/microsoft-authentication/src/node/authProvider.ts @@ -2,8 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AccountInfo, AuthenticationResult, ClientAuthError, ClientAuthErrorCodes, ServerError } from '@azure/msal-node'; -import { AuthenticationGetSessionOptions, AuthenticationProvider, AuthenticationProviderAuthenticationSessionsChangeEvent, AuthenticationProviderSessionOptions, AuthenticationSession, AuthenticationSessionAccountInformation, CancellationError, EventEmitter, ExtensionContext, ExtensionKind, l10n, LogOutputChannel, Uri, window } from 'vscode'; +import { AccountInfo, AuthenticationResult, AuthError, ClientAuthError, ClientAuthErrorCodes, ServerError } from '@azure/msal-node'; +import { AuthenticationChallenge, AuthenticationConstraint, AuthenticationGetSessionOptions, AuthenticationProvider, AuthenticationProviderAuthenticationSessionsChangeEvent, AuthenticationProviderSessionOptions, AuthenticationSession, AuthenticationSessionAccountInformation, CancellationError, env, EventEmitter, ExtensionContext, ExtensionKind, l10n, LogOutputChannel, Uri, window } from 'vscode'; import { Environment } from '@azure/ms-rest-azure-env'; import { CachedPublicClientApplicationManager } from './publicClientCache'; import { UriEventHandler } from '../UriEventHandler'; @@ -12,13 +12,30 @@ import { MicrosoftAccountType, MicrosoftAuthenticationTelemetryReporter } from ' import { ScopeData } from '../common/scopeData'; import { EventBufferer } from '../common/event'; import { BetterTokenStorage } from '../betterSecretStorage'; -import { IStoredSession } from '../AADHelper'; import { ExtensionHost, getMsalFlows } from './flows'; +import { base64Decode } from './buffer'; +import { Config } from '../common/config'; +import { isSupportedClient } from '../common/env'; -const redirectUri = 'https://vscode.dev/redirect'; const MSA_TID = '9188040d-6c67-4c5b-b112-36a304b66dad'; const MSA_PASSTHRU_TID = 'f8cdef31-a31e-4b4a-93e4-5f571e91255a'; +/** + * Interface for sessions stored from the old authentication flow. + * Used for migration purposes when upgrading to MSAL. + * TODO: Remove this after one or two releases. + */ +export interface IStoredSession { + id: string; + refreshToken: string; + scope: string; // Scopes are alphabetized and joined with a space + account: { + label: string; + id: string; + }; + endpoint: string | undefined; +} + export class MsalAuthProvider implements AuthenticationProvider { private readonly _disposables: { dispose(): void }[]; @@ -86,7 +103,7 @@ export class MsalAuthProvider implements AuthenticationProvider { uriHandler: UriEventHandler, env: Environment = Environment.AzureCloud ): Promise { - const publicClientManager = await CachedPublicClientApplicationManager.create(context.secrets, logger, telemetryReporter, env.name); + const publicClientManager = await CachedPublicClientApplicationManager.create(context.secrets, logger, telemetryReporter, env); context.subscriptions.push(publicClientManager); const authProvider = new MsalAuthProvider(context, telemetryReporter, logger, uriHandler, publicClientManager, env); await authProvider.initialize(); @@ -116,8 +133,8 @@ export class MsalAuthProvider implements AuthenticationProvider { clientTenantMap.get(key)!.refreshTokens.push(session.refreshToken); } - for (const { clientId, refreshTokens } of clientTenantMap.values()) { - await this._publicClientManager.getOrCreate(clientId, refreshTokens); + for (const { clientId, tenant, refreshTokens } of clientTenantMap.values()) { + await this._publicClientManager.getOrCreate(clientId, { refreshTokensToMigrate: refreshTokens, tenant }); } } @@ -129,10 +146,7 @@ export class MsalAuthProvider implements AuthenticationProvider { // Send telemetry for existing accounts for (const cachedPca of this._publicClientManager.getAll()) { for (const account of cachedPca.accounts) { - if (!account.idTokenClaims?.tid) { - continue; - } - const tid = account.idTokenClaims.tid; + const tid = account.tenantId; const type = tid === MSA_TID || tid === MSA_PASSTHRU_TID ? MicrosoftAccountType.MSA : MicrosoftAccountType.AAD; this._telemetryReporter.sendAccountEvent([], type); } @@ -156,7 +170,7 @@ export class MsalAuthProvider implements AuthenticationProvider { async getSessions(scopes: string[] | undefined, options: AuthenticationGetSessionOptions = {}): Promise { const askingForAll = scopes === undefined; - const scopeData = new ScopeData(scopes, options?.authorizationServer); + const scopeData = new ScopeData(scopes, undefined, options?.authorizationServer); // Do NOT use `scopes` beyond this place in the code. Use `scopeData` instead. this._logger.info('[getSessions]', askingForAll ? '[all]' : `[${scopeData.scopeStr}]`, 'starting'); @@ -186,7 +200,7 @@ export class MsalAuthProvider implements AuthenticationProvider { } async createSession(scopes: readonly string[], options: AuthenticationProviderSessionOptions): Promise { - const scopeData = new ScopeData(scopes, options.authorizationServer); + const scopeData = new ScopeData(scopes, undefined, options.authorizationServer); // Do NOT use `scopes` beyond this place in the code. Use `scopeData` instead. this._logger.info('[createSession]', `[${scopeData.scopeStr}]`, 'starting'); @@ -210,11 +224,11 @@ export class MsalAuthProvider implements AuthenticationProvider { } }; - const isNodeEnvironment = typeof process !== 'undefined' && typeof process?.versions?.node === 'string'; + const callbackUri = await env.asExternalUri(Uri.parse(`${env.uriScheme}://vscode.microsoft-authentication`)); const flows = getMsalFlows({ - extensionHost: isNodeEnvironment - ? this._context.extension.extensionKind === ExtensionKind.UI ? ExtensionHost.Local : ExtensionHost.Remote - : ExtensionHost.WebWorker, + extensionHost: this._context.extension.extensionKind === ExtensionKind.UI ? ExtensionHost.Local : ExtensionHost.Remote, + supportedClient: isSupportedClient(callbackUri), + isBrokerSupported: cachedPca.isBrokerAvailable }); const authority = new URL(scopeData.tenant, this._env.activeDirectoryEndpointUrl).toString(); @@ -235,7 +249,8 @@ export class MsalAuthProvider implements AuthenticationProvider { loginHint: options.account?.label, windowHandle: window.nativeHandle ? Buffer.from(window.nativeHandle) : undefined, logger: this._logger, - uriHandler: this._uriHandler + uriHandler: this._uriHandler, + callbackUri }); const session = this.sessionFromAuthenticationResult(result, scopeData.originalScopes); @@ -287,6 +302,145 @@ export class MsalAuthProvider implements AuthenticationProvider { this._logger.info('[removeSession]', sessionId, `attempted to remove ${promises.length} sessions`); } + async getSessionsFromChallenges(constraint: AuthenticationConstraint, options: AuthenticationProviderSessionOptions): Promise { + this._logger.info('[getSessionsFromChallenges]', 'starting with', constraint.challenges.length, 'challenges'); + + // Use scopes from challenges if provided, otherwise use fallback scopes + const scopes = this.extractScopesFromChallenges(constraint.challenges) ?? constraint.fallbackScopes; + if (!scopes || scopes.length === 0) { + throw new Error('No scopes found in authentication challenges or fallback scopes'); + } + const claims = this.extractClaimsFromChallenges(constraint.challenges); + if (!claims) { + throw new Error('No claims found in authentication challenges'); + } + const scopeData = new ScopeData(scopes, claims, options?.authorizationServer); + this._logger.info('[getSessionsFromChallenges]', `[${scopeData.scopeStr}]`, 'with claims:', scopeData.claims); + + const cachedPca = await this._publicClientManager.getOrCreate(scopeData.clientId); + const sessions = await this.getAllSessionsForPca(cachedPca, scopeData, options?.account); + + this._logger.info('[getSessionsFromChallenges]', 'returning', sessions.length, 'sessions'); + return sessions; + } + + async createSessionFromChallenges(constraint: AuthenticationConstraint, options: AuthenticationProviderSessionOptions): Promise { + this._logger.info('[createSessionFromChallenges]', 'starting with', constraint.challenges.length, 'challenges'); + + // Use scopes from challenges if provided, otherwise use fallback scopes + const scopes = this.extractScopesFromChallenges(constraint.challenges) ?? constraint.fallbackScopes; + if (!scopes || scopes.length === 0) { + throw new Error('No scopes found in authentication challenges or fallback scopes'); + } + const claims = this.extractClaimsFromChallenges(constraint.challenges); + + // Use scopes if available, otherwise fall back to default scopes + const effectiveScopes = scopes.length > 0 ? scopes : ['https://graph.microsoft.com/User.Read']; + + const scopeData = new ScopeData(effectiveScopes, claims, options.authorizationServer); + this._logger.info('[createSessionFromChallenges]', `[${scopeData.scopeStr}]`, 'starting with claims:', claims); + + const cachedPca = await this._publicClientManager.getOrCreate(scopeData.clientId); + + // Used for showing a friendlier message to the user when the explicitly cancel a flow. + let userCancelled: boolean | undefined; + const yes = l10n.t('Yes'); + const no = l10n.t('No'); + const promptToContinue = async (mode: string) => { + if (userCancelled === undefined) { + // We haven't had a failure yet so wait to prompt + return; + } + const message = userCancelled + ? l10n.t('Having trouble logging in? Would you like to try a different way? ({0})', mode) + : l10n.t('You have not yet finished authorizing this extension to use your Microsoft Account. Would you like to try a different way? ({0})', mode); + const result = await window.showWarningMessage(message, yes, no); + if (result !== yes) { + throw new CancellationError(); + } + }; + + const callbackUri = await env.asExternalUri(Uri.parse(`${env.uriScheme}://vscode.microsoft-authentication`)); + const flows = getMsalFlows({ + extensionHost: this._context.extension.extensionKind === ExtensionKind.UI ? ExtensionHost.Local : ExtensionHost.Remote, + isBrokerSupported: cachedPca.isBrokerAvailable, + supportedClient: isSupportedClient(callbackUri) + }); + + const authority = new URL(scopeData.tenant, this._env.activeDirectoryEndpointUrl).toString(); + let lastError: Error | undefined; + for (const flow of flows) { + if (flow !== flows[0]) { + try { + await promptToContinue(flow.label); + } finally { + this._telemetryReporter.sendLoginFailedEvent(); + } + } + try { + // Create the authentication request with claims if provided + const authRequest = { + cachedPca, + authority, + scopes: scopeData.scopesToSend, + loginHint: options.account?.label, + windowHandle: window.nativeHandle ? Buffer.from(window.nativeHandle) : undefined, + logger: this._logger, + uriHandler: this._uriHandler, + claims: scopeData.claims, + callbackUri + }; + + const result = await flow.trigger(authRequest); + + const session = this.sessionFromAuthenticationResult(result, scopeData.originalScopes); + this._telemetryReporter.sendLoginEvent(session.scopes); + this._logger.info('[createSessionFromChallenges]', `[${scopeData.scopeStr}]`, 'returned session'); + return session; + } catch (e) { + lastError = e as Error; + if (e instanceof ClientAuthError && e.errorCode === ClientAuthErrorCodes.userCanceled) { + this._logger.info('[createSessionFromChallenges]', `[${scopeData.scopeStr}]`, 'user cancelled'); + userCancelled = true; + continue; + } + this._logger.error('[createSessionFromChallenges]', `[${scopeData.scopeStr}]`, 'error', e); + throw e; + } + } + + this._telemetryReporter.sendLoginFailedEvent(); + throw lastError ?? new Error('No auth flow succeeded'); + } + + private extractScopesFromChallenges(challenges: readonly AuthenticationChallenge[]): string[] | undefined { + for (const challenge of challenges) { + if (challenge.scheme.toLowerCase() === 'bearer' && challenge.params.scope) { + return challenge.params.scope.split(' '); + } + } + return undefined; + } + + private extractClaimsFromChallenges(challenges: readonly AuthenticationChallenge[]): string | undefined { + for (const challenge of challenges) { + if (challenge.scheme.toLowerCase() === 'bearer' && challenge.params.claims) { + try { + return base64Decode(challenge.params.claims); + } catch (e) { + this._logger.warn('[extractClaimsFromChallenges]', 'failed to decode claims... checking if it is already JSON', e); + try { + JSON.parse(challenge.params.claims); + return challenge.params.claims; + } catch (e) { + this._logger.error('[extractClaimsFromChallenges]', 'failed to parse claims as JSON... returning undefined', e); + } + } + } + } + return undefined; + } + //#endregion private async getAllSessionsForPca( @@ -344,10 +498,24 @@ export class MsalAuthProvider implements AuthenticationProvider { forceRefresh = true; } } + // When claims are present, force refresh to ensure we get a token that satisfies the claims + let claims: string | undefined; + if (scopeData.claims) { + forceRefresh = true; + claims = scopeData.claims; + } + let redirectUri: string | undefined; + // If we have the broker available and are on macOS, we HAVE to include the redirect URI or MSAL will throw an error. + // HOWEVER, if we are _not_ using the broker, we MUST NOT include the redirect URI or MSAL will throw an error. + if (cachedPca.isBrokerAvailable && process.platform === 'darwin') { + redirectUri = Config.macOSBrokerRedirectUri; + } + this._logger.trace(`[getAllSessionsForPca] [${scopeData.scopeStr}] [${account.environment}] [${account.username}] acquiring token silently with${forceRefresh ? ' ' : 'out '}force refresh${claims ? ' and claims' : ''}...`); const result = await cachedPca.acquireTokenSilent({ account, authority, scopes: scopeData.scopesToSend, + claims, redirectUri, forceRefresh }); @@ -355,7 +523,12 @@ export class MsalAuthProvider implements AuthenticationProvider { } catch (e) { // If we can't get a token silently, the account is probably in a bad state so we should skip it // MSAL will log this already, so we don't need to log it again - this._telemetryReporter.sendTelemetryErrorEvent(e); + if (e instanceof AuthError) { + this._telemetryReporter.sendTelemetryClientAuthErrorEvent(e); + } else { + this._telemetryReporter.sendTelemetryErrorEvent(e); + } + this._logger.info(`[getAllSessionsForPca] [${scopeData.scopeStr}] [${account.username}] failed to acquire token silently, skipping account`, JSON.stringify(e)); continue; } } diff --git a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts index ac581a4c88ec1..e86269833a8e5 100644 --- a/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts +++ b/extensions/microsoft-authentication/src/node/cachedPublicClientApplication.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { PublicClientApplication, AccountInfo, SilentFlowRequest, AuthenticationResult, InteractiveRequest, LogLevel, RefreshTokenRequest } from '@azure/msal-node'; +import { PublicClientApplication, AccountInfo, SilentFlowRequest, AuthenticationResult, InteractiveRequest, LogLevel, RefreshTokenRequest, BrokerOptions, DeviceCodeRequest } from '@azure/msal-node'; import { NativeBrokerPlugin } from '@azure/msal-node-extensions'; -import { Disposable, SecretStorage, LogOutputChannel, window, ProgressLocation, l10n, EventEmitter } from 'vscode'; -import { raceCancellationAndTimeoutError } from '../common/async'; +import { Disposable, SecretStorage, LogOutputChannel, window, ProgressLocation, l10n, EventEmitter, workspace, env, Uri, UIKind } from 'vscode'; +import { DeferredPromise, raceCancellationAndTimeoutError } from '../common/async'; import { SecretStorageCachePlugin } from '../common/cachePlugin'; import { MsalLoggerOptions } from '../common/loggerOptions'; import { ICachedPublicClientApplication } from '../common/publicClientCache'; @@ -24,7 +24,7 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica private readonly _secretStorageCachePlugin: SecretStorageCachePlugin; // Broker properties - private readonly _isBrokerAvailable: boolean; + readonly isBrokerAvailable: boolean = false; //#region Events @@ -50,18 +50,31 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica ); const loggerOptions = new MsalLoggerOptions(_logger, telemetryReporter); - const nativeBrokerPlugin = new NativeBrokerPlugin(); - this._isBrokerAvailable = nativeBrokerPlugin.isBrokerAvailable; + let broker: BrokerOptions | undefined; + if (env.uiKind === UIKind.Web) { + this._logger.info(`[${this._clientId}] Native Broker is not available in web UI`); + } else if (workspace.getConfiguration('microsoft-authentication').get<'msal' | 'msal-no-broker'>('implementation') === 'msal-no-broker') { + this._logger.info(`[${this._clientId}] Native Broker disabled via settings`); + } else { + const nativeBrokerPlugin = new NativeBrokerPlugin(); + this.isBrokerAvailable = nativeBrokerPlugin.isBrokerAvailable; + this._logger.info(`[${this._clientId}] Native Broker enabled: ${this.isBrokerAvailable}`); + if (this.isBrokerAvailable) { + broker = { nativeBrokerPlugin }; + } + } this._pca = new PublicClientApplication({ auth: { clientId: _clientId }, system: { loggerOptions: { correlationId: _clientId, loggerCallback: (level, message, containsPii) => loggerOptions.loggerCallback(level, message, containsPii), - logLevel: LogLevel.Trace + logLevel: LogLevel.Trace, + // Enable PII logging since it will only go to the output channel + piiLoggingEnabled: true } }, - broker: { nativeBrokerPlugin }, + broker, cache: { cachePlugin: this._secretStorageCachePlugin } }); this._disposable = Disposable.from( @@ -110,9 +123,9 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica ); if (fiveMinutesBefore < new Date()) { this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${request.authority}] [${request.scopes.join(' ')}] [${request.account.username}] id token is expired or about to expire. Forcing refresh...`); - const newRequest = this._isBrokerAvailable + const newRequest = this.isBrokerAvailable // HACK: Broker doesn't support forceRefresh so we need to pass in claims which will force a refresh - ? { ...request, claims: '{ "id_token": {}}' } + ? { ...request, claims: request.claims ?? '{ "id_token": {}}' } : { ...request, forceRefresh: true }; result = await this._sequencer.queue(() => this._pca.acquireTokenSilent(newRequest)); this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${request.authority}] [${request.scopes.join(' ')}] [${request.account.username}] got forced result`); @@ -128,9 +141,9 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica // HACK: Only for the Broker we try one more time with different claims to force a refresh. Why? We've seen the Broker caching tokens by the claims requested, thus // there has been a situation where both tokens are expired. - if (this._isBrokerAvailable) { + if (this.isBrokerAvailable) { this._logger.error(`[acquireTokenSilent] [${this._clientId}] [${request.authority}] [${request.scopes.join(' ')}] [${request.account.username}] forcing refresh with different claims...`); - const newRequest = { ...request, claims: '{ "access_token": {}}' }; + const newRequest = { ...request, claims: request.claims ?? '{ "access_token": {}}' }; result = await this._sequencer.queue(() => this._pca.acquireTokenSilent(newRequest)); this._logger.debug(`[acquireTokenSilent] [${this._clientId}] [${request.authority}] [${request.scopes.join(' ')}] [${request.account.username}] got forced result with different claims`); const newIdTokenExpirationInSecs = (result.idTokenClaims as { exp?: number }).exp; @@ -166,20 +179,25 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica title: l10n.t('Signing in to Microsoft...') }, (_process, token) => this._sequencer.queue(async () => { - const result = await raceCancellationAndTimeoutError( - this._pca.acquireTokenInteractive(request), - token, - 1000 * 60 * 5 - ); - if (this._isBrokerAvailable) { - await this._accountAccess.setAllowedAccess(result.account!, true); + try { + const result = await raceCancellationAndTimeoutError( + this._pca.acquireTokenInteractive(request), + token, + 1000 * 60 * 5 + ); + if (this.isBrokerAvailable) { + await this._accountAccess.setAllowedAccess(result.account!, true); + } + // Force an update so that the account cache is updated. + // TODO:@TylerLeonhardt The problem is, we use the sequencer for + // change events but we _don't_ use it for the accounts cache. + // We should probably use it for the accounts cache as well. + await this._update(); + return result; + } catch (error) { + this._logger.error(`[acquireTokenInteractive] [${this._clientId}] [${request.authority}] [${request.scopes?.join(' ')}] error: ${error}`); + throw error; } - // Force an update so that the account cache is updated. - // TODO:@TylerLeonhardt The problem is, we use the sequencer for - // change events but we _don't_ use it for the accounts cache. - // We should probably use it for the accounts cache as well. - await this._update(); - return result; }) ); } @@ -203,22 +221,97 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica }); if (result) { // this._setupRefresh(result); - if (this._isBrokerAvailable && result.account) { + if (this.isBrokerAvailable && result.account) { + await this._accountAccess.setAllowedAccess(result.account, true); + } + } + return result; + } + + async acquireTokenByDeviceCode(request: Omit): Promise { + this._logger.debug(`[acquireTokenByDeviceCode] [${this._clientId}] [${request.authority}] [${request.scopes.join(' ')}]`); + const result = await this._sequencer.queue(async () => { + const deferredPromise = new DeferredPromise(); + const result = await Promise.race([ + this._pca.acquireTokenByDeviceCode({ + ...request, + deviceCodeCallback: (response) => void this._deviceCodeCallback(response, deferredPromise) + }), + deferredPromise.p + ]); + await deferredPromise.complete(result); + // Force an update so that the account cache is updated. + // TODO:@TylerLeonhardt The problem is, we use the sequencer for + // change events but we _don't_ use it for the accounts cache. + // We should probably use it for the accounts cache as well. + await this._update(); + return result; + }); + if (result) { + if (this.isBrokerAvailable && result.account) { await this._accountAccess.setAllowedAccess(result.account, true); } } return result; } + private async _deviceCodeCallback( + // MSAL doesn't expose this type... + response: Parameters[0], + deferredPromise: DeferredPromise + ): Promise { + const button = l10n.t('Copy & Continue to Microsoft'); + const modalResult = await window.showInformationMessage( + l10n.t({ message: 'Your Code: {0}', args: [response.userCode], comment: ['The {0} will be a code, e.g. 123-456'] }), + { + modal: true, + detail: l10n.t('To finish authenticating, navigate to Microsoft and paste in the above one-time code.') + }, button); + + if (modalResult !== button) { + this._logger.debug(`[deviceCodeCallback] [${this._clientId}] User cancelled the device code flow.`); + deferredPromise.cancel(); + return; + } + + await env.clipboard.writeText(response.userCode); + await env.openExternal(Uri.parse(response.verificationUri)); + await window.withProgress({ + location: ProgressLocation.Notification, + cancellable: true, + title: l10n.t({ + message: 'Open [{0}]({0}) in a new tab and paste your one-time code: {1}', + args: [response.verificationUri, response.userCode], + comment: [ + 'The [{0}]({0}) will be a url and the {1} will be a code, e.g. 123456', + '{Locked="[{0}]({0})"}' + ] + }) + }, async (_, token) => { + const disposable = token.onCancellationRequested(() => { + this._logger.debug(`[deviceCodeCallback] [${this._clientId}] Device code flow cancelled by user.`); + deferredPromise.cancel(); + }); + try { + await deferredPromise.p; + this._logger.debug(`[deviceCodeCallback] [${this._clientId}] Device code flow completed successfully.`); + } catch (error) { + // Ignore errors here, they are handled at a higher scope + } finally { + disposable.dispose(); + } + }); + } + removeAccount(account: AccountInfo): Promise { - if (this._isBrokerAvailable) { + if (this.isBrokerAvailable) { return this._accountAccess.setAllowedAccess(account, false); } return this._sequencer.queue(() => this._pca.getTokenCache().removeAccount(account)); } private _registerOnSecretStorageChanged() { - if (this._isBrokerAvailable) { + if (this.isBrokerAvailable) { return this._accountAccess.onDidAccountAccessChange(() => this._sequencer.queue(() => this._update())); } return this._secretStorageCachePlugin.onDidChange(() => this._sequencer.queue(() => this._update())); @@ -258,7 +351,7 @@ export class CachedPublicClientApplication implements ICachedPublicClientApplica // Clear in-memory cache so we know we're getting account data from the SecretStorage this._pca.clearCache(); let after = await this._pca.getAllAccounts(); - if (this._isBrokerAvailable) { + if (this.isBrokerAvailable) { after = after.filter(a => this._accountAccess.isAllowedAccess(a)); } this._accounts = after; diff --git a/extensions/microsoft-authentication/src/node/flows.ts b/extensions/microsoft-authentication/src/node/flows.ts index ac678d8313dc9..22782330bd68f 100644 --- a/extensions/microsoft-authentication/src/node/flows.ts +++ b/extensions/microsoft-authentication/src/node/flows.ts @@ -9,28 +9,31 @@ import { ICachedPublicClientApplication } from '../common/publicClientCache'; import { UriHandlerLoopbackClient } from '../common/loopbackClientAndOpener'; import { UriEventHandler } from '../UriEventHandler'; import { loopbackTemplate } from './loopbackTemplate'; +import { Config } from '../common/config'; -const redirectUri = 'https://vscode.dev/redirect'; +const DEFAULT_REDIRECT_URI = 'https://vscode.dev/redirect'; export const enum ExtensionHost { - WebWorker, Remote, Local } interface IMsalFlowOptions { supportsRemoteExtensionHost: boolean; - supportsWebWorkerExtensionHost: boolean; + supportsUnsupportedClient: boolean; + supportsBroker: boolean; } interface IMsalFlowTriggerOptions { cachedPca: ICachedPublicClientApplication; authority: string; scopes: string[]; + callbackUri: Uri; loginHint?: string; windowHandle?: Buffer; logger: LogOutputChannel; uriHandler: UriEventHandler; + claims?: string; } interface IMsalFlow { @@ -43,11 +46,16 @@ class DefaultLoopbackFlow implements IMsalFlow { label = 'default'; options: IMsalFlowOptions = { supportsRemoteExtensionHost: false, - supportsWebWorkerExtensionHost: false + supportsUnsupportedClient: true, + supportsBroker: true }; - async trigger({ cachedPca, authority, scopes, loginHint, windowHandle, logger }: IMsalFlowTriggerOptions): Promise { + async trigger({ cachedPca, authority, scopes, claims, loginHint, windowHandle, logger }: IMsalFlowTriggerOptions): Promise { logger.info('Trying default msal flow...'); + let redirectUri: string | undefined; + if (cachedPca.isBrokerAvailable && process.platform === 'darwin') { + redirectUri = Config.macOSBrokerRedirectUri; + } return await cachedPca.acquireTokenInteractive({ openBrowser: async (url: string) => { await env.openExternal(Uri.parse(url)); }, scopes, @@ -56,7 +64,9 @@ class DefaultLoopbackFlow implements IMsalFlow { errorTemplate: loopbackTemplate, loginHint, prompt: loginHint ? undefined : 'select_account', - windowHandle + windowHandle, + claims, + redirectUri }); } } @@ -65,12 +75,17 @@ class UrlHandlerFlow implements IMsalFlow { label = 'protocol handler'; options: IMsalFlowOptions = { supportsRemoteExtensionHost: true, - supportsWebWorkerExtensionHost: false + supportsUnsupportedClient: false, + supportsBroker: false }; - async trigger({ cachedPca, authority, scopes, loginHint, windowHandle, logger, uriHandler }: IMsalFlowTriggerOptions): Promise { + async trigger({ cachedPca, authority, scopes, claims, loginHint, windowHandle, logger, uriHandler, callbackUri }: IMsalFlowTriggerOptions): Promise { logger.info('Trying protocol handler flow...'); - const loopbackClient = new UriHandlerLoopbackClient(uriHandler, redirectUri, logger); + const loopbackClient = new UriHandlerLoopbackClient(uriHandler, DEFAULT_REDIRECT_URI, callbackUri, logger); + let redirectUri: string | undefined; + if (cachedPca.isBrokerAvailable && process.platform === 'darwin') { + redirectUri = Config.macOSBrokerRedirectUri; + } return await cachedPca.acquireTokenInteractive({ openBrowser: (url: string) => loopbackClient.openBrowser(url), scopes, @@ -78,31 +93,55 @@ class UrlHandlerFlow implements IMsalFlow { loopbackClient, loginHint, prompt: loginHint ? undefined : 'select_account', - windowHandle + windowHandle, + claims, + redirectUri }); } } +class DeviceCodeFlow implements IMsalFlow { + label = 'device code'; + options: IMsalFlowOptions = { + supportsRemoteExtensionHost: true, + supportsUnsupportedClient: true, + supportsBroker: false + }; + + async trigger({ cachedPca, authority, scopes, claims, logger }: IMsalFlowTriggerOptions): Promise { + logger.info('Trying device code flow...'); + const result = await cachedPca.acquireTokenByDeviceCode({ scopes, authority, claims }); + if (!result) { + throw new Error('Device code flow did not return a result'); + } + return result; + } +} + const allFlows: IMsalFlow[] = [ new DefaultLoopbackFlow(), - new UrlHandlerFlow() + new UrlHandlerFlow(), + new DeviceCodeFlow() ]; export interface IMsalFlowQuery { extensionHost: ExtensionHost; + supportedClient: boolean; + isBrokerSupported: boolean; } export function getMsalFlows(query: IMsalFlowQuery): IMsalFlow[] { - return allFlows.filter(flow => { + const flows = []; + for (const flow of allFlows) { let useFlow: boolean = true; - switch (query.extensionHost) { - case ExtensionHost.Remote: - useFlow &&= flow.options.supportsRemoteExtensionHost; - break; - case ExtensionHost.WebWorker: - useFlow &&= flow.options.supportsWebWorkerExtensionHost; - break; + if (query.extensionHost === ExtensionHost.Remote) { + useFlow &&= flow.options.supportsRemoteExtensionHost; + } + useFlow &&= flow.options.supportsBroker || !query.isBrokerSupported; + useFlow &&= flow.options.supportsUnsupportedClient || query.supportedClient; + if (useFlow) { + flows.push(flow); } - return useFlow; - }); + } + return flows; } diff --git a/extensions/microsoft-authentication/src/node/publicClientCache.ts b/extensions/microsoft-authentication/src/node/publicClientCache.ts index 728b89d1ecc5f..bea0ccf724156 100644 --- a/extensions/microsoft-authentication/src/node/publicClientCache.ts +++ b/extensions/microsoft-authentication/src/node/publicClientCache.ts @@ -9,6 +9,9 @@ import { ICachedPublicClientApplication, ICachedPublicClientApplicationManager } import { CachedPublicClientApplication } from './cachedPublicClientApplication'; import { IAccountAccess, ScopedAccountAccess } from '../common/accountAccess'; import { MicrosoftAuthenticationTelemetryReporter } from '../common/telemetryReporter'; +import { Environment } from '@azure/ms-rest-azure-env'; +import { Config } from '../common/config'; +import { DEFAULT_REDIRECT_URI } from '../common/env'; export interface IPublicClientApplicationInfo { clientId: string; @@ -26,6 +29,7 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient readonly onDidAccountsChange = this._onDidAccountsChangeEmitter.event; private constructor( + private readonly _env: Environment, private readonly _pcasSecretStorage: IPublicClientApplicationSecretStorage, private readonly _accountAccess: IAccountAccess, private readonly _secretStorage: SecretStorage, @@ -44,13 +48,13 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient secretStorage: SecretStorage, logger: LogOutputChannel, telemetryReporter: MicrosoftAuthenticationTelemetryReporter, - cloudName: string + env: Environment ): Promise { - const pcasSecretStorage = await PublicClientApplicationsSecretStorage.create(secretStorage, cloudName); + const pcasSecretStorage = await PublicClientApplicationsSecretStorage.create(secretStorage, env.name); // TODO: Remove the migrations in a version const migrations = await pcasSecretStorage.getOldValue(); - const accountAccess = await ScopedAccountAccess.create(secretStorage, cloudName, logger, migrations); - const manager = new CachedPublicClientApplicationManager(pcasSecretStorage, accountAccess, secretStorage, logger, telemetryReporter, [pcasSecretStorage, accountAccess]); + const accountAccess = await ScopedAccountAccess.create(secretStorage, env.name, logger, migrations); + const manager = new CachedPublicClientApplicationManager(env, pcasSecretStorage, accountAccess, secretStorage, logger, telemetryReporter, [pcasSecretStorage, accountAccess]); await manager.initialize(); return manager; } @@ -110,7 +114,7 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient Disposable.from(...this._pcaDisposables.values()).dispose(); } - async getOrCreate(clientId: string, refreshTokensToMigrate?: string[]): Promise { + async getOrCreate(clientId: string, migrate?: { refreshTokensToMigrate?: string[]; tenant: string }): Promise { let pca = this._pcas.get(clientId); if (pca) { this._logger.debug(`[getOrCreate] [${clientId}] PublicClientApplicationManager cache hit`); @@ -122,13 +126,24 @@ export class CachedPublicClientApplicationManager implements ICachedPublicClient } // TODO: MSAL Migration. Remove this when we remove the old flow. - if (refreshTokensToMigrate?.length) { + if (migrate?.refreshTokensToMigrate?.length) { this._logger.debug(`[getOrCreate] [${clientId}] Migrating refresh tokens to PCA...`); - for (const refreshToken of refreshTokensToMigrate) { + const authority = new URL(migrate.tenant, this._env.activeDirectoryEndpointUrl).toString(); + let redirectUri = DEFAULT_REDIRECT_URI; + if (pca.isBrokerAvailable && process.platform === 'darwin') { + redirectUri = Config.macOSBrokerRedirectUri; + } + for (const refreshToken of migrate.refreshTokensToMigrate) { try { // Use the refresh token to acquire a result. This will cache the refresh token for future operations. // The scopes don't matter here since we can create any token from the refresh token. - const result = await pca.acquireTokenByRefreshToken({ refreshToken, forceCache: true, scopes: [] }); + const result = await pca.acquireTokenByRefreshToken({ + refreshToken, + forceCache: true, + scopes: [], + authority, + redirectUri + }); if (result?.account) { this._logger.debug(`[getOrCreate] [${clientId}] Refresh token migrated to PCA.`); } diff --git a/extensions/microsoft-authentication/src/node/test/flows.test.ts b/extensions/microsoft-authentication/src/node/test/flows.test.ts new file mode 100644 index 0000000000000..b2685e783cc8e --- /dev/null +++ b/extensions/microsoft-authentication/src/node/test/flows.test.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { getMsalFlows, ExtensionHost, IMsalFlowQuery } from '../flows'; + +suite('getMsalFlows', () => { + test('should return all flows for local extension host with supported client and no broker', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.Local, + supportedClient: true, + isBrokerSupported: false + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 3); + assert.strictEqual(flows[0].label, 'default'); + assert.strictEqual(flows[1].label, 'protocol handler'); + assert.strictEqual(flows[2].label, 'device code'); + }); + + test('should return only default flow for local extension host with supported client and broker', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.Local, + supportedClient: true, + isBrokerSupported: true + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 1); + assert.strictEqual(flows[0].label, 'default'); + }); + + test('should return protocol handler and device code flows for remote extension host with supported client and no broker', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.Remote, + supportedClient: true, + isBrokerSupported: false + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 2); + assert.strictEqual(flows[0].label, 'protocol handler'); + assert.strictEqual(flows[1].label, 'device code'); + }); + + test('should return only default and device code flows for local extension host with unsupported client and no broker', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.Local, + supportedClient: false, + isBrokerSupported: false + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 2); + assert.strictEqual(flows[0].label, 'default'); + assert.strictEqual(flows[1].label, 'device code'); + }); + + test('should return only device code flow for remote extension host with unsupported client and no broker', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.Remote, + supportedClient: false, + isBrokerSupported: false + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 1); + assert.strictEqual(flows[0].label, 'device code'); + }); + + test('should return default flow for local extension host with unsupported client and broker', () => { + const query: IMsalFlowQuery = { + extensionHost: ExtensionHost.Local, + supportedClient: false, + isBrokerSupported: true + }; + const flows = getMsalFlows(query); + assert.strictEqual(flows.length, 1); + assert.strictEqual(flows[0].label, 'default'); + }); +}); diff --git a/extensions/microsoft-authentication/tsconfig.json b/extensions/microsoft-authentication/tsconfig.json index dc4571cacdab9..e9a3ade3ed595 100644 --- a/extensions/microsoft-authentication/tsconfig.json +++ b/extensions/microsoft-authentication/tsconfig.json @@ -1,29 +1,16 @@ { "extends": "../tsconfig.base.json", "compilerOptions": { - "baseUrl": ".", - "experimentalDecorators": true, - "module": "commonjs", - "moduleResolution": "node", + "outDir": "./out", "noFallthroughCasesInSwitch": true, "noUnusedLocals": false, - "outDir": "dist", - "resolveJsonModule": true, - "rootDir": "src", - "skipLibCheck": true, - "sourceMap": true, - "lib": [ - "WebWorker" - ] + "skipLibCheck": true }, - "exclude": [ - "node_modules" - ], "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts", - "../../src/vscode-dts/vscode.proposed.idToken.d.ts", "../../src/vscode-dts/vscode.proposed.nativeWindowHandle.d.ts", - "../../src/vscode-dts/vscode.proposed.authIssuers.d.ts" + "../../src/vscode-dts/vscode.proposed.authIssuers.d.ts", + "../../src/vscode-dts/vscode.proposed.authenticationChallenges.d.ts" ] } diff --git a/extensions/notebook-renderers/.vscodeignore b/extensions/notebook-renderers/.vscodeignore index e168400f68e81..d8277ac1813a4 100644 --- a/extensions/notebook-renderers/.vscodeignore +++ b/extensions/notebook-renderers/.vscodeignore @@ -2,5 +2,5 @@ src/** notebook/** tsconfig.json .gitignore -esbuild.js +esbuild.* src/** diff --git a/extensions/notebook-renderers/esbuild.js b/extensions/notebook-renderers/esbuild.js deleted file mode 100644 index 55d462f8bc37c..0000000000000 --- a/extensions/notebook-renderers/esbuild.js +++ /dev/null @@ -1,17 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -const path = require('path'); - -const srcDir = path.join(__dirname, 'src'); -const outDir = path.join(__dirname, 'renderer-out'); - -require('../esbuild-webview-common').run({ - entryPoints: [ - path.join(srcDir, 'index.ts'), - ], - srcDir, - outdir: outDir, -}, process.argv); diff --git a/extensions/notebook-renderers/esbuild.mjs b/extensions/notebook-renderers/esbuild.mjs new file mode 100644 index 0000000000000..890aacd19bf35 --- /dev/null +++ b/extensions/notebook-renderers/esbuild.mjs @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import path from 'path'; +import { run } from '../esbuild-webview-common.mjs'; + +const srcDir = path.join(import.meta.dirname, 'src'); +const outDir = path.join(import.meta.dirname, 'renderer-out'); + +run({ + entryPoints: [ + path.join(srcDir, 'index.ts'), + ], + srcDir, + outdir: outDir, +}, process.argv); diff --git a/extensions/notebook-renderers/package-lock.json b/extensions/notebook-renderers/package-lock.json index 8dbc5f5ad4c2f..85357e3c85538 100644 --- a/extensions/notebook-renderers/package-lock.json +++ b/extensions/notebook-renderers/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "devDependencies": { "@types/jsdom": "^21.1.0", + "@types/node": "^22.18.10", "@types/vscode-notebook-renderer": "^1.60.0", "jsdom": "^21.1.1" }, @@ -38,10 +39,14 @@ } }, "node_modules/@types/node": { - "version": "18.15.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.3.tgz", - "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==", - "dev": true + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } }, "node_modules/@types/tough-cookie": { "version": "4.0.2", @@ -111,6 +116,20 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -200,6 +219,21 @@ "node": ">=12" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/entities": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", @@ -212,6 +246,55 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escodegen": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", @@ -272,19 +355,126 @@ "dev": true }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { "node": ">= 6" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -400,6 +590,16 @@ "node": ">= 0.8.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -577,6 +777,13 @@ "node": ">= 0.8.0" } }, + "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" + }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", diff --git a/extensions/notebook-renderers/package.json b/extensions/notebook-renderers/package.json index d6ece35af1127..77c042ee66390 100644 --- a/extensions/notebook-renderers/package.json +++ b/extensions/notebook-renderers/package.json @@ -5,7 +5,7 @@ "publisher": "vscode", "version": "1.0.0", "license": "MIT", - "icon": "media/icon.png", + "icon": "media/icon.png", "engines": { "vscode": "^1.57.0" }, @@ -44,11 +44,11 @@ "scripts": { "compile": "npx gulp compile-extension:notebook-renderers && npm run build-notebook", "watch": "npx gulp compile-watch:notebook-renderers", - "build-notebook": "node ./esbuild" + "build-notebook": "node ./esbuild.mjs" }, - "dependencies": {}, "devDependencies": { "@types/jsdom": "^21.1.0", + "@types/node": "^22.18.10", "@types/vscode-notebook-renderer": "^1.60.0", "jsdom": "^21.1.1" }, diff --git a/extensions/notebook-renderers/src/htmlHelper.ts b/extensions/notebook-renderers/src/htmlHelper.ts index 819a3a640affd..dd22022ce3dec 100644 --- a/extensions/notebook-renderers/src/htmlHelper.ts +++ b/extensions/notebook-renderers/src/htmlHelper.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ export const ttPolicy = (typeof window !== 'undefined') ? - window.trustedTypes?.createPolicy('notebookRenderer', { - createHTML: value => value, - createScript: value => value, + (window as Window & { trustedTypes?: any }).trustedTypes?.createPolicy('notebookRenderer', { + createHTML: (value: string) => value, + createScript: (value: string) => value, }) : undefined; diff --git a/extensions/notebook-renderers/src/index.ts b/extensions/notebook-renderers/src/index.ts index dfff75d617e8e..09d4129e81746 100644 --- a/extensions/notebook-renderers/src/index.ts +++ b/extensions/notebook-renderers/src/index.ts @@ -65,7 +65,7 @@ const domEval = (container: Element) => { for (const key of preservedScriptAttributes) { const val = node[key] || node.getAttribute && node.getAttribute(key); if (val) { - scriptTag.setAttribute(key, val as any); + scriptTag.setAttribute(key, val as unknown as string); } } @@ -75,8 +75,8 @@ const domEval = (container: Element) => { }; function getAltText(outputInfo: OutputItem) { - const metadata = outputInfo.metadata; - if (typeof metadata === 'object' && metadata && 'vscode_altText' in metadata && typeof metadata.vscode_altText === 'string') { + const metadata = outputInfo.metadata as Record | undefined; + if (typeof metadata === 'object' && metadata && typeof metadata.vscode_altText === 'string') { return metadata.vscode_altText; } return undefined; @@ -336,9 +336,9 @@ function findScrolledHeight(container: HTMLElement): number | undefined { } function scrollingEnabled(output: OutputItem, options: RenderOptions) { - const metadata = output.metadata; + const metadata = output.metadata as Record | undefined; return (typeof metadata === 'object' && metadata - && 'scrollable' in metadata && typeof metadata.scrollable === 'boolean') ? + && typeof metadata.scrollable === 'boolean') ? metadata.scrollable : options.outputScrolling; } diff --git a/extensions/notebook-renderers/src/test/linkify.test.ts b/extensions/notebook-renderers/src/test/linkify.test.ts index cae8f5694230d..d24145a2b62ae 100644 --- a/extensions/notebook-renderers/src/test/linkify.test.ts +++ b/extensions/notebook-renderers/src/test/linkify.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { JSDOM } from "jsdom"; +import { JSDOM } from 'jsdom'; import { LinkDetector, linkify } from '../linkify'; const dom = new JSDOM(); diff --git a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts index 9dc8f6c845e26..a193ce38d7293 100644 --- a/extensions/notebook-renderers/src/test/notebookRenderer.test.ts +++ b/extensions/notebook-renderers/src/test/notebookRenderer.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { activate } from '..'; import { RendererApi } from 'vscode-notebook-renderer'; import { IDisposable, IRichRenderContext, OutputWithAppend, RenderOptions } from '../rendererTypes'; -import { JSDOM } from "jsdom"; +import { JSDOM } from 'jsdom'; import { LinkDetector } from '../linkify'; const dom = new JSDOM(); @@ -16,12 +16,12 @@ global.document = dom.window.document; suite('Notebook builtin output renderer', () => { const error = { - name: "TypeError", - message: "Expected type `str`, but received type ``", - stack: "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m" + - "\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)" + - "\u001b[1;32mc:\\src\\test\\ws1\\testing.py\u001b[0m in \u001b[0;36mline 2\n\u001b[0;32m 35\u001b[0m \u001b[39m# %%\u001b[39;00m\n\u001b[1;32m----> 36\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mTypeError\u001b[39;00m(\u001b[39m'\u001b[39m\u001b[39merror = f\u001b[39m\u001b[39m\"\u001b[39m\u001b[39mExpected type `str`, but received type `\u001b[39m\u001b[39m{\u001b[39m\u001b[39mtype(name)}`\u001b[39m\u001b[39m\"\u001b[39m\u001b[39m'\u001b[39m)\n" + - "\u001b[1;31mTypeError\u001b[0m: Expected type `str`, but received type ``\"" + name: 'TypeError', + message: 'Expected type `str`, but received type ``', + stack: '\u001b[1;31m---------------------------------------------------------------------------\u001b[0m' + + '\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)' + + '\u001b[1;32mc:\\src\\test\\ws1\\testing.py\u001b[0m in \u001b[0;36mline 2\n\u001b[0;32m 35\u001b[0m \u001b[39m# %%\u001b[39;00m\n\u001b[1;32m----> 36\u001b[0m \u001b[39mraise\u001b[39;00m \u001b[39mTypeError\u001b[39;00m(\u001b[39m\'\u001b[39m\u001b[39merror = f\u001b[39m\u001b[39m"\u001b[39m\u001b[39mExpected type `str`, but received type `\u001b[39m\u001b[39m{\u001b[39m\u001b[39mtype(name)}`\u001b[39m\u001b[39m"\u001b[39m\u001b[39m\'\u001b[39m)\n' + + '\u001b[1;31mTypeError\u001b[0m: Expected type `str`, but received type ``"' }; const errorMimeType = 'application/vnd.code.notebook.error'; @@ -127,13 +127,13 @@ suite('Notebook builtin output renderer', () => { return text; }, blob() { - return [] as any; + return new Blob([text], { type: mime }); }, json() { return '{ }'; }, data() { - return [] as any; + return new Uint8Array(); }, metadata: {} }; @@ -487,13 +487,13 @@ suite('Notebook builtin output renderer', () => { }); const rawIPythonError = { - name: "NameError", - message: "name 'x' is not defined", - stack: "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m" + - "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)" + - "Cell \u001b[1;32mIn[2], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mmyfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n" + - "Cell \u001b[1;32mIn[1], line 2\u001b[0m, in \u001b[0;36mmyfunc\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmyfunc\u001b[39m():\n\u001b[1;32m----> 2\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[43mx\u001b[49m)\n" + - "\u001b[1;31mNameError\u001b[0m: name 'x' is not defined" + name: 'NameError', + message: `name 'x' is not defined`, + stack: '\u001b[1;31m---------------------------------------------------------------------------\u001b[0m' + + '\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)' + + 'Cell \u001b[1;32mIn[2], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mmyfunc\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n' + + 'Cell \u001b[1;32mIn[1], line 2\u001b[0m, in \u001b[0;36mmyfunc\u001b[1;34m()\u001b[0m\n\u001b[0;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmyfunc\u001b[39m():\n\u001b[1;32m----> 2\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[43mx\u001b[49m)\n' + + `\u001b[1;31mNameError\u001b[0m: name 'x' is not defined` }; test(`Should clean up raw IPython error stack traces`, async () => { diff --git a/extensions/notebook-renderers/tsconfig.json b/extensions/notebook-renderers/tsconfig.json index 3472d5bbaa784..07c5ef470f51e 100644 --- a/extensions/notebook-renderers/tsconfig.json +++ b/extensions/notebook-renderers/tsconfig.json @@ -3,7 +3,14 @@ "compilerOptions": { "outDir": "./out", "lib": [ + "es2024", "dom" + ], + "types": [ + "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/npm/extension-browser.webpack.config.js b/extensions/npm/extension-browser.webpack.config.js index ec1313ebf260d..6ec242a87a221 100644 --- a/extensions/npm/extension-browser.webpack.config.js +++ b/extensions/npm/extension-browser.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; const config = withBrowserDefaults({ - context: __dirname, + context: import.meta.dirname, entry: { extension: './src/npmBrowserMain.ts' }, @@ -24,4 +20,4 @@ const config = withBrowserDefaults({ } }); -module.exports = config; +export default config; diff --git a/extensions/npm/extension.webpack.config.js b/extensions/npm/extension.webpack.config.js index 320956abe3d09..0dcad6f8b143f 100644 --- a/extensions/npm/extension.webpack.config.js +++ b/extensions/npm/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { extension: './src/npmMain.ts', }, diff --git a/extensions/npm/package-lock.json b/extensions/npm/package-lock.json index 352ee31ae9f08..694e98b5e1213 100644 --- a/extensions/npm/package-lock.json +++ b/extensions/npm/package-lock.json @@ -150,9 +150,10 @@ } }, "node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" diff --git a/extensions/npm/src/features/bowerJSONContribution.ts b/extensions/npm/src/features/bowerJSONContribution.ts deleted file mode 100644 index c7a1f38095441..0000000000000 --- a/extensions/npm/src/features/bowerJSONContribution.ts +++ /dev/null @@ -1,208 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { MarkdownString, CompletionItemKind, CompletionItem, DocumentSelector, SnippetString, workspace, Uri, l10n } from 'vscode'; -import { IJSONContribution, ISuggestionsCollector } from './jsonContributions'; -import { XHRRequest } from 'request-light'; -import { Location } from 'jsonc-parser'; - - -const USER_AGENT = 'Visual Studio Code'; - -export class BowerJSONContribution implements IJSONContribution { - - private topRanked = ['twitter', 'bootstrap', 'angular-1.1.6', 'angular-latest', 'angulerjs', 'd3', 'myjquery', 'jq', 'abcdef1234567890', 'jQuery', 'jquery-1.11.1', 'jquery', - 'sushi-vanilla-x-data', 'font-awsome', 'Font-Awesome', 'font-awesome', 'fontawesome', 'html5-boilerplate', 'impress.js', 'homebrew', - 'backbone', 'moment1', 'momentjs', 'moment', 'linux', 'animate.css', 'animate-css', 'reveal.js', 'jquery-file-upload', 'blueimp-file-upload', 'threejs', 'express', 'chosen', - 'normalize-css', 'normalize.css', 'semantic', 'semantic-ui', 'Semantic-UI', 'modernizr', 'underscore', 'underscore1', - 'material-design-icons', 'ionic', 'chartjs', 'Chart.js', 'nnnick-chartjs', 'select2-ng', 'select2-dist', 'phantom', 'skrollr', 'scrollr', 'less.js', 'leancss', 'parser-lib', - 'hui', 'bootstrap-languages', 'async', 'gulp', 'jquery-pjax', 'coffeescript', 'hammer.js', 'ace', 'leaflet', 'jquery-mobile', 'sweetalert', 'typeahead.js', 'soup', 'typehead.js', - 'sails', 'codeigniter2']; - - private xhr: XHRRequest; - - public constructor(xhr: XHRRequest) { - this.xhr = xhr; - } - - public getDocumentSelector(): DocumentSelector { - return [{ language: 'json', scheme: '*', pattern: '**/bower.json' }, { language: 'json', scheme: '*', pattern: '**/.bower.json' }]; - } - - private isEnabled() { - return !!workspace.getConfiguration('npm').get('fetchOnlinePackageInfo'); - } - - public collectDefaultSuggestions(_resource: Uri, collector: ISuggestionsCollector): Thenable { - const defaultValue = { - 'name': '${1:name}', - 'description': '${2:description}', - 'authors': ['${3:author}'], - 'version': '${4:1.0.0}', - 'main': '${5:pathToMain}', - 'dependencies': {} - }; - const proposal = new CompletionItem(l10n.t("Default bower.json")); - proposal.kind = CompletionItemKind.Class; - proposal.insertText = new SnippetString(JSON.stringify(defaultValue, null, '\t')); - collector.add(proposal); - return Promise.resolve(null); - } - - public collectPropertySuggestions(_resource: Uri, location: Location, currentWord: string, addValue: boolean, isLast: boolean, collector: ISuggestionsCollector): Thenable | null { - if (!this.isEnabled()) { - return null; - } - if ((location.matches(['dependencies']) || location.matches(['devDependencies']))) { - if (currentWord.length > 0) { - const queryUrl = 'https://registry.bower.io/packages/search/' + encodeURIComponent(currentWord); - - return this.xhr({ - url: queryUrl, - headers: { agent: USER_AGENT } - }).then((success) => { - if (success.status === 200) { - try { - const obj = JSON.parse(success.responseText); - if (Array.isArray(obj)) { - const results = <{ name: string; description: string }[]>obj; - for (const result of results) { - const name = result.name; - const description = result.description || ''; - const insertText = new SnippetString().appendText(JSON.stringify(name)); - if (addValue) { - insertText.appendText(': ').appendPlaceholder('latest'); - if (!isLast) { - insertText.appendText(','); - } - } - const proposal = new CompletionItem(name); - proposal.kind = CompletionItemKind.Property; - proposal.insertText = insertText; - proposal.filterText = JSON.stringify(name); - proposal.documentation = description; - collector.add(proposal); - } - collector.setAsIncomplete(); - } - } catch (e) { - // ignore - } - } else { - collector.error(l10n.t("Request to the bower repository failed: {0}", success.responseText)); - return 0; - } - return undefined; - }, (error) => { - collector.error(l10n.t("Request to the bower repository failed: {0}", error.responseText)); - return 0; - }); - } else { - this.topRanked.forEach((name) => { - const insertText = new SnippetString().appendText(JSON.stringify(name)); - if (addValue) { - insertText.appendText(': ').appendPlaceholder('latest'); - if (!isLast) { - insertText.appendText(','); - } - } - - const proposal = new CompletionItem(name); - proposal.kind = CompletionItemKind.Property; - proposal.insertText = insertText; - proposal.filterText = JSON.stringify(name); - proposal.documentation = ''; - collector.add(proposal); - }); - collector.setAsIncomplete(); - return Promise.resolve(null); - } - } - return null; - } - - public collectValueSuggestions(_resource: Uri, location: Location, collector: ISuggestionsCollector): Promise | null { - if (!this.isEnabled()) { - return null; - } - if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']))) { - // not implemented. Could be do done calling the bower command. Waiting for web API: https://github.com/bower/registry/issues/26 - const proposal = new CompletionItem(l10n.t("latest")); - proposal.insertText = new SnippetString('"${1:latest}"'); - proposal.filterText = '""'; - proposal.kind = CompletionItemKind.Value; - proposal.documentation = 'The latest version of the package'; - collector.add(proposal); - } - return null; - } - - public resolveSuggestion(_resource: Uri | undefined, item: CompletionItem): Thenable | null { - if (item.kind === CompletionItemKind.Property && item.documentation === '') { - - let label = item.label; - if (typeof label !== 'string') { - label = label.label; - } - - return this.getInfo(label).then(documentation => { - if (documentation) { - item.documentation = documentation; - return item; - } - return null; - }); - } - return null; - } - - private getInfo(pack: string): Thenable { - const queryUrl = 'https://registry.bower.io/packages/' + encodeURIComponent(pack); - - return this.xhr({ - url: queryUrl, - headers: { agent: USER_AGENT } - }).then((success) => { - try { - const obj = JSON.parse(success.responseText); - if (obj && obj.url) { - let url: string = obj.url; - if (url.indexOf('git://') === 0) { - url = url.substring(6); - } - if (url.length >= 4 && url.substr(url.length - 4) === '.git') { - url = url.substring(0, url.length - 4); - } - return url; - } - } catch (e) { - // ignore - } - return undefined; - }, () => { - return undefined; - }); - } - - public getInfoContribution(_resource: Uri, location: Location): Thenable | null { - if (!this.isEnabled()) { - return null; - } - if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']))) { - const pack = location.path[location.path.length - 1]; - if (typeof pack === 'string') { - return this.getInfo(pack).then(documentation => { - if (documentation) { - const str = new MarkdownString(); - str.appendText(documentation); - return [str]; - } - return null; - }); - } - } - return null; - } -} diff --git a/extensions/npm/src/features/jsonContributions.ts b/extensions/npm/src/features/jsonContributions.ts index cf65a5b0851c4..88b6f3c1e2ac6 100644 --- a/extensions/npm/src/features/jsonContributions.ts +++ b/extensions/npm/src/features/jsonContributions.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { Location, getLocation, createScanner, SyntaxKind, ScanError, JSONScanner } from 'jsonc-parser'; -import { BowerJSONContribution } from './bowerJSONContribution'; import { PackageJSONContribution } from './packageJSONContribution'; import { XHRRequest } from 'request-light'; @@ -30,7 +29,7 @@ export interface IJSONContribution { } export function addJSONProviders(xhr: XHRRequest, npmCommandPath: string | undefined): Disposable { - const contributions = [new PackageJSONContribution(xhr, npmCommandPath), new BowerJSONContribution(xhr)]; + const contributions = [new PackageJSONContribution(xhr, npmCommandPath)]; const subscriptions: Disposable[] = []; contributions.forEach(contribution => { const selector = contribution.getDocumentSelector(); diff --git a/extensions/npm/src/tasks.ts b/extensions/npm/src/tasks.ts index 19a45488c0775..ba833705cb4d7 100644 --- a/extensions/npm/src/tasks.ts +++ b/extensions/npm/src/tasks.ts @@ -58,9 +58,9 @@ export class NpmTaskProvider implements TaskProvider { } public async resolveTask(_task: Task): Promise { - const npmTask = (_task.definition).script; + const npmTask = _task.definition.script; if (npmTask) { - const kind: INpmTaskDefinition = (_task.definition); + const kind = _task.definition as INpmTaskDefinition; let packageJsonUri: Uri; if (_task.scope === undefined || _task.scope === TaskScope.Global || _task.scope === TaskScope.Workspace) { // scope is required to be a WorkspaceFolder for resolveTask diff --git a/extensions/npm/tsconfig.json b/extensions/npm/tsconfig.json index ec12eb547b386..0c84fcc94e320 100644 --- a/extensions/npm/tsconfig.json +++ b/extensions/npm/tsconfig.json @@ -4,6 +4,9 @@ "outDir": "./out", "types": [ "node" + ], + "typeRoots": [ + "./node_modules/@types" ] }, "include": [ diff --git a/extensions/package-lock.json b/extensions/package-lock.json index 0eaa987965d67..d315b71a7fefe 100644 --- a/extensions/package-lock.json +++ b/extensions/package-lock.json @@ -10,10 +10,10 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "typescript": "^5.9.2" + "typescript": "^5.9.3" }, "devDependencies": { - "@parcel/watcher": "2.5.1", + "@vscode/watcher": "bpasero/watcher#8ecffb4a57df24ac3e6946aa095b9b1f14f8bba9", "esbuild": "0.25.0", "vscode-grammar-updater": "^1.1.0" } @@ -443,15 +443,15 @@ "node": ">=18" } }, - "node_modules/@parcel/watcher": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "node_modules/@vscode/watcher": { + "version": "2.5.1-vscode", + "resolved": "git+ssh://git@github.com/bpasero/watcher.git#8ecffb4a57df24ac3e6946aa095b9b1f14f8bba9", + "integrity": "sha512-7F4REbtMh5JAtdPpBCyPq7yLgcqnZV5L+uzuT4IDaZUyCKvIqi9gDiNPyoKpvCtrw6funLmrAncFHHWoDI+S4g==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" @@ -459,294 +459,6 @@ "engines": { "node": ">= 10.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" - } - }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", - "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", - "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", - "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", - "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", - "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", - "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", - "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", - "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", - "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", - "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", - "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", - "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", - "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" @@ -790,16 +502,13 @@ } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", "dev": true, "license": "Apache-2.0", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/esbuild": { @@ -940,9 +649,9 @@ } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/extensions/package.json b/extensions/package.json index f0d7f2818de6a..28f88ed4db356 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,13 +4,13 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "^5.9.2" + "typescript": "^5.9.3" }, "scripts": { "postinstall": "node ./postinstall.mjs" }, "devDependencies": { - "@parcel/watcher": "2.5.1", + "@vscode/watcher": "bpasero/watcher#8ecffb4a57df24ac3e6946aa095b9b1f14f8bba9", "esbuild": "0.25.0", "vscode-grammar-updater": "^1.1.0" }, diff --git a/extensions/php-language-features/extension.webpack.config.js b/extensions/php-language-features/extension.webpack.config.js index a44fc8e56be85..81e71e442ec5a 100644 --- a/extensions/php-language-features/extension.webpack.config.js +++ b/extensions/php-language-features/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { extension: './src/phpMain.ts', }, diff --git a/extensions/php-language-features/tsconfig.json b/extensions/php-language-features/tsconfig.json index 7234fdfeb9757..29a69e99d9878 100644 --- a/extensions/php-language-features/tsconfig.json +++ b/extensions/php-language-features/tsconfig.json @@ -4,7 +4,10 @@ "outDir": "./out", "types": [ "node" - ] + ], + "typeRoots": [ + "./node_modules/@types" + ], }, "include": [ "src/**/*", diff --git a/extensions/php/language-configuration.json b/extensions/php/language-configuration.json index 4d9a3238d4077..c50f3f997e37a 100644 --- a/extensions/php/language-configuration.json +++ b/extensions/php/language-configuration.json @@ -105,8 +105,8 @@ }, "folding": { "markers": { - "start": "^\\s*(#|\/\/)region\\b", - "end": "^\\s*(#|\/\/)endregion\\b" + "start": "^\\s*(#|\/\/|\/\/ #)region\\b", + "end": "^\\s*(#|\/\/|\/\/ #)endregion\\b" } }, "wordPattern": "(-?\\d*\\.\\d\\w*)|([^\\-\\`\\~\\!\\@\\#\\%\\^\\&\\*\\(\\)\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>\\/\\?\\s]+)", diff --git a/extensions/prompt-basics/package.json b/extensions/prompt-basics/package.json index 91ea0c081b0dd..f1d4ee98b298e 100644 --- a/extensions/prompt-basics/package.json +++ b/extensions/prompt-basics/package.json @@ -20,8 +20,7 @@ "prompt" ], "extensions": [ - ".prompt.md", - "copilot-instructions.md" + ".prompt.md" ], "configuration": "./language-configuration.json" }, @@ -38,14 +37,18 @@ "configuration": "./language-configuration.json" }, { - "id": "chatmode", + "id": "chatagent", "aliases": [ - "Chat Mode", - "chat mode" + "Agent", + "chat agent" ], "extensions": [ + ".agent.md", ".chatmode.md" ], + "filenamePatterns": [ + "**/.github/agents/*.md" + ], "configuration": "./language-configuration.json" } ], @@ -69,7 +72,7 @@ ] }, { - "language": "chatmode", + "language": "chatagent", "path": "./syntaxes/prompt.tmLanguage.json", "scopeName": "text.html.markdown.prompt", "unbalancedBracketScopes": [ @@ -80,37 +83,49 @@ ], "configurationDefaults": { "[prompt]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.autoIndent": "advanced", "editor.unicodeHighlight.ambiguousCharacters": false, "editor.unicodeHighlight.invisibleCharacters": false, "diffEditor.ignoreTrimWhitespace": false, "editor.wordWrap": "on", "editor.quickSuggestions": { "comments": "off", - "strings": "off", - "other": "off" - } + "strings": "on", + "other": "on" + }, + "editor.wordBasedSuggestions": "off" }, "[instructions]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.autoIndent": "advanced", "editor.unicodeHighlight.ambiguousCharacters": false, "editor.unicodeHighlight.invisibleCharacters": false, "diffEditor.ignoreTrimWhitespace": false, "editor.wordWrap": "on", "editor.quickSuggestions": { "comments": "off", - "strings": "off", - "other": "off" - } + "strings": "on", + "other": "on" + }, + "editor.wordBasedSuggestions": "off" }, - "[chatmode]": { + "[chatagent]": { + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.autoIndent": "advanced", "editor.unicodeHighlight.ambiguousCharacters": false, "editor.unicodeHighlight.invisibleCharacters": false, "diffEditor.ignoreTrimWhitespace": false, "editor.wordWrap": "on", "editor.quickSuggestions": { "comments": "off", - "strings": "off", - "other": "off" - } + "strings": "on", + "other": "on" + }, + "editor.wordBasedSuggestions": "off" } } }, diff --git a/extensions/razor/.vscodeignore b/extensions/razor/.vscodeignore index 0a622e7e30046..2ff7df8f44cff 100644 --- a/extensions/razor/.vscodeignore +++ b/extensions/razor/.vscodeignore @@ -1,2 +1,3 @@ test/** cgmanifest.json +build/** diff --git a/extensions/references-view/extension-browser.webpack.config.js b/extensions/references-view/extension-browser.webpack.config.js index 10c0a19e356c8..1e0caad7e7297 100644 --- a/extensions/references-view/extension-browser.webpack.config.js +++ b/extensions/references-view/extension-browser.webpack.config.js @@ -2,21 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; +import path from 'path'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; -const path = require('path'); - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, entry: { extension: './src/extension.ts' }, output: { filename: 'extension.js', - path: path.join(__dirname, 'dist') + path: path.join(import.meta.dirname, 'dist') } }); diff --git a/extensions/references-view/extension.webpack.config.js b/extensions/references-view/extension.webpack.config.js index de88398eca0d3..4928186ae556c 100644 --- a/extensions/references-view/extension.webpack.config.js +++ b/extensions/references-view/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, resolve: { mainFields: ['module', 'main'] }, diff --git a/extensions/references-view/package.json b/extensions/references-view/package.json index 62c9e29e0c6cd..5f2714589c77a 100644 --- a/extensions/references-view/package.json +++ b/extensions/references-view/package.json @@ -129,13 +129,13 @@ "command": "references-view.showOutgoingCalls", "title": "%cmd.references-view.showOutgoingCalls%", "category": "Calls", - "icon": "$(call-outgoing)" + "icon": "$(call-incoming)" }, { "command": "references-view.showIncomingCalls", "title": "%cmd.references-view.showIncomingCalls%", "category": "Calls", - "icon": "$(call-incoming)" + "icon": "$(call-outgoing)" }, { "command": "references-view.removeCallItem", diff --git a/extensions/references-view/tsconfig.json b/extensions/references-view/tsconfig.json index f0f7c00adf58b..796a159a61c21 100644 --- a/extensions/references-view/tsconfig.json +++ b/extensions/references-view/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/rust/.vscodeignore b/extensions/rust/.vscodeignore index 0a622e7e30046..2ff7df8f44cff 100644 --- a/extensions/rust/.vscodeignore +++ b/extensions/rust/.vscodeignore @@ -1,2 +1,3 @@ test/** cgmanifest.json +build/** diff --git a/extensions/search-result/extension-browser.webpack.config.js b/extensions/search-result/extension-browser.webpack.config.js index 10c0a19e356c8..1e0caad7e7297 100644 --- a/extensions/search-result/extension-browser.webpack.config.js +++ b/extensions/search-result/extension-browser.webpack.config.js @@ -2,21 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; +import path from 'path'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; -const path = require('path'); - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, entry: { extension: './src/extension.ts' }, output: { filename: 'extension.js', - path: path.join(__dirname, 'dist') + path: path.join(import.meta.dirname, 'dist') } }); diff --git a/extensions/search-result/extension.webpack.config.js b/extensions/search-result/extension.webpack.config.js index de88398eca0d3..4928186ae556c 100644 --- a/extensions/search-result/extension.webpack.config.js +++ b/extensions/search-result/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, resolve: { mainFields: ['module', 'main'] }, diff --git a/extensions/search-result/package-lock.json b/extensions/search-result/package-lock.json index 4fbe8b97ef8db..f85d9e265e03e 100644 --- a/extensions/search-result/package-lock.json +++ b/extensions/search-result/package-lock.json @@ -8,9 +8,29 @@ "name": "search-result", "version": "1.0.0", "license": "MIT", + "devDependencies": { + "@types/node": "^22.18.10" + }, "engines": { "vscode": "^1.39.0" } + }, + "node_modules/@types/node": { + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "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" } } } diff --git a/extensions/search-result/package.json b/extensions/search-result/package.json index 6582db3e78242..1119636313f67 100644 --- a/extensions/search-result/package.json +++ b/extensions/search-result/package.json @@ -16,7 +16,7 @@ ], "scripts": { "generate-grammar": "node ./syntaxes/generateTMLanguage.js", - "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:search-result ./tsconfig.json" + "vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:search-result ./tsconfig.json" }, "capabilities": { "virtualWorkspaces": true, @@ -55,5 +55,8 @@ "repository": { "type": "git", "url": "https://github.com/microsoft/vscode.git" + }, + "devDependencies": { + "@types/node": "^22.18.10" } } diff --git a/extensions/search-result/tsconfig.json b/extensions/search-result/tsconfig.json index f0f7c00adf58b..796a159a61c21 100644 --- a/extensions/search-result/tsconfig.json +++ b/extensions/search-result/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/shared.webpack.config.js b/extensions/shared.webpack.config.js deleted file mode 100644 index ad9d70c249087..0000000000000 --- a/extensions/shared.webpack.config.js +++ /dev/null @@ -1,207 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -//@ts-check -/** @typedef {import('webpack').Configuration} WebpackConfig **/ - -'use strict'; - -const path = require('path'); -const fs = require('fs'); -const merge = require('merge-options'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const { DefinePlugin, optimize } = require('webpack'); - -const tsLoaderOptions = { - compilerOptions: { - 'sourceMap': true, - }, - onlyCompileBundledFiles: true, -}; - -function withNodeDefaults(/**@type WebpackConfig & { context: string }*/extConfig) { - const defaultConfig = { - mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') - target: 'node', // extensions run in a node context - node: { - __dirname: false // leave the __dirname-behaviour intact - }, - - resolve: { - conditionNames: ['import', 'require', 'node-addons', 'node'], - mainFields: ['module', 'main'], - extensions: ['.ts', '.js'], // support ts-files and js-files - extensionAlias: { - // this is needed to resolve dynamic imports that now require the .js extension - '.js': ['.js', '.ts'], - } - }, - module: { - rules: [{ - test: /\.ts$/, - exclude: /node_modules/, - use: [{ - // configure TypeScript loader: - // * enable sources maps for end-to-end source maps - loader: 'ts-loader', - options: tsLoaderOptions - }, { - loader: path.resolve(__dirname, 'mangle-loader.js'), - options: { - configFile: path.join(extConfig.context, 'tsconfig.json') - }, - },] - }] - }, - externals: { - 'electron': 'commonjs electron', // ignored to avoid bundling from node_modules - 'vscode': 'commonjs vscode', // ignored because it doesn't exist, - 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', // ignored because we don't ship native module - '@azure/functions-core': 'commonjs azure/functions-core', // optioinal dependency of appinsights that we don't use - '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing', // ignored because we don't ship this module - '@opentelemetry/instrumentation': 'commonjs @opentelemetry/instrumentation', // ignored because we don't ship this module - '@azure/opentelemetry-instrumentation-azure-sdk': 'commonjs @azure/opentelemetry-instrumentation-azure-sdk', // ignored because we don't ship this module - }, - output: { - // all output goes into `dist`. - // packaging depends on that and this must always be like it - filename: '[name].js', - path: path.join(extConfig.context, 'dist'), - libraryTarget: 'commonjs', - }, - // yes, really source maps - devtool: 'source-map', - plugins: nodePlugins(extConfig.context), - }; - - return merge(defaultConfig, extConfig); -} - -/** - * - * @param {string} context - */ -function nodePlugins(context) { - // Need to find the top-most `package.json` file - const folderName = path.relative(__dirname, context).split(/[\\\/]/)[0]; - const pkgPath = path.join(__dirname, folderName, 'package.json'); - const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - const id = `${pkg.publisher}.${pkg.name}`; - return [ - new CopyWebpackPlugin({ - patterns: [ - { from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true } - ] - }) - ]; -} -/** - * @typedef {{ - * configFile?: string - * }} AdditionalBrowserConfig - */ - -function withBrowserDefaults(/**@type WebpackConfig & { context: string }*/extConfig, /** @type AdditionalBrowserConfig */ additionalOptions = {}) { - /** @type WebpackConfig */ - const defaultConfig = { - mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') - target: 'webworker', // extensions run in a webworker context - resolve: { - mainFields: ['browser', 'module', 'main'], - extensions: ['.ts', '.js'], // support ts-files and js-files - fallback: { - 'path': require.resolve('path-browserify'), - 'os': require.resolve('os-browserify'), - 'util': require.resolve('util') - }, - extensionAlias: { - // this is needed to resolve dynamic imports that now require the .js extension - '.js': ['.js', '.ts'], - }, - }, - module: { - rules: [{ - test: /\.ts$/, - exclude: /node_modules/, - use: [ - { - // configure TypeScript loader: - // * enable sources maps for end-to-end source maps - loader: 'ts-loader', - options: { - ...tsLoaderOptions, - // ...(additionalOptions ? {} : { configFile: additionalOptions.configFile }), - } - }, - { - loader: path.resolve(__dirname, 'mangle-loader.js'), - options: { - configFile: path.join(extConfig.context, additionalOptions?.configFile ?? 'tsconfig.json') - }, - }, - ] - }, { - test: /\.wasm$/, - type: 'asset/inline' - }] - }, - externals: { - 'vscode': 'commonjs vscode', // ignored because it doesn't exist, - 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', // ignored because we don't ship native module - '@azure/functions-core': 'commonjs azure/functions-core', // optioinal dependency of appinsights that we don't use - '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing', // ignored because we don't ship this module - '@opentelemetry/instrumentation': 'commonjs @opentelemetry/instrumentation', // ignored because we don't ship this module - '@azure/opentelemetry-instrumentation-azure-sdk': 'commonjs @azure/opentelemetry-instrumentation-azure-sdk', // ignored because we don't ship this module - }, - performance: { - hints: false - }, - output: { - // all output goes into `dist`. - // packaging depends on that and this must always be like it - filename: '[name].js', - path: path.join(extConfig.context, 'dist', 'browser'), - libraryTarget: 'commonjs', - }, - // yes, really source maps - devtool: 'source-map', - plugins: browserPlugins(extConfig.context) - }; - - return merge(defaultConfig, extConfig); -} - -/** - * - * @param {string} context - */ -function browserPlugins(context) { - // Need to find the top-most `package.json` file - // const folderName = path.relative(__dirname, context).split(/[\\\/]/)[0]; - // const pkgPath = path.join(__dirname, folderName, 'package.json'); - // const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); - // const id = `${pkg.publisher}.${pkg.name}`; - return [ - new optimize.LimitChunkCountPlugin({ - maxChunks: 1 - }), - new CopyWebpackPlugin({ - patterns: [ - { from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true } - ] - }), - new DefinePlugin({ - 'process.platform': JSON.stringify('web'), - 'process.env': JSON.stringify({}), - 'process.env.BROWSER_ENV': JSON.stringify('true') - }) - ]; -} - -module.exports = withNodeDefaults; -module.exports.node = withNodeDefaults; -module.exports.browser = withBrowserDefaults; -module.exports.nodePlugins = nodePlugins; -module.exports.browserPlugins = browserPlugins; diff --git a/extensions/shared.webpack.config.mjs b/extensions/shared.webpack.config.mjs new file mode 100644 index 0000000000000..12b1ea522a4ad --- /dev/null +++ b/extensions/shared.webpack.config.mjs @@ -0,0 +1,209 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import path from 'node:path'; +import fs from 'node:fs'; +import merge from 'merge-options'; +import CopyWebpackPlugin from 'copy-webpack-plugin'; +import webpack from 'webpack'; +import { createRequire } from 'node:module'; + +/** @typedef {import('webpack').Configuration} WebpackConfig **/ + +const require = createRequire(import.meta.url); + +const tsLoaderOptions = { + compilerOptions: { + 'sourceMap': true, + }, + onlyCompileBundledFiles: true, +}; + +function withNodeDefaults(/**@type WebpackConfig & { context: string }*/extConfig) { + const defaultConfig = { + mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') + target: 'node', // extensions run in a node context + node: { + __dirname: false // leave the __dirname-behaviour intact + }, + + resolve: { + conditionNames: ['import', 'require', 'node-addons', 'node'], + mainFields: ['module', 'main'], + extensions: ['.ts', '.js'], // support ts-files and js-files + extensionAlias: { + // this is needed to resolve dynamic imports that now require the .js extension + '.js': ['.js', '.ts'], + } + }, + module: { + rules: [{ + test: /\.ts$/, + exclude: /node_modules/, + use: [ + { + // configure TypeScript loader: + // * enable sources maps for end-to-end source maps + loader: 'ts-loader', + options: tsLoaderOptions + }, + // disable mangling for now, SEE https://github.com/microsoft/vscode/issues/204692 + // { + // loader: path.resolve(import.meta.dirname, 'mangle-loader.js'), + // options: { + // configFile: path.join(extConfig.context, 'tsconfig.json') + // }, + // }, + ] + }] + }, + externals: { + 'electron': 'commonjs electron', // ignored to avoid bundling from node_modules + 'vscode': 'commonjs vscode', // ignored because it doesn't exist, + 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', // ignored because we don't ship native module + '@azure/functions-core': 'commonjs azure/functions-core', // optional dependency of appinsights that we don't use + '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing', // ignored because we don't ship this module + '@opentelemetry/instrumentation': 'commonjs @opentelemetry/instrumentation', // ignored because we don't ship this module + '@azure/opentelemetry-instrumentation-azure-sdk': 'commonjs @azure/opentelemetry-instrumentation-azure-sdk', // ignored because we don't ship this module + }, + output: { + // all output goes into `dist`. + // packaging depends on that and this must always be like it + filename: '[name].js', + path: path.join(extConfig.context, 'dist'), + libraryTarget: 'commonjs', + }, + // yes, really source maps + devtool: 'source-map', + plugins: nodePlugins(extConfig.context), + }; + + return merge(defaultConfig, extConfig); +} + +/** + * + * @param {string} context + */ +function nodePlugins(context) { + // Need to find the top-most `package.json` file + const folderName = path.relative(import.meta.dirname, context).split(/[\\\/]/)[0]; + const pkgPath = path.join(import.meta.dirname, folderName, 'package.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + const id = `${pkg.publisher}.${pkg.name}`; + return [ + new CopyWebpackPlugin({ + patterns: [ + { from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true } + ] + }) + ]; +} +/** + * @typedef {{ + * configFile?: string + * }} AdditionalBrowserConfig + */ + +function withBrowserDefaults(/**@type WebpackConfig & { context: string }*/extConfig, /** @type AdditionalBrowserConfig */ additionalOptions = {}) { + /** @type WebpackConfig */ + const defaultConfig = { + mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') + target: 'webworker', // extensions run in a webworker context + resolve: { + mainFields: ['browser', 'module', 'main'], + extensions: ['.ts', '.js'], // support ts-files and js-files + fallback: { + 'path': require.resolve('path-browserify'), + 'os': require.resolve('os-browserify'), + 'util': require.resolve('util') + }, + extensionAlias: { + // this is needed to resolve dynamic imports that now require the .js extension + '.js': ['.js', '.ts'], + }, + }, + module: { + rules: [{ + test: /\.ts$/, + exclude: /node_modules/, + use: [ + { + // configure TypeScript loader: + // * enable sources maps for end-to-end source maps + loader: 'ts-loader', + options: { + ...tsLoaderOptions, + // ...(additionalOptions ? {} : { configFile: additionalOptions.configFile }), + } + }, + // disable mangling for now, SEE https://github.com/microsoft/vscode/issues/204692 + // { + // loader: path.resolve(import.meta.dirname, 'mangle-loader.js'), + // options: { + // configFile: path.join(extConfig.context, additionalOptions?.configFile ?? 'tsconfig.json') + // }, + // }, + ] + }, { + test: /\.wasm$/, + type: 'asset/inline' + }] + }, + externals: { + 'vscode': 'commonjs vscode', // ignored because it doesn't exist, + 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', // ignored because we don't ship native module + '@azure/functions-core': 'commonjs azure/functions-core', // optional dependency of appinsights that we don't use + '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing', // ignored because we don't ship this module + '@opentelemetry/instrumentation': 'commonjs @opentelemetry/instrumentation', // ignored because we don't ship this module + '@azure/opentelemetry-instrumentation-azure-sdk': 'commonjs @azure/opentelemetry-instrumentation-azure-sdk', // ignored because we don't ship this module + }, + performance: { + hints: false + }, + output: { + // all output goes into `dist`. + // packaging depends on that and this must always be like it + filename: '[name].js', + path: path.join(extConfig.context, 'dist', 'browser'), + libraryTarget: 'commonjs', + }, + // yes, really source maps + devtool: 'source-map', + plugins: browserPlugins(extConfig.context) + }; + + return merge(defaultConfig, extConfig); +} + +/** + * + * @param {string} context + */ +function browserPlugins(context) { + // Need to find the top-most `package.json` file + // const folderName = path.relative(__dirname, context).split(/[\\\/]/)[0]; + // const pkgPath = path.join(__dirname, folderName, 'package.json'); + // const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); + // const id = `${pkg.publisher}.${pkg.name}`; + return [ + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1 + }), + new CopyWebpackPlugin({ + patterns: [ + { from: 'src', to: '.', globOptions: { ignore: ['**/test/**', '**/*.ts'] }, noErrorOnMissing: true } + ] + }), + new webpack.DefinePlugin({ + 'process.platform': JSON.stringify('web'), + 'process.env': JSON.stringify({}), + 'process.env.BROWSER_ENV': JSON.stringify('true') + }) + ]; +} + +export default withNodeDefaults; +export { withNodeDefaults as node, withBrowserDefaults as browser, nodePlugins, browserPlugins }; diff --git a/extensions/shellscript/package.json b/extensions/shellscript/package.json index ab9be7b29ad11..9cad54150bbff 100644 --- a/extensions/shellscript/package.json +++ b/extensions/shellscript/package.json @@ -69,9 +69,6 @@ "bashrc_Apple_Terminal", "zshrc_Apple_Terminal" ], - "filenamePatterns": [ - ".env.*" - ], "firstLine": "^#!.*\\b(bash|fish|zsh|sh|ksh|dtksh|pdksh|mksh|ash|dash|yash|sh|csh|jcsh|tcsh|itcsh).*|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", "configuration": "./language-configuration.json", "mimetypes": [ diff --git a/extensions/simple-browser/.vscodeignore b/extensions/simple-browser/.vscodeignore index c69acedcc2473..ef5dc0365fa33 100644 --- a/extensions/simple-browser/.vscodeignore +++ b/extensions/simple-browser/.vscodeignore @@ -11,4 +11,4 @@ cgmanifest.json package-lock.json preview-src/** webpack.config.js -esbuild-preview.js +esbuild-* diff --git a/extensions/simple-browser/esbuild-preview.js b/extensions/simple-browser/esbuild-preview.js deleted file mode 100644 index 9c94a67d56ffe..0000000000000 --- a/extensions/simple-browser/esbuild-preview.js +++ /dev/null @@ -1,23 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check -const path = require('path'); - -const srcDir = path.join(__dirname, 'preview-src'); -const outDir = path.join(__dirname, 'media'); - -require('../esbuild-webview-common').run({ - entryPoints: { - 'index': path.join(srcDir, 'index.ts'), - 'codicon': path.join(__dirname, 'node_modules', '@vscode', 'codicons', 'dist', 'codicon.css'), - }, - srcDir, - outdir: outDir, - additionalOptions: { - loader: { - '.ttf': 'dataurl', - } - } -}, process.argv); diff --git a/extensions/simple-browser/esbuild-preview.mjs b/extensions/simple-browser/esbuild-preview.mjs new file mode 100644 index 0000000000000..3ce58360a30d2 --- /dev/null +++ b/extensions/simple-browser/esbuild-preview.mjs @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// @ts-check +import path from 'path'; +import { run } from '../esbuild-webview-common.mjs'; + +const srcDir = path.join(import.meta.dirname, 'preview-src'); +const outDir = path.join(import.meta.dirname, 'media'); + +run({ + entryPoints: { + 'index': path.join(srcDir, 'index.ts'), + 'codicon': path.join(import.meta.dirname, 'node_modules', '@vscode', 'codicons', 'dist', 'codicon.css'), + }, + srcDir, + outdir: outDir, + additionalOptions: { + loader: { + '.ttf': 'dataurl', + } + } +}, process.argv); diff --git a/extensions/simple-browser/extension-browser.webpack.config.js b/extensions/simple-browser/extension-browser.webpack.config.js index 7fcc53a728b4f..b758f2d8155a3 100644 --- a/extensions/simple-browser/extension-browser.webpack.config.js +++ b/extensions/simple-browser/extension-browser.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import { browser as withBrowserDefaults } from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withBrowserDefaults = require('../shared.webpack.config').browser; - -module.exports = withBrowserDefaults({ - context: __dirname, +export default withBrowserDefaults({ + context: import.meta.dirname, entry: { extension: './src/extension.ts' } diff --git a/extensions/simple-browser/extension.webpack.config.js b/extensions/simple-browser/extension.webpack.config.js index de88398eca0d3..4928186ae556c 100644 --- a/extensions/simple-browser/extension.webpack.config.js +++ b/extensions/simple-browser/extension.webpack.config.js @@ -2,15 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check - -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, resolve: { mainFields: ['module', 'main'] }, diff --git a/extensions/simple-browser/package-lock.json b/extensions/simple-browser/package-lock.json index c6d9b23636a95..8aa3894ba1e97 100644 --- a/extensions/simple-browser/package-lock.json +++ b/extensions/simple-browser/package-lock.json @@ -12,6 +12,7 @@ "@vscode/extension-telemetry": "^0.9.8" }, "devDependencies": { + "@types/node": "22.x", "@types/vscode-webview": "^1.57.0", "@vscode/codicons": "^0.0.36" }, @@ -143,6 +144,16 @@ "integrity": "sha512-OUUJTh3fnaUSzg9DEHgv3d7jC+DnPL65mIO7RaR+jWve7+MmcgIvF79gY97DPQ4frH+IpNR78YAYd/dW4gK3kg==", "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.18.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.13.tgz", + "integrity": "sha512-Bo45YKIjnmFtv6I1TuC8AaHBbqXtIo+Om5fE4QiU1Tj8QR/qt+8O3BAtOimG5IFmwaWiPmB3Mv3jtYzBA4Us2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, "node_modules/@types/vscode-webview": { "version": "1.57.0", "resolved": "https://registry.npmjs.org/@types/vscode-webview/-/vscode-webview-1.57.0.tgz", @@ -169,6 +180,13 @@ "engines": { "vscode": "^1.75.0" } + }, + "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" } } } diff --git a/extensions/simple-browser/package.json b/extensions/simple-browser/package.json index 9aba9ad25036a..79802e7366821 100644 --- a/extensions/simple-browser/package.json +++ b/extensions/simple-browser/package.json @@ -57,11 +57,11 @@ ] }, "scripts": { - "compile": "gulp compile-extension:markdown-language-features && npm run build-preview", - "watch": "npm run build-preview && gulp watch-extension:markdown-language-features", + "compile": "gulp compile-extension:simple-browser && npm run build-preview", + "watch": "npm run build-preview && gulp watch-extension:simple-browser", "vscode:prepublish": "npm run build-ext && npm run build-preview", - "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", - "build-preview": "node ./esbuild-preview", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.mjs compile-extension:simple-browser ./tsconfig.json", + "build-preview": "node ./esbuild-preview.mjs", "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, @@ -69,6 +69,7 @@ "@vscode/extension-telemetry": "^0.9.8" }, "devDependencies": { + "@types/node": "22.x", "@types/vscode-webview": "^1.57.0", "@vscode/codicons": "^0.0.36" }, diff --git a/extensions/simple-browser/preview-src/tsconfig.json b/extensions/simple-browser/preview-src/tsconfig.json index 72282fb0c7d01..e8e5336a66b44 100644 --- a/extensions/simple-browser/preview-src/tsconfig.json +++ b/extensions/simple-browser/preview-src/tsconfig.json @@ -7,6 +7,9 @@ "ES2024", "DOM", "DOM.Iterable" + ], + "typeRoots": [ + "../node_modules/@types" ] } } diff --git a/extensions/simple-browser/src/extension.ts b/extensions/simple-browser/src/extension.ts index 927167a851dac..885afe287121d 100644 --- a/extensions/simple-browser/src/extension.ts +++ b/extensions/simple-browser/src/extension.ts @@ -86,6 +86,5 @@ export function activate(context: vscode.ExtensionContext) { } function isWeb(): boolean { - // @ts-expect-error return !(typeof process === 'object' && !!process.versions.node) && vscode.env.uiKind === vscode.UIKind.Web; } diff --git a/extensions/simple-browser/src/simpleBrowserView.ts b/extensions/simple-browser/src/simpleBrowserView.ts index 5725dcf4f9ba6..56c5aff5c8a52 100644 --- a/extensions/simple-browser/src/simpleBrowserView.ts +++ b/extensions/simple-browser/src/simpleBrowserView.ts @@ -5,6 +5,7 @@ import * as vscode from 'vscode'; import { Disposable } from './dispose'; +import { generateUuid } from './uuid'; export interface ShowOptions { @@ -112,7 +113,7 @@ export class SimpleBrowserView extends Disposable { private getHtml(url: string) { const configuration = vscode.workspace.getConfiguration('simpleBrowser'); - const nonce = getNonce(); + const nonce = generateUuid(); const mainJs = this.extensionResourceUrl('media', 'index.js'); const mainCss = this.extensionResourceUrl('media', 'main.css'); @@ -181,12 +182,3 @@ export class SimpleBrowserView extends Disposable { function escapeAttribute(value: string | vscode.Uri): string { return value.toString().replace(/"/g, '"'); } - -function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 64; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; -} diff --git a/extensions/simple-browser/src/uuid.ts b/extensions/simple-browser/src/uuid.ts new file mode 100644 index 0000000000000..ca420b3b6afad --- /dev/null +++ b/extensions/simple-browser/src/uuid.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Copied from src/vs/base/common/uuid.ts + */ +export function generateUuid(): string { + // use `randomUUID` if possible + if (typeof crypto.randomUUID === 'function') { + // see https://developer.mozilla.org/en-US/docs/Web/API/Window/crypto + // > Although crypto is available on all windows, the returned Crypto object only has one + // > usable feature in insecure contexts: the getRandomValues() method. + // > In general, you should use this API only in secure contexts. + + return crypto.randomUUID.bind(crypto)(); + } + + // prep-work + const _data = new Uint8Array(16); + const _hex: string[] = []; + for (let i = 0; i < 256; i++) { + _hex.push(i.toString(16).padStart(2, '0')); + } + + // get data + crypto.getRandomValues(_data); + + // set version bits + _data[6] = (_data[6] & 0x0f) | 0x40; + _data[8] = (_data[8] & 0x3f) | 0x80; + + // print as string + let i = 0; + let result = ''; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += '-'; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + result += _hex[_data[i++]]; + return result; +} diff --git a/extensions/simple-browser/tsconfig.json b/extensions/simple-browser/tsconfig.json index bd3708266788e..43ed762ce7dab 100644 --- a/extensions/simple-browser/tsconfig.json +++ b/extensions/simple-browser/tsconfig.json @@ -2,7 +2,9 @@ "extends": "../tsconfig.base.json", "compilerOptions": { "outDir": "./out", - "types": [] + "typeRoots": [ + "./node_modules/@types" + ] }, "include": [ "src/**/*", diff --git a/extensions/swift/cgmanifest.json b/extensions/swift/cgmanifest.json index d40d7c7e6e5e9..ecd2705da2a90 100644 --- a/extensions/swift/cgmanifest.json +++ b/extensions/swift/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "jtbandes/swift-tmlanguage", "repositoryUrl": "https://github.com/jtbandes/swift-tmlanguage", - "commitHash": "b8d2889b4af1d8bad41578317a6adade642555a3" + "commitHash": "45ac01d47c6d63402570c2c36bcfbadbd1c7bca6" } }, "license": "MIT" diff --git a/extensions/swift/syntaxes/swift.tmLanguage.json b/extensions/swift/syntaxes/swift.tmLanguage.json index 7d6694cbead32..a8bbe5d00b479 100644 --- a/extensions/swift/syntaxes/swift.tmLanguage.json +++ b/extensions/swift/syntaxes/swift.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/jtbandes/swift-tmlanguage/commit/b8d2889b4af1d8bad41578317a6adade642555a3", + "version": "https://github.com/jtbandes/swift-tmlanguage/commit/45ac01d47c6d63402570c2c36bcfbadbd1c7bca6", "name": "Swift", "scopeName": "source.swift", "comment": "See swift.tmbundle/grammar-test.swift for test cases.", @@ -939,6 +939,17 @@ { "include": "#declarations-available-types" }, + { + "include": "#literals-numeric" + }, + { + "name": "support.variable.inferred.swift", + "match": "\\b_\\b" + }, + { + "name": "keyword.other.inline-array.swift", + "match": "(?<=\\s)\\bof\\b(?=\\s+[\\p{L}_\\d\\p{N}\\p{M}\\[(])" + }, { "begin": ":", "end": "(?=\\]|[>){}])", @@ -980,28 +991,24 @@ }, "declarations-extension": { "name": "meta.definition.type.$1.swift", - "begin": "\\b(extension)\\s+((?`?)[\\p{L}_][\\p{L}_\\p{N}\\p{M}]*(\\k))", + "begin": "\\b(extension)\\s+", "end": "(?<=\\})", "beginCaptures": { "1": { "name": "storage.type.$1.swift" - }, - "2": { + } + }, + "patterns": [ + { "name": "entity.name.type.swift", + "begin": "\\G(?!\\s*[:{\\n])", + "end": "(?=\\s*[:{\\n])|(?!\\G)(?=\\s*where\\b)", "patterns": [ { "include": "#declarations-available-types" } ] }, - "3": { - "name": "punctuation.definition.identifier.swift" - }, - "4": { - "name": "punctuation.definition.identifier.swift" - } - }, - "patterns": [ { "include": "#comments" }, @@ -1158,6 +1165,10 @@ } }, "patterns": [ + { + "name": "storage.modifier.swift", + "match": "\\bsending\\b" + }, { "include": "#declarations-available-types" } @@ -1228,6 +1239,9 @@ } }, "patterns": [ + { + "include": "#literals-numeric" + }, { "include": "#declarations-available-types" } @@ -1255,6 +1269,10 @@ "comment": "Swift 2: constraints inside the generic param list", "include": "#declarations-generic-where-clause" }, + { + "name": "keyword.other.declaration-specifier.swift", + "match": "\\blet\\b" + }, { "name": "keyword.control.loop.swift", "match": "\\beach\\b" @@ -1469,6 +1487,9 @@ "begin": "\\G", "end": "(?!\\G)$|(?=[={}]|(?!\\G)\\bwhere\\b)", "patterns": [ + { + "include": "#attributes" + }, { "include": "#comments" }, @@ -1500,6 +1521,9 @@ "begin": ",\\s*", "end": "(?!\\G)(?!//|/\\*)|(?=[,={}]|(?!\\G)\\bwhere\\b)", "patterns": [ + { + "include": "#attributes" + }, { "include": "#comments" }, @@ -1736,6 +1760,10 @@ "begin": ":\\s*(?!\\s)", "end": "(?=[,)])", "patterns": [ + { + "name": "storage.modifier.swift", + "match": "\\bsending\\b" + }, { "include": "#declarations-available-types" }, @@ -2807,12 +2835,16 @@ }, { "name": "keyword.control.transfer.swift", - "match": "(?\n (?> # no backtracking, avoids issues with negative lookbehind at end\n (?:\n \\\\Q\n (?:(?!\\\\E)(?!/\\2).)*+\n (?:\\\\E\n # A quoted sequence may not have a closing E, in which case it extends to the end of the regex\n | (?(3)|(?(\\{(?:\\g<-1>|(?!{).*?)\\}))\n (?:\\[(?!\\d)\\w+\\])?\n [X<>]?\n \\)\n | (?\\[ (?:\\\\. | [^\\[\\]] | \\g)+ \\])\n | \\(\\g?+\\)\n | (?:(?!/\\2)[^()\\[\\\\])+ # any character (until end)\n )+\n )\n)?+\n# may end with a space only if it is an extended literal or contains only a single escaped space\n(?(3)|(?(5)(?\n (?> # no backtracking, avoids issues with negative lookbehind at end\n (?:\n \\\\Q\n (?:(?!\\\\E)(?!/).)*+\n # A quoted sequence may not have a closing E, in which case it extends to the end of the regex\n (?:\\\\E | (?=/))\n | \\\\.\n | \\(\\?\\#[^)]*\\)\n | \\(\\?\n # InterpolatedCallout\n (?>\n {[^{].*?}\n | {{[^{].*?}}\n | {{{[^{].*?}}}\n | {{{{[^{].*?}}}}\n | {{{{{[^{].*?}}}}}\n | {{{{{{.+?}}}}}}\n )\n (?:\\[(?!\\d)\\w+\\])?\n [X<>]?\n \\)\n # Allow nested character classes to a limited depth\n | \\[(?:\n \\\\. |\n [^\\[\\]\\\\] |\n \\[(?:\n \\\\. |\n [^\\[\\]\\\\] |\n \\[(?:\n \\\\. |\n [^\\[\\]\\\\] |\n \\[(?:\n \\\\. |\n [^\\[\\]\\\\]\n )+\\]\n )+\\]\n )+\\]\n )+\\]\n | \\(\\g?+\\)\n | (?:(?!/)[^()\\[\\\\])+ # any character (until end)\n )+\n )\n )?+\n (?\n (?> # no backtracking, avoids issues with negative lookbehind at end\n (?:\n \\\\Q\n (?:(?!\\\\E)(?!/\\2).)*+\n # A quoted sequence may not have a closing E, in which case it extends to the end of the regex\n (?:\\\\E | (?=/\\2))\n | \\\\.\n | \\(\\?\\#[^)]*\\)\n | \\(\\?\n # InterpolatedCallout\n (?>\n {[^{].*?}\n | {{[^{].*?}}\n | {{{[^{].*?}}}\n | {{{{[^{].*?}}}}\n | {{{{{[^{].*?}}}}}\n | {{{{{{.+?}}}}}}\n )\n (?:\\[(?!\\d)\\w+\\])?\n [X<>]?\n \\)\n # Allow nested character classes to a limited depth\n | \\[(?:\n \\\\. |\n [^\\[\\]\\\\] |\n \\[(?:\n \\\\. |\n [^\\[\\]\\\\] |\n \\[(?:\n \\\\. |\n [^\\[\\]\\\\] |\n \\[(?:\n \\\\. |\n [^\\[\\]\\\\]\n )+\\]\n )+\\]\n )+\\]\n )+\\]\n | \\(\\g?+\\)\n | (?:(?!/\\2)[^()\\[\\\\])+ # any character (until end)\n )+\n )\n)?+\n(/\\2) # (4)\n| \\#+/.+(\\n)", + "captures": { + "0": { + "patterns": [ + { + "include": "#literals-regular-expression-literal-regex-guts" + } + ] }, - "9": { + "1": { + "name": "punctuation.definition.string.begin.regexp.swift" + }, + "4": { + "name": "punctuation.definition.string.end.regexp.swift" + }, + "5": { "name": "invalid.illegal.returns-not-allowed.regexp" } } @@ -3952,7 +4003,7 @@ "contentName": "source.swift", "name": "meta.embedded.line.swift", "begin": "\\\\#\\(", - "end": "(\\))", + "end": "\\)", "beginCaptures": { "0": { "name": "punctuation.section.embedded.begin.swift" @@ -3961,9 +4012,6 @@ "endCaptures": { "0": { "name": "punctuation.section.embedded.end.swift" - }, - "1": { - "name": "source.swift" } }, "patterns": [ @@ -3997,7 +4045,7 @@ "contentName": "source.swift", "name": "meta.embedded.line.swift", "begin": "\\\\\\(", - "end": "(\\))", + "end": "\\)", "beginCaptures": { "0": { "name": "punctuation.section.embedded.begin.swift" @@ -4006,9 +4054,6 @@ "endCaptures": { "0": { "name": "punctuation.section.embedded.end.swift" - }, - "1": { - "name": "source.swift" } }, "patterns": [ diff --git a/extensions/terminal-suggest/.npmrc b/extensions/terminal-suggest/.npmrc new file mode 100644 index 0000000000000..a9c57709666b2 --- /dev/null +++ b/extensions/terminal-suggest/.npmrc @@ -0,0 +1,2 @@ +legacy-peer-deps="true" +timeout=180000 diff --git a/extensions/terminal-suggest/cgmanifest.json b/extensions/terminal-suggest/cgmanifest.json index ead3479e6673c..410da24da1641 100644 --- a/extensions/terminal-suggest/cgmanifest.json +++ b/extensions/terminal-suggest/cgmanifest.json @@ -88,7 +88,8 @@ "type": "git", "git": { "repositoryUrl": "https://github.com/zsh-users/zsh", - "commitHash": "435cb1b748ce1f2f5c38edc1d64f4ee2424f9b3a" + "commitHash": "435cb1b748ce1f2f5c38edc1d64f4ee2424f9b3a", + "tag": "5.9" } }, "version": "5.9", diff --git a/extensions/terminal-suggest/extension.webpack.config.js b/extensions/terminal-suggest/extension.webpack.config.js index 89f3ea28d8747..8ac2b3abb22d2 100644 --- a/extensions/terminal-suggest/extension.webpack.config.js +++ b/extensions/terminal-suggest/extension.webpack.config.js @@ -2,14 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// @ts-check +import withDefaults from '../shared.webpack.config.mjs'; -//@ts-check -'use strict'; - -const withDefaults = require('../shared.webpack.config'); - -module.exports = withDefaults({ - context: __dirname, +export default withDefaults({ + context: import.meta.dirname, entry: { extension: './src/terminalSuggestMain.ts' }, diff --git a/extensions/terminal-suggest/package-lock.json b/extensions/terminal-suggest/package-lock.json index 239e60b0c25d0..2fb1083a7f478 100644 --- a/extensions/terminal-suggest/package-lock.json +++ b/extensions/terminal-suggest/package-lock.json @@ -8,9 +8,29 @@ "name": "terminal-suggest", "version": "1.0.1", "license": "MIT", + "devDependencies": { + "@types/node": "^22.18.10" + }, "engines": { "vscode": "^1.95.0" } + }, + "node_modules/@types/node": { + "version": "22.18.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz", + "integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "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" } } } diff --git a/extensions/terminal-suggest/package.json b/extensions/terminal-suggest/package.json index 6e6fb77c14652..734e3e91c82a5 100644 --- a/extensions/terminal-suggest/package.json +++ b/extensions/terminal-suggest/package.json @@ -24,13 +24,20 @@ "category": "Terminal", "title": "%terminal.integrated.suggest.clearCachedGlobals%" } - ] + ], + "terminal": { + "completionProviders": [ + { + "description": "Show suggestions for commands, arguments, flags, and file paths based upon the Fig spec." + } + ] + } }, "scripts": { "compile": "npx gulp compile-extension:terminal-suggest", "watch": "npx gulp watch-extension:terminal-suggest", - "pull-zshbuiltins": "ts-node ./scripts/pullZshBuiltins.ts", - "pull-fishbuiltins": "ts-node ./scripts/pullFishBuiltins.ts" + "pull-zshbuiltins": "node ./scripts/pullZshBuiltins.ts", + "pull-fishbuiltins": "node ./scripts/pullFishBuiltins.ts" }, "main": "./out/terminalSuggestMain", "activationEvents": [ @@ -39,5 +46,8 @@ "repository": { "type": "git", "url": "https://github.com/microsoft/vscode.git" + }, + "devDependencies": { + "@types/node": "^22.18.10" } } diff --git a/extensions/terminal-suggest/scripts/update-specs.js b/extensions/terminal-suggest/scripts/update-specs.js index 775c5dd326483..4376830d6fcbc 100644 --- a/extensions/terminal-suggest/scripts/update-specs.js +++ b/extensions/terminal-suggest/scripts/update-specs.js @@ -15,11 +15,44 @@ const replaceStrings = [ 'import { filepaths } from "@fig/autocomplete-generators";', 'import { filepaths } from \'../../helpers/filepaths\';' ], + [ + 'import { filepaths, keyValue } from "@fig/autocomplete-generators";', + 'import { filepaths } from \'../../helpers/filepaths\'; import { keyValue } from \'../../helpers/keyvalue\';' + ], ]; const indentSearch = [20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1].map(e => new RegExp('^' + ' '.repeat(e * 2), 'gm')); const indentReplaceValue = [20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1].map(e => '\t'.repeat(e)); const specSpecificReplaceStrings = new Map([ + ['docker', [ + [ + 'console.error(error);', + 'console.error(error); return null!;' + ] + ]], + ['dotnet', [ + [ + '.match(argRegex)', + '.match(argRegex)!' + ], [ + '"https://upload.wikimedia.org/wikipedia/commons/7/7d/Microsoft_.NET_logo.svg";', + 'undefined;', + ] + ]], + ['gh', [ + [ + 'const parts = elm.match(/\\S+/g);', + 'const parts = elm.match(/\\S+/g)!;' + ], + [ + 'description: repo.description,', + 'description: repo.description ?? undefined,' + ], + [ + 'icon: "fig://icon?type=git"', + 'icon: "vscode://icon?type=11"' + ] + ]], ['git', [ [ 'import { ai } from "@fig/autocomplete-generators";', @@ -32,6 +65,12 @@ const specSpecificReplaceStrings = new Map([ 'message: async ({ executeCommand }: any) =>' ] ]], + ['yo', [ + [ + 'icon: "https://avatars.githubusercontent.com/u/1714870?v=4",', + 'icon: undefined,', + ] + ]], ]); for (const spec of upstreamSpecs) { diff --git a/extensions/terminal-suggest/src/completions/azd.ts b/extensions/terminal-suggest/src/completions/azd.ts new file mode 100644 index 0000000000000..2a4adb84d62f4 --- /dev/null +++ b/extensions/terminal-suggest/src/completions/azd.ts @@ -0,0 +1,1870 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +interface AzdEnvListItem { + Name: string; + DotEnvPath: string; + HasLocal: boolean; + HasRemote: boolean; + IsDefault: boolean; +} + +interface AzdTemplateListItem { + name: string; + description: string; + repositoryPath: string; + tags: string[]; +} + +interface AzdExtensionListItem { + id: string; + name: string; + namespace: string; + version: string; + installedVersion: string; + source: string; +} + +const azdGenerators: Record = { + listEnvironments: { + script: ['azd', 'env', 'list', '--output', 'json'], + postProcess: (out) => { + try { + const envs: AzdEnvListItem[] = JSON.parse(out); + return envs.map((env) => ({ + name: env.Name, + displayName: env.IsDefault ? 'Default' : undefined, + })); + } catch { + return []; + } + }, + }, + listEnvironmentVariables: { + script: ['azd', 'env', 'get-values', '--output', 'json'], + postProcess: (out) => { + try { + const envVars: Record = JSON.parse(out); + return Object.keys(envVars).map((key) => ({ + name: key, + })); + } catch { + return []; + } + }, + }, + listTemplates: { + script: ['azd', 'template', 'list', '--output', 'json'], + postProcess: (out) => { + try { + const templates: AzdTemplateListItem[] = JSON.parse(out); + return templates.map((template) => ({ + name: template.repositoryPath, + description: template.name, + })); + } catch { + return []; + } + }, + cache: { + strategy: 'stale-while-revalidate', + } + }, + listTemplateTags: { + script: ['azd', 'template', 'list', '--output', 'json'], + postProcess: (out) => { + try { + const templates: AzdTemplateListItem[] = JSON.parse(out); + const tagsSet = new Set(); + + // Collect all unique tags from all templates + templates.forEach((template) => { + if (template.tags && Array.isArray(template.tags)) { + template.tags.forEach((tag) => tagsSet.add(tag)); + } + }); + + // Convert set to array and return as suggestions + return Array.from(tagsSet).sort().map((tag) => ({ + name: tag, + })); + } catch { + return []; + } + }, + cache: { + strategy: 'stale-while-revalidate', + } + }, + listTemplatesFiltered: { + custom: async (tokens, executeCommand, generatorContext) => { + // Find if there's a -f or --filter flag in the tokens + let filterValue: string | undefined; + for (let i = 0; i < tokens.length; i++) { + if ((tokens[i] === '-f' || tokens[i] === '--filter') && i + 1 < tokens.length) { + filterValue = tokens[i + 1]; + break; + } + } + + // Build the azd command with filter if present + const args = ['template', 'list', '--output', 'json']; + if (filterValue) { + args.push('--filter', filterValue); + } + + try { + const { stdout } = await executeCommand({ + command: 'azd', + args: args, + }); + + const templates: AzdTemplateListItem[] = JSON.parse(stdout); + return templates.map((template) => ({ + name: template.repositoryPath, + description: template.name, + })); + } catch { + return []; + } + }, + cache: { + strategy: 'stale-while-revalidate', + } + }, + listExtensions: { + script: ['azd', 'ext', 'list', '--output', 'json'], + postProcess: (out) => { + try { + const extensions: AzdExtensionListItem[] = JSON.parse(out); + const uniqueExtensions = new Map(); + + extensions.forEach((ext) => { + if (!uniqueExtensions.has(ext.id)) { + uniqueExtensions.set(ext.id, ext); + } + }); + + return Array.from(uniqueExtensions.values()).map((ext) => ({ + name: ext.id, + description: ext.name, + })); + } catch { + return []; + } + }, + cache: { + strategy: 'stale-while-revalidate', + } + }, + listInstalledExtensions: { + script: ['azd', 'ext', 'list', '--installed', '--output', 'json'], + postProcess: (out) => { + try { + const extensions: AzdExtensionListItem[] = JSON.parse(out); + const uniqueExtensions = new Map(); + + extensions.forEach((ext) => { + if (!uniqueExtensions.has(ext.id)) { + uniqueExtensions.set(ext.id, ext); + } + }); + + return Array.from(uniqueExtensions.values()).map((ext) => ({ + name: ext.id, + description: ext.name, + })); + } catch { + return []; + } + }, + }, +}; + +const completionSpec: Fig.Spec = { + name: 'azd', + description: 'Azure Developer CLI', + subcommands: [ + { + name: ['add'], + description: 'Add a component to your project.', + }, + { + name: ['ai'], + description: 'Extension for the Foundry Agent Service. (Preview)', + subcommands: [ + { + name: ['agent'], + description: 'Extension for the Foundry Agent Service. (Preview)', + }, + ], + }, + { + name: ['auth'], + description: 'Authenticate with Azure.', + subcommands: [ + { + name: ['login'], + description: 'Log in to Azure.', + options: [ + { + name: ['--check-status'], + description: 'Checks the log-in status instead of logging in.', + }, + { + name: ['--client-certificate'], + description: 'The path to the client certificate for the service principal to authenticate with.', + args: [ + { + name: 'client-certificate', + }, + ], + }, + { + name: ['--client-id'], + description: 'The client id for the service principal to authenticate with.', + args: [ + { + name: 'client-id', + }, + ], + }, + { + name: ['--client-secret'], + description: 'The client secret for the service principal to authenticate with. Set to the empty string to read the value from the console.', + args: [ + { + name: 'client-secret', + }, + ], + }, + { + name: ['--federated-credential-provider'], + description: 'The provider to use to acquire a federated token to authenticate with. Supported values: github, azure-pipelines, oidc', + args: [ + { + name: 'federated-credential-provider', + suggestions: ['github', 'azure-pipelines', 'oidc'], + }, + ], + }, + { + name: ['--managed-identity'], + description: 'Use a managed identity to authenticate.', + }, + { + name: ['--redirect-port'], + description: 'Choose the port to be used as part of the redirect URI during interactive login.', + args: [ + { + name: 'redirect-port', + }, + ], + }, + { + name: ['--tenant-id'], + description: 'The tenant id or domain name to authenticate with.', + args: [ + { + name: 'tenant-id', + }, + ], + }, + { + name: ['--use-device-code'], + description: 'When true, log in by using a device code instead of a browser.', + }, + ], + }, + { + name: ['logout'], + description: 'Log out of Azure.', + }, + ], + }, + { + name: ['coding-agent'], + description: 'This extension configures GitHub Copilot Coding Agent access to Azure', + }, + { + name: ['completion'], + description: 'Generate shell completion scripts.', + subcommands: [ + { + name: ['bash'], + description: 'Generate bash completion script.', + }, + { + name: ['fig'], + description: 'Generate Fig autocomplete spec.', + }, + { + name: ['fish'], + description: 'Generate fish completion script.', + }, + { + name: ['powershell'], + description: 'Generate PowerShell completion script.', + }, + { + name: ['zsh'], + description: 'Generate zsh completion script.', + }, + ], + }, + { + name: ['config'], + description: 'Manage azd configurations (ex: default Azure subscription, location).', + subcommands: [ + { + name: ['get'], + description: 'Gets a configuration.', + args: { + name: 'path', + }, + }, + { + name: ['list-alpha'], + description: 'Display the list of available features in alpha stage.', + }, + { + name: ['reset'], + description: 'Resets configuration to default.', + options: [ + { + name: ['--force', '-f'], + description: 'Force reset without confirmation.', + isDangerous: true, + }, + ], + }, + { + name: ['set'], + description: 'Sets a configuration.', + args: [ + { + name: 'path', + }, + { + name: 'value', + }, + ], + }, + { + name: ['show'], + description: 'Show all the configuration values.', + }, + { + name: ['unset'], + description: 'Unsets a configuration.', + args: { + name: 'path', + }, + }, + ], + }, + { + name: ['demo'], + description: 'This extension provides examples of the AZD extension framework.', + }, + { + name: ['deploy'], + description: 'Deploy your project code to Azure.', + options: [ + { + name: ['--all'], + description: 'Deploys all services that are listed in azure.yaml', + }, + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--from-package'], + description: 'Deploys the packaged service located at the provided path. Supports zipped file packages (file path) or container images (image tag).', + args: [ + { + name: 'file-path|image-tag', + }, + ], + }, + ], + args: { + name: 'service', + isOptional: true, + }, + }, + { + name: ['down'], + description: 'Delete your project\'s Azure resources.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--force'], + description: 'Does not require confirmation before it deletes resources.', + isDangerous: true, + }, + { + name: ['--purge'], + description: 'Does not require confirmation before it permanently deletes resources that are soft-deleted by default (for example, key vaults).', + isDangerous: true, + }, + ], + args: { + name: 'layer', + isOptional: true, + }, + }, + { + name: ['env'], + description: 'Manage environments (ex: default environment, environment variables).', + subcommands: [ + { + name: ['get-value'], + description: 'Get specific environment value.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + ], + args: { + name: 'keyName', + generators: azdGenerators.listEnvironmentVariables, + }, + }, + { + name: ['get-values'], + description: 'Get all environment values.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + ], + }, + { + name: ['list', 'ls'], + description: 'List environments.', + }, + { + name: ['new'], + description: 'Create a new environment and set it as the default.', + options: [ + { + name: ['--location', '-l'], + description: 'Azure location for the new environment', + args: [ + { + name: 'location', + }, + ], + }, + { + name: ['--subscription'], + description: 'Name or ID of an Azure subscription to use for the new environment', + args: [ + { + name: 'subscription', + }, + ], + }, + ], + args: { + name: 'environment', + }, + }, + { + name: ['refresh'], + description: 'Refresh environment values by using information from a previous infrastructure provision.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--hint'], + description: 'Hint to help identify the environment to refresh', + args: [ + { + name: 'hint', + }, + ], + }, + { + name: ['--layer'], + description: 'Provisioning layer to refresh the environment from.', + args: [ + { + name: 'layer', + }, + ], + }, + ], + args: { + name: 'environment', + }, + }, + { + name: ['select'], + description: 'Set the default environment.', + args: { + name: 'environment', + generators: azdGenerators.listEnvironments, + }, + }, + { + name: ['set'], + description: 'Set one or more environment values.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--file'], + description: 'Path to .env formatted file to load environment values from.', + args: [ + { + name: 'file', + }, + ], + }, + ], + args: [ + { + name: 'key', + isOptional: true, + }, + { + name: 'value', + isOptional: true, + }, + ], + }, + { + name: ['set-secret'], + description: 'Set a name as a reference to a Key Vault secret in the environment.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + ], + args: { + name: 'name', + }, + }, + ], + }, + { + name: ['extension', 'ext'], + description: 'Manage azd extensions.', + subcommands: [ + { + name: ['install'], + description: 'Installs specified extensions.', + options: [ + { + name: ['--force', '-f'], + description: 'Force installation even if it would downgrade the current version', + isDangerous: true, + }, + { + name: ['--source', '-s'], + description: 'The extension source to use for installs', + args: [ + { + name: 'source', + }, + ], + }, + { + name: ['--version', '-v'], + description: 'The version of the extension to install', + args: [ + { + name: 'version', + }, + ], + }, + ], + args: { + name: 'extension-id', + generators: azdGenerators.listExtensions, + }, + }, + { + name: ['list'], + description: 'List available extensions.', + options: [ + { + name: ['--installed'], + description: 'List installed extensions', + }, + { + name: ['--source'], + description: 'Filter extensions by source', + args: [ + { + name: 'source', + }, + ], + }, + { + name: ['--tags'], + description: 'Filter extensions by tags', + isRepeatable: true, + args: [ + { + name: 'tags', + }, + ], + }, + ], + }, + { + name: ['show'], + description: 'Show details for a specific extension.', + options: [ + { + name: ['--source', '-s'], + description: 'The extension source to use.', + args: [ + { + name: 'source', + }, + ], + }, + ], + args: { + name: 'extension-id', + generators: azdGenerators.listExtensions, + }, + }, + { + name: ['source'], + description: 'View and manage extension sources', + subcommands: [ + { + name: ['add'], + description: 'Add an extension source with the specified name', + options: [ + { + name: ['--location', '-l'], + description: 'The location of the extension source', + args: [ + { + name: 'location', + }, + ], + }, + { + name: ['--name', '-n'], + description: 'The name of the extension source', + args: [ + { + name: 'name', + }, + ], + }, + { + name: ['--type', '-t'], + description: 'The type of the extension source. Supported types are \'file\' and \'url\'', + args: [ + { + name: 'type', + }, + ], + }, + ], + }, + { + name: ['list'], + description: 'List extension sources', + }, + { + name: ['remove'], + description: 'Remove an extension source with the specified name', + args: { + name: 'name', + }, + }, + ], + }, + { + name: ['uninstall'], + description: 'Uninstall specified extensions.', + options: [ + { + name: ['--all'], + description: 'Uninstall all installed extensions', + }, + ], + args: { + name: 'extension-id', + isOptional: true, + generators: azdGenerators.listInstalledExtensions, + }, + }, + { + name: ['upgrade'], + description: 'Upgrade specified extensions.', + options: [ + { + name: ['--all'], + description: 'Upgrade all installed extensions', + }, + { + name: ['--source', '-s'], + description: 'The extension source to use for upgrades', + args: [ + { + name: 'source', + }, + ], + }, + { + name: ['--version', '-v'], + description: 'The version of the extension to upgrade to', + args: [ + { + name: 'version', + }, + ], + }, + ], + args: { + name: 'extension-id', + isOptional: true, + generators: azdGenerators.listInstalledExtensions, + }, + }, + ], + }, + { + name: ['hooks'], + description: 'Develop, test and run hooks for a project.', + subcommands: [ + { + name: ['run'], + description: 'Runs the specified hook for the project and services', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--platform'], + description: 'Forces hooks to run for the specified platform.', + args: [ + { + name: 'platform', + }, + ], + }, + { + name: ['--service'], + description: 'Only runs hooks for the specified service.', + args: [ + { + name: 'service', + }, + ], + }, + ], + args: { + name: 'name', + suggestions: [ + 'prebuild', + 'postbuild', + 'predeploy', + 'postdeploy', + 'predown', + 'postdown', + 'prepackage', + 'postpackage', + 'preprovision', + 'postprovision', + 'prepublish', + 'postpublish', + 'prerestore', + 'postrestore', + 'preup', + 'postup', + ], + }, + }, + ], + }, + { + name: ['infra'], + description: 'Manage your Infrastructure as Code (IaC).', + subcommands: [ + { + name: ['generate', 'gen', 'synth'], + description: 'Write IaC for your project to disk, allowing you to manually manage it.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--force'], + description: 'Overwrite any existing files without prompting', + isDangerous: true, + }, + ], + }, + ], + }, + { + name: ['init'], + description: 'Initialize a new application.', + options: [ + { + name: ['--branch', '-b'], + description: 'The template branch to initialize from. Must be used with a template argument (--template or -t).', + args: [ + { + name: 'branch', + }, + ], + }, + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--filter', '-f'], + description: 'The tag(s) used to filter template results. Supports comma-separated values.', + isRepeatable: true, + args: [ + { + name: 'filter', + generators: azdGenerators.listTemplateTags, + }, + ], + }, + { + name: ['--from-code'], + description: 'Initializes a new application from your existing code.', + }, + { + name: ['--location', '-l'], + description: 'Azure location for the new environment', + args: [ + { + name: 'location', + }, + ], + }, + { + name: ['--minimal', '-m'], + description: 'Initializes a minimal project.', + }, + { + name: ['--subscription', '-s'], + description: 'Name or ID of an Azure subscription to use for the new environment', + args: [ + { + name: 'subscription', + }, + ], + }, + { + name: ['--template', '-t'], + description: 'Initializes a new application from a template. You can use Full URI, /, or if it\'s part of the azure-samples organization.', + args: [ + { + name: 'template', + generators: azdGenerators.listTemplatesFiltered, + }, + ], + }, + { + name: ['--up'], + description: 'Provision and deploy to Azure after initializing the project from a template.', + }, + ], + }, + { + name: ['mcp'], + description: 'Manage Model Context Protocol (MCP) server. (Alpha)', + subcommands: [ + { + name: ['consent'], + description: 'Manage MCP tool consent.', + subcommands: [ + { + name: ['grant'], + description: 'Grant consent trust rules.', + options: [ + { + name: ['--action'], + description: 'Action type: \'all\' or \'readonly\'', + args: [ + { + name: 'action', + suggestions: ['all', 'readonly'], + }, + ], + }, + { + name: ['--global'], + description: 'Apply globally to all servers', + }, + { + name: ['--operation'], + description: 'Operation type: \'tool\' or \'sampling\'', + args: [ + { + name: 'operation', + suggestions: ['tool', 'sampling'], + }, + ], + }, + { + name: ['--permission'], + description: 'Permission: \'allow\', \'deny\', or \'prompt\'', + args: [ + { + name: 'permission', + suggestions: ['allow', 'deny', 'prompt'], + }, + ], + }, + { + name: ['--scope'], + description: 'Rule scope: \'global\', or \'project\'', + args: [ + { + name: 'scope', + suggestions: ['global', 'project'], + }, + ], + }, + { + name: ['--server'], + description: 'Server name', + args: [ + { + name: 'server', + }, + ], + }, + { + name: ['--tool'], + description: 'Specific tool name (requires --server)', + args: [ + { + name: 'tool', + }, + ], + }, + ], + }, + { + name: ['list'], + description: 'List consent rules.', + options: [ + { + name: ['--action'], + description: 'Action type to filter by (readonly, any)', + args: [ + { + name: 'action', + suggestions: ['all', 'readonly'], + }, + ], + }, + { + name: ['--operation'], + description: 'Operation to filter by (tool, sampling)', + args: [ + { + name: 'operation', + suggestions: ['tool', 'sampling'], + }, + ], + }, + { + name: ['--permission'], + description: 'Permission to filter by (allow, deny, prompt)', + args: [ + { + name: 'permission', + suggestions: ['allow', 'deny', 'prompt'], + }, + ], + }, + { + name: ['--scope'], + description: 'Consent scope to filter by (global, project). If not specified, lists rules from all scopes.', + args: [ + { + name: 'scope', + suggestions: ['global', 'project'], + }, + ], + }, + { + name: ['--target'], + description: 'Specific target to operate on (server/tool format)', + args: [ + { + name: 'target', + }, + ], + }, + ], + }, + { + name: ['revoke'], + description: 'Revoke consent rules.', + options: [ + { + name: ['--action'], + description: 'Action type to filter by (readonly, any)', + args: [ + { + name: 'action', + suggestions: ['all', 'readonly'], + }, + ], + }, + { + name: ['--operation'], + description: 'Operation to filter by (tool, sampling)', + args: [ + { + name: 'operation', + suggestions: ['tool', 'sampling'], + }, + ], + }, + { + name: ['--permission'], + description: 'Permission to filter by (allow, deny, prompt)', + args: [ + { + name: 'permission', + suggestions: ['allow', 'deny', 'prompt'], + }, + ], + }, + { + name: ['--scope'], + description: 'Consent scope to filter by (global, project). If not specified, revokes rules from all scopes.', + args: [ + { + name: 'scope', + suggestions: ['global', 'project'], + }, + ], + }, + { + name: ['--target'], + description: 'Specific target to operate on (server/tool format)', + args: [ + { + name: 'target', + }, + ], + }, + ], + }, + ], + }, + { + name: ['start'], + description: 'Starts the MCP server.', + }, + ], + }, + { + name: ['monitor'], + description: 'Monitor a deployed project.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--live'], + description: 'Open a browser to Application Insights Live Metrics. Live Metrics is currently not supported for Python apps.', + }, + { + name: ['--logs'], + description: 'Open a browser to Application Insights Logs.', + }, + { + name: ['--overview'], + description: 'Open a browser to Application Insights Overview Dashboard.', + }, + ], + }, + { + name: ['package'], + description: 'Packages the project\'s code to be deployed to Azure.', + options: [ + { + name: ['--all'], + description: 'Packages all services that are listed in azure.yaml', + }, + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--output-path'], + description: 'File or folder path where the generated packages will be saved.', + args: [ + { + name: 'output-path', + }, + ], + }, + ], + args: { + name: 'service', + isOptional: true, + }, + }, + { + name: ['pipeline'], + description: 'Manage and configure your deployment pipelines.', + subcommands: [ + { + name: ['config'], + description: 'Configure your deployment pipeline to connect securely to Azure. (Beta)', + options: [ + { + name: ['--applicationServiceManagementReference', '-m'], + description: 'Service Management Reference. References application or service contact information from a Service or Asset Management database. This value must be a Universally Unique Identifier (UUID). You can set this value globally by running azd config set pipeline.config.applicationServiceManagementReference .', + args: [ + { + name: 'applicationServiceManagementReference', + }, + ], + }, + { + name: ['--auth-type'], + description: 'The authentication type used between the pipeline provider and Azure for deployment (Only valid for GitHub provider). Valid values: federated, client-credentials.', + args: [ + { + name: 'auth-type', + suggestions: ['federated', 'client-credentials'], + }, + ], + }, + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--principal-id'], + description: 'The client id of the service principal to use to grant access to Azure resources as part of the pipeline.', + args: [ + { + name: 'principal-id', + }, + ], + }, + { + name: ['--principal-name'], + description: 'The name of the service principal to use to grant access to Azure resources as part of the pipeline.', + args: [ + { + name: 'principal-name', + }, + ], + }, + { + name: ['--principal-role'], + description: 'The roles to assign to the service principal. By default the service principal will be granted the Contributor and User Access Administrator roles.', + isRepeatable: true, + args: [ + { + name: 'principal-role', + }, + ], + }, + { + name: ['--provider'], + description: 'The pipeline provider to use (github for Github Actions and azdo for Azure Pipelines).', + args: [ + { + name: 'provider', + suggestions: ['github', 'azdo'], + }, + ], + }, + { + name: ['--remote-name'], + description: 'The name of the git remote to configure the pipeline to run on.', + args: [ + { + name: 'remote-name', + }, + ], + }, + ], + }, + ], + }, + { + name: ['provision'], + description: 'Provision Azure resources for your project.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--no-state'], + description: '(Bicep only) Forces a fresh deployment based on current Bicep template files, ignoring any stored deployment state.', + }, + { + name: ['--preview'], + description: 'Preview changes to Azure resources.', + }, + ], + args: { + name: 'layer', + isOptional: true, + }, + }, + { + name: ['publish'], + description: 'Publish a service to a container registry.', + options: [ + { + name: ['--all'], + description: 'Publishes all services that are listed in azure.yaml', + }, + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--from-package'], + description: 'Publishes the service from a container image (image tag).', + args: [ + { + name: 'image-tag', + }, + ], + }, + { + name: ['--to'], + description: 'The target container image in the form \'[registry/]repository[:tag]\' to publish to.', + args: [ + { + name: 'image-tag', + }, + ], + }, + ], + args: { + name: 'service', + isOptional: true, + }, + }, + { + name: ['restore'], + description: 'Restores the project\'s dependencies.', + options: [ + { + name: ['--all'], + description: 'Restores all services that are listed in azure.yaml', + }, + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + ], + args: { + name: 'service', + isOptional: true, + }, + }, + { + name: ['show'], + description: 'Display information about your project and its resources.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + { + name: ['--show-secrets'], + description: 'Unmask secrets in output.', + isDangerous: true, + }, + ], + args: { + name: 'resource-name|resource-id', + isOptional: true, + }, + }, + { + name: ['template'], + description: 'Find and view template details.', + subcommands: [ + { + name: ['list', 'ls'], + description: 'Show list of sample azd templates. (Beta)', + options: [ + { + name: ['--filter', '-f'], + description: 'The tag(s) used to filter template results. Supports comma-separated values.', + isRepeatable: true, + args: [ + { + name: 'filter', + generators: azdGenerators.listTemplateTags, + }, + ], + }, + { + name: ['--source', '-s'], + description: 'Filters templates by source.', + args: [ + { + name: 'source', + }, + ], + }, + ], + }, + { + name: ['show'], + description: 'Show details for a given template. (Beta)', + args: { + name: 'template', + generators: azdGenerators.listTemplates, + }, + }, + { + name: ['source'], + description: 'View and manage template sources. (Beta)', + subcommands: [ + { + name: ['add'], + description: 'Adds an azd template source with the specified key. (Beta)', + options: [ + { + name: ['--location', '-l'], + description: 'Location of the template source. Required when using type flag.', + args: [ + { + name: 'location', + }, + ], + }, + { + name: ['--name', '-n'], + description: 'Display name of the template source.', + args: [ + { + name: 'name', + }, + ], + }, + { + name: ['--type', '-t'], + description: 'Kind of the template source. Supported types are \'file\', \'url\' and \'gh\'.', + args: [ + { + name: 'type', + }, + ], + }, + ], + args: { + name: 'key', + }, + }, + { + name: ['list', 'ls'], + description: 'Lists the configured azd template sources. (Beta)', + }, + { + name: ['remove'], + description: 'Removes the specified azd template source (Beta)', + args: { + name: 'key', + }, + }, + ], + }, + ], + }, + { + name: ['up'], + description: 'Provision and deploy your project to Azure with a single command.', + options: [ + { + name: ['--environment', '-e'], + description: 'The name of the environment to use.', + args: [ + { + name: 'environment', + }, + ], + }, + ], + }, + { + name: ['version'], + description: 'Print the version number of Azure Developer CLI.', + }, + { + name: ['x'], + description: 'This extension provides a set of tools for AZD extension developers to test and debug their extensions.', + }, + { + name: ['help'], + description: 'Help about any command', + subcommands: [ + { + name: ['add'], + description: 'Add a component to your project.', + }, + { + name: ['ai'], + description: 'Extension for the Foundry Agent Service. (Preview)', + subcommands: [ + { + name: ['agent'], + description: 'Extension for the Foundry Agent Service. (Preview)', + }, + ], + }, + { + name: ['auth'], + description: 'Authenticate with Azure.', + subcommands: [ + { + name: ['login'], + description: 'Log in to Azure.', + }, + { + name: ['logout'], + description: 'Log out of Azure.', + }, + ], + }, + { + name: ['coding-agent'], + description: 'This extension configures GitHub Copilot Coding Agent access to Azure', + }, + { + name: ['completion'], + description: 'Generate shell completion scripts.', + subcommands: [ + { + name: ['bash'], + description: 'Generate bash completion script.', + }, + { + name: ['fig'], + description: 'Generate Fig autocomplete spec.', + }, + { + name: ['fish'], + description: 'Generate fish completion script.', + }, + { + name: ['powershell'], + description: 'Generate PowerShell completion script.', + }, + { + name: ['zsh'], + description: 'Generate zsh completion script.', + }, + ], + }, + { + name: ['config'], + description: 'Manage azd configurations (ex: default Azure subscription, location).', + subcommands: [ + { + name: ['get'], + description: 'Gets a configuration.', + }, + { + name: ['list-alpha'], + description: 'Display the list of available features in alpha stage.', + }, + { + name: ['reset'], + description: 'Resets configuration to default.', + }, + { + name: ['set'], + description: 'Sets a configuration.', + }, + { + name: ['show'], + description: 'Show all the configuration values.', + }, + { + name: ['unset'], + description: 'Unsets a configuration.', + }, + ], + }, + { + name: ['demo'], + description: 'This extension provides examples of the AZD extension framework.', + }, + { + name: ['deploy'], + description: 'Deploy your project code to Azure.', + }, + { + name: ['down'], + description: 'Delete your project\'s Azure resources.', + }, + { + name: ['env'], + description: 'Manage environments (ex: default environment, environment variables).', + subcommands: [ + { + name: ['get-value'], + description: 'Get specific environment value.', + }, + { + name: ['get-values'], + description: 'Get all environment values.', + }, + { + name: ['list', 'ls'], + description: 'List environments.', + }, + { + name: ['new'], + description: 'Create a new environment and set it as the default.', + }, + { + name: ['refresh'], + description: 'Refresh environment values by using information from a previous infrastructure provision.', + }, + { + name: ['select'], + description: 'Set the default environment.', + }, + { + name: ['set'], + description: 'Set one or more environment values.', + }, + { + name: ['set-secret'], + description: 'Set a name as a reference to a Key Vault secret in the environment.', + }, + ], + }, + { + name: ['extension', 'ext'], + description: 'Manage azd extensions.', + subcommands: [ + { + name: ['install'], + description: 'Installs specified extensions.', + }, + { + name: ['list'], + description: 'List available extensions.', + }, + { + name: ['show'], + description: 'Show details for a specific extension.', + }, + { + name: ['source'], + description: 'View and manage extension sources', + subcommands: [ + { + name: ['add'], + description: 'Add an extension source with the specified name', + }, + { + name: ['list'], + description: 'List extension sources', + }, + { + name: ['remove'], + description: 'Remove an extension source with the specified name', + }, + ], + }, + { + name: ['uninstall'], + description: 'Uninstall specified extensions.', + }, + { + name: ['upgrade'], + description: 'Upgrade specified extensions.', + }, + ], + }, + { + name: ['hooks'], + description: 'Develop, test and run hooks for a project.', + subcommands: [ + { + name: ['run'], + description: 'Runs the specified hook for the project and services', + }, + ], + }, + { + name: ['infra'], + description: 'Manage your Infrastructure as Code (IaC).', + subcommands: [ + { + name: ['generate', 'gen', 'synth'], + description: 'Write IaC for your project to disk, allowing you to manually manage it.', + }, + ], + }, + { + name: ['init'], + description: 'Initialize a new application.', + }, + { + name: ['mcp'], + description: 'Manage Model Context Protocol (MCP) server. (Alpha)', + subcommands: [ + { + name: ['consent'], + description: 'Manage MCP tool consent.', + subcommands: [ + { + name: ['grant'], + description: 'Grant consent trust rules.', + }, + { + name: ['list'], + description: 'List consent rules.', + }, + { + name: ['revoke'], + description: 'Revoke consent rules.', + }, + ], + }, + { + name: ['start'], + description: 'Starts the MCP server.', + }, + ], + }, + { + name: ['monitor'], + description: 'Monitor a deployed project.', + }, + { + name: ['package'], + description: 'Packages the project\'s code to be deployed to Azure.', + }, + { + name: ['pipeline'], + description: 'Manage and configure your deployment pipelines.', + subcommands: [ + { + name: ['config'], + description: 'Configure your deployment pipeline to connect securely to Azure. (Beta)', + }, + ], + }, + { + name: ['provision'], + description: 'Provision Azure resources for your project.', + }, + { + name: ['publish'], + description: 'Publish a service to a container registry.', + }, + { + name: ['restore'], + description: 'Restores the project\'s dependencies.', + }, + { + name: ['show'], + description: 'Display information about your project and its resources.', + }, + { + name: ['template'], + description: 'Find and view template details.', + subcommands: [ + { + name: ['list', 'ls'], + description: 'Show list of sample azd templates. (Beta)', + }, + { + name: ['show'], + description: 'Show details for a given template. (Beta)', + }, + { + name: ['source'], + description: 'View and manage template sources. (Beta)', + subcommands: [ + { + name: ['add'], + description: 'Adds an azd template source with the specified key. (Beta)', + }, + { + name: ['list', 'ls'], + description: 'Lists the configured azd template sources. (Beta)', + }, + { + name: ['remove'], + description: 'Removes the specified azd template source (Beta)', + }, + ], + }, + ], + }, + { + name: ['up'], + description: 'Provision and deploy your project to Azure with a single command.', + }, + { + name: ['version'], + description: 'Print the version number of Azure Developer CLI.', + }, + { + name: ['x'], + description: 'This extension provides a set of tools for AZD extension developers to test and debug their extensions.', + }, + ], + }, + ], + options: [ + { + name: ['--cwd', '-C'], + description: 'Sets the current working directory.', + isPersistent: true, + args: [ + { + name: 'cwd', + }, + ], + }, + { + name: ['--debug'], + description: 'Enables debugging and diagnostics logging.', + isPersistent: true, + }, + { + name: ['--no-prompt'], + description: 'Accepts the default value instead of prompting, or it fails if there is no default.', + isPersistent: true, + }, + { + name: ['--docs'], + description: 'Opens the documentation for azd in your web browser.', + isPersistent: true, + }, + { + name: ['--help', '-h'], + description: 'Gets help for azd.', + isPersistent: true, + }, + ], +}; + +export default completionSpec; diff --git a/extensions/terminal-suggest/src/completions/code.ts b/extensions/terminal-suggest/src/completions/code.ts index 99e1371b1815f..35cfbe28c58b6 100644 --- a/extensions/terminal-suggest/src/completions/code.ts +++ b/extensions/terminal-suggest/src/completions/code.ts @@ -357,6 +357,10 @@ export const troubleshootingOptions = (cliName: string): Fig.Option[] => [ name: '--telemetry', description: 'Shows all telemetry events which VS code collects', }, + { + name: '--transient', + description: 'Run with temporary data and extension directories, as if launched for the first time.', + }, ]; export function createCodeGenerators(cliName: string): Fig.Generator { diff --git a/extensions/terminal-suggest/src/completions/copilot.ts b/extensions/terminal-suggest/src/completions/copilot.ts new file mode 100644 index 0000000000000..6457479e7087b --- /dev/null +++ b/extensions/terminal-suggest/src/completions/copilot.ts @@ -0,0 +1,164 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const copilotSpec: Fig.Spec = { + name: 'copilot', + description: 'GitHub Copilot CLI - An AI-powered coding assistant', + options: [ + { + name: '--add-dir', + description: 'Add a directory to the allowed list for file access (can be used multiple times)', + args: { + name: 'directory', + template: 'folders' + }, + isRepeatable: true + }, + { + name: '--additional-mcp-config', + description: 'Additional MCP servers configuration as JSON string or file path (prefix with @)', + args: { + name: 'json', + description: 'JSON string or file path (prefix with @)' + }, + isRepeatable: true + }, + { + name: '--allow-all-paths', + description: 'Disable file path verification and allow access to any path' + }, + { + name: '--allow-all-tools', + description: 'Allow all tools to run automatically without confirmation; required for non-interactive mode' + }, + { + name: '--allow-tool', + description: 'Allow specific tools', + args: { + name: 'tools', + isVariadic: true, + isOptional: true + } + }, + { + name: '--banner', + description: 'Show the startup banner' + }, + { + name: '--continue', + description: 'Resume the most recent session' + }, + { + name: '--deny-tool', + description: 'Deny specific tools, takes precedence over --allow-tool or --allow-all-tools', + args: { + name: 'tools', + isVariadic: true, + isOptional: true + } + }, + { + name: '--disable-builtin-mcps', + description: 'Disable all built-in MCP servers (currently: github-mcp-server)' + }, + { + name: '--disable-mcp-server', + description: 'Disable a specific MCP server (can be used multiple times)', + args: { + name: 'server-name' + }, + isRepeatable: true + }, + { + name: '--disable-parallel-tools-execution', + description: 'Disable parallel execution of tools (LLM can still make parallel tool calls, but they will be executed sequentially)' + }, + { + name: '--disallow-temp-dir', + description: 'Prevent automatic access to the system temporary directory' + }, + { + name: ['-h', '--help'], + description: 'Display help for command' + }, + { + name: '--log-dir', + description: 'Set log file directory (default: ~/.copilot/logs/)', + args: { + name: 'directory', + template: 'folders' + } + }, + { + name: '--log-level', + description: 'Set the log level', + args: { + name: 'level', + suggestions: ['none', 'error', 'warning', 'info', 'debug', 'all', 'default'] + } + }, + { + name: '--model', + description: 'Set the AI model to use', + args: { + name: 'model', + suggestions: ['claude-sonnet-4.5', 'claude-sonnet-4', 'claude-haiku-4.5', 'gpt-5'] + } + }, + { + name: '--no-color', + description: 'Disable all color output' + }, + { + name: '--no-custom-instructions', + description: 'Disable loading of custom instructions from AGENTS.md and related files' + }, + { + name: ['-p', '--prompt'], + description: 'Execute a prompt directly without interactive mode', + args: { + name: 'text', + description: 'The prompt text to execute' + } + }, + { + name: '--resume', + description: 'Resume from a previous session (optionally specify session ID)', + args: { + name: 'sessionId', + isOptional: true + } + }, + { + name: '--screen-reader', + description: 'Enable screen reader optimizations' + }, + { + name: '--stream', + description: 'Enable or disable streaming mode', + args: { + name: 'mode', + suggestions: ['on', 'off'] + } + }, + { + name: ['-v', '--version'], + description: 'Show version information' + } + ], + subcommands: [ + { + name: 'help', + description: 'Display help information', + args: { + name: 'topic', + isOptional: true, + suggestions: ['config', 'commands', 'environment', 'logging', 'permissions'] + } + } + ] +}; + +export default copilotSpec; diff --git a/extensions/terminal-suggest/src/completions/gh.ts b/extensions/terminal-suggest/src/completions/gh.ts new file mode 100644 index 0000000000000..ea163beeb7f7b --- /dev/null +++ b/extensions/terminal-suggest/src/completions/gh.ts @@ -0,0 +1,3402 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* eslint-disable local/code-no-unexternalized-strings */ + +import * as vscode from 'vscode'; +import { filepaths } from '../helpers/filepaths'; +import { keyValue } from '../helpers/keyvalue'; + +const filterMessages = (out: string): string => { + return out.startsWith("warning:") || out.startsWith("error:") + ? out.split("\n").slice(1).join("\n") + : out; +}; + +const postProcessRemoteBranches: Fig.Generator["postProcess"] = (out) => { + const output = filterMessages(out); + + if (output.startsWith("fatal:")) { + return []; + } + + return output.split("\n").map((elm) => { + // Trim and remove the remote part of the branch name (origin/, fork/...) + let name = elm.trim().replace(/\w+\//, ""); + + const parts = elm.match(/\S+/g)!; + if (parts.length > 1) { + if (parts[0] === "*") { + // We are in a detached HEAD state + if (elm.includes("HEAD detached")) { + return {}; + } + // Current branch + return { + name: elm.replace("*", "").trim(), + description: "Current branch", + priority: 100, + // allow-any-unicode-next-line + icon: "⭐️", + }; + } else if (parts[0] === "+") { + // Branch checked out in another worktree. + name = elm.replace("+", "").trim(); + } + } + + return { + name, + description: "Branch", + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`, + priority: 75, + }; + }); +}; + +interface RepoDataType { + isPrivate: boolean; + nameWithOwner: string; + description: string | null; +} + +const listRepoMapFunction = (repo: RepoDataType) => ({ + name: repo.nameWithOwner, + description: repo.description ?? undefined, + //be able to see if the repo is private at a glance + // allow-any-unicode-next-line + icon: repo.isPrivate ? "🔒" : "👀", +}); + +const ghGenerators: Record = { + listCustomRepositories: { + trigger: "/", + //execute is script then postProcess + custom: async (tokens, execute) => { + //get the last command token + const last = tokens.pop(); + + //gatekeeper + if (!last) { + return []; + } + + /** + * this turns this input: + * `withfig/autocomplete` + * + * into: + * ["withfig", "autocomplete"] + */ + const userRepoSplit = last.split("/"); + + // make sure it has some length. + if (userRepoSplit.length === 0) { + return []; + } + + //get first element of arr + const userOrOrg = userRepoSplit.shift(); + + // make sure it has some existence. + if (!userOrOrg) { + return []; + } + + //run `gh repo list` cmd + const { stdout, status } = await execute({ + command: "gh", + args: [ + "repo", + "list", + userOrOrg, + "--limit", + "9999", + "--json", + "nameWithOwner,description,isPrivate", + ], + }); + + // make sure it has some existence. + if (status !== 0) { + return []; + } + + //parse the JSON string output of the command + const repoArr: RepoDataType[] = JSON.parse(stdout); + + return repoArr.map(listRepoMapFunction); + }, + }, + listRepositories: { + /* + * based on the gh api (use this instead as it also returns repos in the orgs that the user is part of) + * https://cli.github.com/manual/gh_api + * + * --jq https://cli.github.com/manual/gh_help_formatting https://www.baeldung.com/linux/jq-command-json + */ + script: [ + "gh", + "api", + "graphql", + "--paginate", + "-f", + "query='query($endCursor: String) { viewer { repositories(first: 100, after: $endCursor) { nodes { isPrivate, nameWithOwner, description } pageInfo { hasNextPage endCursor }}}}'", + "--jq", + ".data.viewer.repositories.nodes[]", + ], + postProcess: (out) => { + if (out) { + /** + * the string thats returned bt the command will contain lines like this: + * + * {...data} + * {...data} + * etc + * + * so the string needs to be transformed into a json array by adding commas on all newline chars then wrapping in square braces + * + * compared to none paginating request this is a touch slower 300ms or so, but it fixes the over 100 repos issue! + * + */ + const jsonifiedOutString = `[${out.replace(/(?:\r\n|\r|\n)/g, ",")}]`; + try { + const data: RepoDataType[] = JSON.parse(jsonifiedOutString); + + return data.map(listRepoMapFunction); + } catch { + return []; + } + } + return []; + }, + }, + listPR: { + cache: { strategy: "stale-while-revalidate" }, + script: ["gh", "pr", "list", "--json=number,title,headRefName,state"], + postProcess: (out) => { + interface PR { + headRefName: string; + number: number; + state: string; + title: string; + } + const items = JSON.parse(out) as PR[]; + return items.map((line) => { + const { number, title, headRefName, state } = line; + return { + name: number.toString(), + displayName: title, + description: `#${number} | ${headRefName}`, + icon: `vscode://icon?type=${state === "OPEN" ? vscode.TerminalCompletionItemKind.PullRequest : vscode.TerminalCompletionItemKind.PullRequestDone}`, + }; + }); + }, + }, + listAlias: { + script: ["gh", "alias", "list"], + postProcess: (out) => { + const aliases = out.split("\n").map((line) => { + const [name, content] = line.split(":"); + + return { name: name.trim(), content: content.trim() }; + }); + + return aliases.map(({ name, content }) => ({ + name, + description: `Alias for '${content}'`, + icon: "fig://icon?type=commandkey", + })); + }, + }, + remoteBranches: { + script: [ + "git", + "--no-optional-locks", + "branch", + "-r", + "--no-color", + "--sort=-committerdate", + ], + postProcess: postProcessRemoteBranches, + }, +}; + +const codespaceOption: Fig.Option = { + name: ["-c", "--codespace"], + description: "Name of the codespace", + args: { + name: "string", + }, +}; + +const ghOptions: Record = { + clone: { name: "--clone", description: "Clone the fork {true|false}" }, + cloneGitFlags: { + name: "--", + description: "Flags to pass to git when cloning", + priority: 25, + args: { + name: "flags", + description: "Flags to pass to git when cloning", + isVariadic: true, + }, + }, + confirm: { + name: ["-y", "--confirm"], + description: "Skip the confirmation prompt", + }, + all: { + name: ["--repo", "-R"], + description: "Select another repository", + args: { + name: "[HOST/]OWNER/REPO", + }, + }, + env: { + name: ["-e", "--env"], + description: "List secrets for an environment", + args: { + name: "string", + }, + }, + org: { + name: ["-o", "--org"], + description: "List secrets for an environment", + args: { + name: "string", + }, + }, +}; + +const completionSpec: Fig.Spec = { + name: "gh", + description: "GitHub's CLI tool", + args: { + name: "alias", + description: "Custom user defined gh alias", + isOptional: true, + generators: ghGenerators.listAlias, + parserDirectives: { + alias: async (token, executeShellCommand) => { + const { stdout } = await executeShellCommand({ + command: "gh", + args: ["alias", "list"], + }); + const alias = stdout + .split("\n") + .find((line) => line.startsWith(`${token}:\t`)); + + if (!alias) { + throw new Error("Failed to parse alias"); + } + + return alias.slice(token.length + 1).trim(); + }, + }, + }, + subcommands: [ + { + name: "alias", + description: "Create command shortcuts", + + subcommands: [ + { + name: "delete", + description: "Delete an alias", + args: { + name: "alias", + generators: ghGenerators.listAlias, + }, + }, + { + name: "list", + description: "List available aliases", + }, + { + name: "set", + description: "Set an alias for a gh command", + args: [ + { + name: "alias", + description: "A word that will expand to the gh command", + }, + { + name: "expansion", + description: + "The gh command to be invoked, more info with --help", + }, + ], + options: [ + { + name: ["-s", "--shell"], + description: + "Declare an alias to be passed through a shell interpreter", + }, + ], + }, + ], + }, { + name: "api", + description: "Make an authenticated GitHub API request", + args: { + name: " [flags]a", + }, + options: [ + { + name: "--cache", + description: 'Cache the response, e.g. "3600s", "60m", "1h"', + args: { name: "duration" }, + }, + { + name: ["-F", "--field"], + description: "Add a typed parameter in key=value format", + args: { name: "key:value" }, + }, + { + name: "--hostname", + description: + 'The GitHub hostname for the request (default "github.com")', + args: { + name: "string", + }, + }, + { + name: ["-i", "--include"], + description: + "Include HTTP response status line and headers in the output", + }, + { + name: "--input", + description: + 'The file to use as body for the HTTP request (use "-" to read from standard input)', + args: { name: "file" }, + }, + { + name: ["-q", "--jq"], + description: + "Query to select values from the response using jq syntax", + args: { name: "string" }, + }, + { + name: ["-X", "--method"], + description: "The HTTP method for the request", + args: { name: "string", description: '(default "GET")' }, + }, + { + name: "--paginate", + description: + "Make additional HTTP requests to fetch all pages of results", + }, + { + name: ["-p", "--preview"], + description: + 'GitHub API preview names to request (without the "-preview" suffix)', + args: { name: "names" }, + }, + { + name: ["-f", "--raw-field"], + description: "Add a string parameter in key=value format", + args: { name: "key=value" }, + }, + { + name: "--silent", + description: "Do not print the response body", + }, + { + name: "--slurp", + description: + 'Use with "--paginate" to return an array of all pages of either JSON arrays or objects', + }, + { + name: ["-t", "--template"], + description: + 'Format JSON output using a Go template; see "gh help formatting"', + args: { name: "string" }, + }, + { + name: "--verbose", + description: "Include full HTTP request and response in the output", + }, + ], + }, + { + name: "auth", + description: "Authenticate gh and git with GitHub", + + subcommands: [ + { + name: "login", + description: "Gh auth login [flags]", + options: [ + { + name: ["-p", "--git-protocol"], + description: + "The protocol to use for git operations on this host: {ssh|https}", + args: { name: "string" }, + }, + { + name: ["-h", "--hostname"], + description: + "The hostname of the GitHub instance to authenticate with", + args: { name: "string" }, + }, + { + name: "--insecure-storage", + description: + "Save authentication credentials in plain text instead of credential store", + }, + { + name: ["-s", "--scopes"], + description: "Additional authentication scopes to request", + args: { name: "strings" }, + }, + { + name: "--skip-ssh-key", + description: "Skip generate/upload SSH key prompt", + }, + { + name: ["-w", "--web"], + description: "Open a browser to authenticate", + }, + { + name: "--with-token", + description: "Read token from standard input", + }, + ], + }, + { + name: "logout", + description: "Gh auth logout [flags]", + options: [ + { + name: ["-h", "--hostname"], + description: "The hostname of the GitHub instance to log out of", + args: { name: "string" }, + }, + { + name: ["-u", "--user"], + description: "The account to log out of", + args: { name: "string" }, + }, + ], + }, + { + name: "refresh", + description: "Gh auth refresh [flags]", + options: [ + { + name: ["-h", "--hostname"], + description: "The GitHub host to use for authentication", + args: { name: "string" }, + }, + { + name: "--insecure-storage", + description: + "Save authentication credentials in plain text instead of credential store", + }, + { + name: ["-r", "--remove-scopes"], + description: "Authentication scopes to remove from gh", + args: { name: "strings" }, + }, + { + name: "--reset-scopes", + description: + "Reset authentication scopes to the default minimum set of scopes", + }, + { + name: ["-s", "--scopes"], + description: "Additional authentication scopes for gh to have", + args: { name: "strings" }, + }, + ], + }, + { + name: "setup-git", + description: "Gh auth setup-git [flags]", + options: [ + { + name: ["-f", "--force"], + description: + "Force setup even if the host is not known. Must be used in conjunction with --hostname", + args: { name: "--hostname" }, + }, + { + name: ["-h", "--hostname"], + description: "The hostname to configure git for", + args: { name: "string" }, + }, + ], + }, + { + name: "status", + description: "View authentication status", + options: [ + { + name: ["-a", "--active"], + description: "Display the active account only", + }, + { + name: ["-h", "--hostname"], + description: "Check only a specific hostname's auth status", + args: { name: "string" }, + }, + { + name: ["-t", "--show-token"], + description: "Display the auth token", + }, + ], + }, + { + name: "switch", + description: "Switch the active account for a GitHub host", + options: [ + { + name: ["-h", "--hostname"], + description: + "The hostname of the GitHub instance to switch account for", + args: { name: "string" }, + }, + { + name: ["-u", "--user"], + description: "The account to switch to", + args: { name: "string" }, + }, + ], + }, + { + name: "token", + description: "Gh auth token [flags]", + options: [ + { + name: ["-h", "--hostname"], + description: + "The hostname of the GitHub instance authenticated with", + args: { name: "string" }, + }, + { + name: ["-u", "--user"], + description: "The account to output the token for", + args: { name: "string" }, + }, + ], + }, + ], + }, + { + name: "gpg-key", + description: "Manage GPG keys registered with your GitHub account", + + subcommands: [ + { + name: "add", + description: "Add a GPG key to your GitHub account", + }, + { + name: "list", + description: "Lists GPG keys in your GitHub account", + }, + ], + }, + { + name: "browse", + description: "Open the repository in the browser", + args: { + name: "[pr | issue | path[:line]", + generators: ghGenerators.listPR, + suggestCurrentToken: true, + }, + options: [ + { + name: ["-b", "--branch"], + description: "Select another branch by passing in the branch name", + args: { + name: "branch", + generators: ghGenerators.remoteBranches, + }, + }, + { + name: ["-c", "--commit"], + description: "Open the last commit", + }, + { + name: ["-n", "--no-browser"], + description: "Print destination URL instead of opening the browser", + }, + { + name: ["-p", "--projects"], + description: "Open repository projects", + }, + { + name: ["-R", "--repo"], + description: + "Select another repository using the [HOST/]OWNER/REPO format", + args: { + name: "[HOST/]OWNER/REPO", + }, + }, + { + name: ["-s", "--settings"], + description: "Open repository settings", + }, + { + name: ["-w", "--wiki"], + description: "Open repository wiki", + }, + ], + }, + { + name: "completion", + description: "Generate shell completion scripts", + options: [ + { + name: ["-s", "--shell"], + args: { + name: "shell", + suggestions: ["bash", "zsh", "fish", "powershell"], + }, + }, + ], + }, + { + name: "config", + description: "Manage configuration for gh", + + subcommands: [ + { + name: "get", + description: "Print the value of a given configuration key", + args: { + name: "key", + suggestions: [ + "git_protocol", + "editor", + "prompt", + "pager", + "http_unix_socket", + ], + }, + options: [ + { + name: ["-h", "--host"], + args: { name: "host" }, + description: "Get per-host setting", + }, + ], + }, + { + name: "set", + description: "Update configuration with a value for the given key", + subcommands: [ + { + name: "git_protocol", + description: + "The protocol to use for git clone and push operations", + args: { + name: "option", + suggestions: ["https", "ssh"], + }, + }, + { + name: "editor", + description: "The text editor program to use for authoring text", + args: { name: "editor", suggestions: ["vim", "nano"] }, + }, + { + name: "prompt", + description: "Toggle interactive prompting in the terminal", + args: { + name: "value", + suggestions: ["enable", "disable"], + }, + }, + { + name: "pager", + insertValue: "pager {cursor}", + description: + "The terminal pager program to send standard output to", + args: { name: "value" }, + }, + { + name: "http_unix_socket", + description: + "The path to a unix socket through which to make HTTP connection", + args: { name: "path" }, + }, + ], + options: [ + { + name: ["-h", "--host"], + args: { name: "host" }, + description: "Get per-host setting", + }, + ], + }, + ], + }, + { + name: "extensions", + description: "Manage gh extensions", + + subcommands: [ + { + name: "create", + description: "Create a new extension", + + args: { + name: "name", + }, + }, + { + name: "install", + description: "Install a gh extension from a repository", + + args: { + name: "repo", + }, + }, + { + name: "list", + description: "List installed extension commands", + }, + { + name: "remove", + description: "Remove an installed extension", + + args: { + name: "name", + }, + }, + { + name: "upgrade", + description: "Upgrade installed extensions", + options: [ + { name: "--all", description: "Upgrade all extensions" }, + { name: "--force", description: "Force upgrade extensions" }, + ], + args: { + name: "name", + }, + }, + ], + }, + { + name: "gist", + description: "Manage gists", + + subcommands: [ + { + name: "clone", + description: "Clone a gist locally", + + args: [ + { name: "gist", description: "Gist ID or URL" }, + { name: "directory", isOptional: true, template: "folders" }, + ], + }, + { + name: "create", + description: "Create a new gist", + args: { + name: "filename", + template: "filepaths", + }, + options: [ + { + name: ["-d", "--desc"], + description: "A description for this gist", + insertValue: "-d '{cursor}'", + args: { name: "description" }, + }, + { + name: ["-f", "--filename"], + description: + "Provide a filename to be used when reading from STDIN", + args: { name: "filename", template: "filepaths" }, + }, + { + name: ["-p", "--public"], + description: "List the gist publicly (default: secret)", + }, + { + name: ["-w", "--web"], + description: "Open the web browser with created gist", + }, + ], + }, + { + name: "delete", + description: "Delete a gist", + + args: { name: "gist", description: "Gist ID or URL" }, + }, + { + name: "edit", + description: "Edit one of your gists", + args: { name: "gist", description: "Gist ID or URL" }, + options: [ + { + name: ["-a", "--add"], + description: "Add a new file to the gist", + args: { name: "filename", template: "filepaths" }, + }, + { + name: ["-f", "--filename"], + description: "Select a file to edit", + }, + ], + }, + { + name: "list", + description: "List your gists", + options: [ + { + name: ["-L", "--limit"], + displayName: "-L, --limit", + description: "Maximum number of gists to fetch (default 10)", + args: { name: "int" }, + }, + { + name: "--public", + description: "Show only public gists", + }, + { + name: "--secret", + description: "Show only secret gists", + }, + ], + }, + { + name: "view", + description: "View a gist", + args: { name: "gist", description: "Gist ID or URL" }, + options: [ + { + name: ["-f", "--filename"], + description: "Display a single file from the gist", + }, + { + name: "--files", + description: "List file names from the gist", + }, + { + name: ["-r", "--raw"], + description: "Print raw instead of rendered gist contents", + }, + { + name: ["-w", "--web"], + description: "Open gist in the browser", + }, + ], + }, + ], + }, + { + name: "issue", + description: "Manage issues", + + subcommands: [ + { + name: "close", + description: "Close issue", + args: { name: "issue", description: "Number or URL" }, + options: [ + { + name: ["-R", "--repo"], + insertValue: "-R '{cursor}'", + description: + "Select another repository using the [HOST/]OWNER/REPO format", + args: { name: "repo" }, + }, + ], + }, + { + name: "comment", + description: "Create a new issue comment", + args: { name: "issue", description: "Number or URL" }, + options: [ + { + name: ["-R", "--repo"], + insertValue: "-R '{cursor}'", + description: + "Select another repository using the [HOST/]OWNER/REPO format", + args: { name: "repo" }, + }, + { + name: ["-b", "--body"], + insertValue: "-b '{cursor}'", + description: "Supply a body. Will prompt for one otherwise", + args: { name: "string" }, + }, + { + name: ["-F", "--body-file"], + description: "Read body text from file", + args: { name: "file", template: "filepaths" }, + }, + { + name: ["-e", "--editor"], + description: "Add body using editor", + args: { name: "editor" }, + }, + { + name: ["-w", "--web"], + description: "Add body in browser", + }, + ], + }, + { + name: "create", + description: "Create a new issue", + options: [ + { + name: ["-R", "--repo"], + insertValue: "-R '{cursor}'", + description: + "Select another repository using the [HOST/]OWNER/REPO format", + args: { name: "repo" }, + }, + { + name: ["-a", "--assignee"], + description: + 'Assign people by their login. Use "@me" to self-assign', + args: { name: "login" }, + }, + { + name: ["-b", "--body"], + insertValue: "-b '{cursor}'", + description: "Supply a body. Will prompt for one otherwise", + args: { name: "string" }, + }, + { + name: ["-F", "--body-file"], + description: "Read body text from file", + args: { name: "file", template: "filepaths" }, + }, + { + name: ["-l", "--label"], + insertValue: "-l '{cursor}'", + description: "Add labels by name", + args: { name: "name" }, + }, + { + name: ["-m", "--milestone"], + description: "Add the issue to a milestone by name", + args: { name: "name" }, + }, + { + name: ["-p", "--project"], + insertValue: "-p '{cursor}'", + description: "Add the issue to projects by name", + args: { name: "name" }, + }, + { + name: "--recover", + insertValue: "--recover '{cursor}'", + description: "Recover input from a failed run of create", + args: { name: "string" }, + }, + { + name: ["-t", "--title"], + description: "Supply a title. Will prompt for one otherwise", + insertValue: "-t '{cursor}'", + args: { name: "string" }, + }, + { + name: ["-w", "--web"], + description: "Open the browser to create an issue", + }, + ], + }, + { + name: "delete", + description: "Delete issue", + options: [ + { + name: ["-R", "--repo"], + insertValue: "-R '{cursor}'", + description: + "Select another repository using the [HOST/]OWNER/REPO format", + args: { name: "repo" }, + }, + ], + }, + { + name: "edit", + description: "Edit an issue", + args: { name: "issue", description: "Number or URL" }, + options: [ + { + name: ["-R", "--repo"], + insertValue: "-R '{cursor}'", + description: + "Select another repository using the [HOST/]OWNER/REPO format", + args: { name: "repo" }, + }, + { + name: "--add-assignee", + description: + 'Add assigned users by their login. Use "@me" to assign yourself', + args: { name: "login" }, + }, + { + name: "--add-label", + description: "Add labels by name", + args: { name: "name" }, + }, + { + name: ["-b", "--body"], + insertValue: "-b '{cursor}'", + description: "Set the new body", + args: { name: "string" }, + }, + { + name: ["-F", "--body-file"], + description: "Read body text from file", + args: { name: "file", template: "filepaths" }, + }, + { + name: ["-m", "--milestone"], + description: "Edit the milestone the issue belongs to by name", + args: { name: "name" }, + }, + { + name: "--remove-assignee", + description: + 'Remove assigned users by their login. Use "@me" to unassign yourself', + args: { name: "login" }, + }, + { + name: "--remove-label", + description: "Remove labels by name", + args: { name: "name" }, + }, + { + name: "--remove-project", + description: "Remove the issue from projects by name", + args: { name: "name" }, + }, + { + name: ["-t", "--title"], + description: "Set the new title", + insertValue: "-t '{cursor}'", + args: { name: "string" }, + }, + ], + }, + { + name: "list", + description: "List and filter issues in this repository", + options: [ + { + name: ["-R", "--repo"], + insertValue: "-R '{cursor}'", + description: + "Select another repository using the [HOST/]OWNER/REPO format", + args: { name: "repo" }, + }, + { + name: ["-a", "--assignee"], + description: "Filter by assignee", + args: { name: "string" }, + }, + { + name: ["-A", "--author"], + description: "Filter by author", + args: { name: "string" }, + }, + { + name: ["-q", "--jq"], + description: "Filter JSON output using a jq expression", + args: { name: "expression" }, + }, + { + name: "--json", + description: "Output JSON with the specified fields", + args: { name: "fields" }, + }, + { + name: ["-l", "--label"], + insertValue: "-l '{cursor}'", + description: "Filter by labels", + args: { name: "string" }, + }, + { + name: ["-L", "--limit"], + description: "Maximum number of issues to fetch (default 30)", + args: { name: "int" }, + }, + { + name: "--mention", + description: "Filter by mention", + args: { name: "string" }, + }, + { + name: ["-m", "--milestone"], + insertValue: "-m '{cursor}'", + description: "Filter by milestone number or `title`", + args: { name: "number", description: "Number or Title" }, + }, + { + name: ["-S", "--search"], + insertValue: "--search '{cursor}'", + description: "Search issues with query", + args: { name: "query" }, + }, + { + name: ["-s", "--state"], + description: 'Filter by state (default "open")', + args: { + name: "state", + suggestions: ["open", "closed", "all"], + default: "open", + description: '(default "open")', + }, + }, + { + name: ["-t", "--template"], + description: "Format JSON output using a Go template", + args: { name: "string" }, + }, + { + name: ["-w", "--web"], + description: "Open the browser to list the issue(s)", + }, + ], + }, + { + name: "reopen", + description: "Reopen issue", + options: [ + { + name: ["-R", "--repo"], + insertValue: "-R '{cursor}'", + description: + "Select another repository using the [HOST/]OWNER/REPO format", + args: { name: "repo" }, + }, + ], + }, + { + name: "status", + description: "Show status of relevant issues", + options: [ + { + name: ["-R", "--repo"], + insertValue: "-R '{cursor}'", + description: + "Select another repository using the [HOST/]OWNER/REPO format", + args: { name: "repo" }, + }, + { + name: ["-q", "--jq"], + description: "Filter JSON output using a jq expression", + args: { name: "expression" }, + }, + { + name: "--json", + description: "Output JSON with the specified fields", + args: { name: "fields" }, + }, + { + name: ["-t", "--template"], + description: "Format JSON output using a Go template", + args: { name: "string" }, + }, + ], + }, + { + name: "transfer", + description: "Transfer issue to another repository", + args: [ + { name: "issue", description: "Number or URL" }, + { name: "destination-repo" }, + ], + options: [ + { + name: ["-R", "--repo"], + insertValue: "-R '{cursor}'", + description: + "Select another repository using the [HOST/]OWNER/REPO format", + args: { name: "repo" }, + }, + ], + }, + { + name: "view", + description: "View an issue", + args: { name: "issue", description: "Number or URL" }, + options: [ + { + name: ["-R", "--repo"], + insertValue: "-R '{cursor}'", + description: + "Select another repository using the [HOST/]OWNER/REPO format", + args: { name: "repo" }, + }, + { + name: ["-c", "--comments"], + description: "View issue comments", + }, + { + name: ["-q", "--jq"], + description: "Filter JSON output using a jq expression", + args: { name: "expression" }, + }, + { + name: "--json", + description: "Output JSON with the specified fields", + args: { name: "fields" }, + }, + { + name: ["-t", "--template"], + description: "Format JSON output using a Go template", + args: { name: "string" }, + }, + { + name: ["-w", "--web"], + description: "Open an issue in the browser", + }, + ], + }, + ], + }, + { + name: "pr", + description: "Manage pull requests", + subcommands: [ + { + name: "checkout", + description: "Check out a pull request in git", + args: { + name: "number | url | branch", + generators: ghGenerators.listPR, + }, + options: [ + { + name: "--recurse-submodules", + description: "Update all active submodules (recursively)", + }, + ], + }, + { + name: "checks", + description: "Show CI status for a single pull request", + args: { + name: "number | url | branch", + generators: ghGenerators.listPR, + }, + options: [ + { + name: ["-w", "--web"], + description: "Open the web browser to show details about checks", + }, + ], + }, + { + name: "close", + description: "Close a pull request", + args: { + name: "number | url | branch", + generators: ghGenerators.listPR, + }, + options: [ + { + name: ["-d", "--delete-branch"], + description: "Delete the local and remote branch after close", + }, + ], + }, + { + name: "edit", + description: + "Edit a pull request. Without an argument, the pull request that belongs to the current branch is selected", + args: { + name: "number | url | branch", + generators: ghGenerators.listPR, + }, + options: [ + { + name: "--add-assignee", + description: + 'Add assigned users by their login. Use "@me" to assign yourself', + args: { + name: "login", + }, + }, + { + name: "--add-label", + description: "Add labels by name", + args: { + name: "name", + }, + }, + { + name: "--add-project", + description: "Add the pull request to projects by name", + args: { + name: "name", + }, + }, + { + name: "--add-reviewer", + description: "Add reviewers by their login", + args: { + name: "login", + }, + }, + { + name: ["-B", "--base"], + description: "Change the base branch for this pull request", + args: { + name: "branch", + }, + }, + { + name: ["-b", "--body"], + description: "Set the new body", + args: { + name: "string", + }, + }, + { + name: ["-F", "--body-file"], + description: + 'Read body text from file (use "-" to read from standard input)', + args: { + name: "file", + }, + }, + { + name: ["-m", "--milestone"], + description: + "Edit the milestone the pull request belongs to by name", + args: { + name: "name", + }, + }, + { + name: "--remove-assignee", + description: + 'Remove assigned users by their login. Use "@me" to unassign yourself', + args: { + name: "login", + }, + }, + { + name: "--remove-label", + description: "Remove labels by name", + args: { + name: "name", + }, + }, + { + name: "--remove-project", + description: "Remove the pull request from projects by name", + args: { + name: "name", + }, + }, + { + name: "--remove-reviewer", + description: "Remove reviewers by their login", + args: { + name: "login", + }, + }, + { + name: ["-t", "--title"], + description: "Set the new title", + args: { + name: "string", + }, + }, + + ghOptions.all, + ], + }, + { + name: "comment", + description: "Create a new pr comment", + args: { + name: "number | url | branch", + generators: ghGenerators.listPR, + }, + options: [ + { + name: ["-b", "--body"], + insertValue: "-b '{cursor}'", + description: "Supply a body. Will prompt for one otherwise", + args: { + name: "message", + }, + }, + { name: ["-e", "--editor"], description: "Add body using editor" }, + { name: ["-w", "--web"], description: "Add body in browser" }, + ], + }, + { + name: "create", + description: "Create a pull request", + options: [ + { + name: ["-a", "--assignee"], + description: "Assign people by their login", + args: { + name: "login", + }, + }, + { + name: ["-B", "--base"], + description: "The branch into which you want your code merged", + args: { + name: "branch", + generators: ghGenerators.remoteBranches, + }, + }, + { + name: ["-b", "--body"], + insertValue: "-b '{cursor}'", + description: "Body for the pull request", + args: { + name: "body", + }, + }, + { + name: ["-d", "--draft"], + description: "Mark pull request as a draft", + }, + { + name: ["-f", "--fill"], + description: + "Do not prompt for title/body and just use commit info", + }, + { + name: ["-H", "--head"], + description: + "The branch that contains commits for your pull request (default: current branch)", + args: { + name: "branch", + }, + }, + { + name: ["-l", "--label"], + description: + "The branch that contains commits for your pull request (default: current branch)", + args: { + name: "branch", + }, + }, + { + name: ["-m", "--milestone"], + description: "Add the pull request to a milestone by name", + args: { + name: "name", + }, + }, + { + name: "--no-maintainer-edit", + description: + "Disable maintainer's ability to modify pull request", + }, + { + name: ["-p", "--project"], + description: "Add the pull request to projects by name", + args: { + name: "name", + }, + }, + { + name: "-recover", + description: "Recover input from a failed run of create", + args: { + name: "string", + }, + }, + { + name: ["-r", "--reviewer"], + description: + "Request reviews from people or teams by their handle", + args: { + name: "handle", + }, + }, + { + name: ["-t", "--title"], + description: "Title for the pull request", + args: { + name: "string", + }, + }, + { + name: ["-w", "--web"], + description: "Open the web browser to create a pull request", + }, + ], + }, + { + name: "diff", + description: "View changes in a pull request", + args: { + name: "number | url | branch", + generators: ghGenerators.listPR, + }, + options: [ + { + name: "--color", + description: "Use color in diff output: {always|never|auto}", + args: { + name: "choice", + }, + }, + ], + }, + { + name: "list", + description: "List and filter pull requests in this repository", + options: [ + { + name: ["-a", "--assignee"], + description: "Filter by assignee", + args: { + name: "string", + }, + }, + { + name: ["-B", "--base"], + description: "Filter by base branch", + args: { + name: "string", + }, + }, + { + name: ["-l", "--label"], + description: "Filter by labels", + args: { + name: "string", + }, + }, + { + name: ["-L", "--limit"], + description: "Maximum number of items to fetch", + args: { + name: "int", + }, + }, + { + name: ["-s", "--state"], + description: "Filter by state: {open|closed|merged|all}", + args: { + name: "string", + }, + }, + { + name: ["-w", "--web"], + description: "Open the browser to list the pull requests", + args: { + name: "string", + }, + }, + ], + }, + { + name: "merge", + description: "Merge a pull request", + args: { + name: "number | url | branch", + generators: ghGenerators.listPR, + }, + options: [ + { + name: ["-d", "--delete-branch"], + description: "Delete the local and remote branch after merge", + }, + { + name: ["-m", "--merge"], + description: "Merge the commits with the base branch", + }, + { + name: ["-r", "--rebase"], + description: "Rebase the commits onto the base branch", + }, + { + name: ["-s", "--squash"], + description: + "Squash the commits into one commit and merge it into the base branch", + }, + ], + }, + { + name: "ready", + description: "Mark a pull request as ready for review", + args: { + name: "number | url | branch", + generators: ghGenerators.listPR, + }, + }, + { + name: "reopen", + description: "Reopen a pull request", + args: { + name: "number | url | branch", + generators: ghGenerators.listPR, + }, + }, + { + name: "review", + description: "Add a review to a pull request", + args: { + name: "number | url | branch", + generators: ghGenerators.listPR, + }, + options: [ + { name: ["-a", "--approve"], description: "Approve pull request" }, + { + name: ["-b", "--body"], + description: "Specify the body of a review", + args: { + name: "string", + }, + }, + { + name: ["-c", "--comment"], + description: "Comment on a pull request", + }, + { + name: ["-r", "--request-changes"], + description: "Request changes on a pull request", + }, + ], + }, + { + name: "status", + description: "Show status of relevant pull requests", + }, + { + name: "view", + description: "View a pull request", + args: { + name: "number | url | branch", + generators: ghGenerators.listPR, + }, + options: [ + { + name: ["-c", "--comments"], + description: "View pull request comments", + }, + { + name: ["-w", "--web"], + description: "Open a pull request in the browser", + }, + ], + }, + ], + }, + { name: "release", description: "Manage GitHub releases" }, + { + name: "repo", + description: "Work with GitHub repositories", + subcommands: [ + { + name: "archive", + description: + "Archive a GitHub repository. With no argument, archives the current repository", + isDangerous: true, + args: { + name: "repository", + generators: ghGenerators.listRepositories, + isOptional: true, + }, + options: [ghOptions.confirm], + }, + { + name: "clone", + description: `Clone a GitHub repository locally. +If the "OWNER/" portion of the "OWNER/REPO" repository argument is omitted, it +defaults to the name of the authenticating user. +Pass additional 'git clone' flags by listing them after '--'`, + args: [ + { + name: "repository", + generators: [ + ghGenerators.listRepositories, + ghGenerators.listCustomRepositories, + ], + }, + { + name: "directory", + isOptional: true, + }, + ], + options: [ + ghOptions.cloneGitFlags, + { + name: ["-u", "--upstream-remote-name"], + description: + 'Upstream remote name when cloning a fork (default "upstream")', + args: { + name: "string", + }, + }, + ], + }, + { + name: "create", + description: `Create a new GitHub repository. +To create a repository interactively, use 'gh repo create' with no arguments. +To create a remote repository non-interactively, supply the repository name and one of '--public', '--private', or '--internal'. +Pass '--clone' to clone the new repository locally. +To create a remote repository from an existing local repository, specify the source directory with '--source'. +By default, the remote repository name will be the name of the source directory. +Pass '--push' to push any local commits to the new repository`, + args: { + name: "name", + }, + options: [ + ghOptions.confirm, + { + name: ["-d", "--description"], + description: "Description of the repository", + args: { + name: "string", + }, + }, + { + name: ["-h", "--homepage"], + description: "Repository home page URL", + args: { + name: "string", + }, + }, + { name: "--public", description: "Make the repository public" }, + { name: "--private", description: "Make the repository private" }, + { + name: "--internal", + description: "Make the repository internal", + }, + { + name: ["-p", "--template"], + description: + "Make the new repository based on a template repository", + args: { + name: "string", + }, + }, + { + name: ["-c", "--clone"], + description: "Clone the new repository to the current directory", + }, + { + name: "--disable-issues", + description: "Disable issues in the new repository", + }, + { + name: "--disable-wiki", + description: "Disable wiki in the new repository", + }, + { + name: ["-g", "--gitignore"], + description: "Specify a gitignore template for the repository", + args: { + name: "string", + }, + }, + { + name: ["-l", "--license"], + description: "Specify an Open Source License for the repository", + args: { + name: "string", + }, + }, + { + name: ["-r", "--remote"], + description: "Specify remote name for the new repository", + args: { + name: "string", + }, + }, + { + name: ["-s", "--source"], + description: "Specify path to local repository to use as source", + args: { + name: "string", + }, + }, + { + name: ["-t", "--team"], + description: + "The name of the organization team to be granted access", + args: { + name: "string", + }, + }, + { + name: "--include-all-branches", + description: "Include all branches from template repository", + }, + { + name: "--push", + description: "Push local commits to the new repository", + }, + { + name: "--add-readme", + description: "Add a README file to the new repository", + }, + ], + }, + { + name: "deploy-key", + description: "Manage deploy keys in a repository", + subcommands: [ + { + name: "add", + description: "Add a deploy key to a GitHub repository", + args: { + name: "key-file", + description: "Path to the public key file", + template: "filepaths", + }, + options: [ + { + name: ["-w", "--allow-write"], + description: "Allow write access for the key", + }, + { + name: ["-t", "--title"], + description: "Title of the new key", + args: { + name: "string", + }, + }, + ], + }, + { + name: "delete", + description: "Delete a deploy key from a GitHub repository", + args: { + name: "key-id", + description: "ID of the key to delete", + }, + }, + { + name: "list", + description: "List deploy keys in a GitHub repository", + }, + ], + options: [ + { + name: ["-R", "--repo"], + description: + "Select another repository using the `[HOST/]OWNER/REPO` format", + args: { + name: "[HOST/]OWNER/REPO", + }, + }, + ], + }, + { + name: "delete", + description: `Delete a GitHub repository. +With no argument, deletes the current repository. Otherwise, deletes the specified repository. +Deletion requires authorization with the "delete_repo" scope. +To authorize, run "gh auth refresh -s delete_repo"`, + isDangerous: true, + args: { + name: "repository", + generators: ghGenerators.listRepositories, + isOptional: true, + }, + options: [ghOptions.confirm], + }, + { + name: "edit", + description: "Edit repository settings", + args: { + name: "repository", + generators: ghGenerators.listRepositories, + isOptional: true, + }, + options: [ + ghOptions.clone, + { + name: "--add-topic", + description: "Add repository topic", + args: { + name: "topic names", + }, + }, + { + name: "--allow-forking", + description: "Allow forking of an organization repository", + }, + { + name: "--default-branch", + description: "Set the default branch name for the repository", + args: { + name: "branch name", + }, + }, + { + name: "--delete-branch-on-merge", + description: "Delete head branch when pull requests are merged", + }, + { + name: ["-d", "--description"], + description: "Description of the repository", + args: { + name: "description", + }, + }, + { + name: "--enable-auto-merge", + description: "Enable auto-merge functionality", + }, + { + name: "--enable-issues", + description: "Enable issues in the repository", + }, + { + name: "--enable-merge-commit", + description: "Enable merging pull requests via merge commit", + }, + { + name: "--enable-projects", + description: "Enable projects in the repository", + }, + { + name: "--enable-rebase-merge", + description: "Enable merging pull requests via rebase", + }, + { + name: "--enable-squash-merge", + description: "Enable merging pull requests via squashed commit", + }, + { + name: "--enable-wiki", + description: "Enable wiki in the repository", + }, + { + name: ["-h", "--homepage"], + description: "Repository home page URL", + args: { + name: "URL", + }, + }, + { + name: "--remove-topic", + description: "Remove repository topic", + args: { + name: "topic names", + }, + }, + { + name: "--template", + description: + "Make the repository available as a template repository", + }, + { + name: "--visibility", + description: + "Change the visibility of the repository to {public,private,internal}", + args: { + name: "string", + suggestions: ["public", "private", "internal"], + }, + }, + ], + }, + { + name: "fork", + description: `Create a fork of a repository. +With no argument, creates a fork of the current repository. Otherwise, forks +the specified repository. +By default, the new fork is set to be your 'origin' remote and any existing +origin remote is renamed to 'upstream'. To alter this behavior, you can set +a name for the new fork's remote with --remote-name. +Additional 'git clone' flags can be passed in by listing them after '--'`, + args: { + name: "repository", + generators: [ + ghGenerators.listRepositories, + ghGenerators.listCustomRepositories, + ], + }, + options: [ + ghOptions.cloneGitFlags, + { + name: "--clone", + description: "Clone the fork", + }, + { + name: "--remote", + description: "Add remote for fork {true|false}", + }, + { + name: "--remote-name", + description: + 'Specify a name for a fork\'s new remote. (default "origin")', + args: { + name: "string", + }, + }, + { + name: "--org", + description: "Create the fork in an organization", + args: { + name: "string", + }, + }, + { + name: "--fork-name", + description: "Rename the forked repository", + args: { + name: "string", + }, + }, + ], + }, + { + name: "list", + description: `List repositories owned by user or organization. +For more information about output formatting flags, see 'gh help formatting'`, + args: { + name: "owner", + isOptional: true, + }, + options: [ + { + name: "--visibility", + description: "Filter repositories by visibility", + args: { + name: "visibility", + suggestions: ["public", "private", "internal"], + }, + }, + { + name: "--archived", + description: "Show only archived repositories", + }, + { name: "--fork", description: "Show only forked repositories" }, + { + name: ["-l", "--language"], + description: "Filter by primary coding language", + }, + { + name: ["-L", "--limit"], + description: + "Maximum number of repositories to list (default 30)", + args: { + name: "string", + }, + }, + { + name: "--no-archived", + description: "Omit archived repositories", + }, + { name: "--source", description: "Show only non-forks" }, + { + name: ["-q", "--jq"], + description: "Filter JSON output using a jq expression", + }, + { + name: "--json", + description: "Output JSON with the specified fields", + }, + { + name: ["-t", "--template"], + description: "Format JSON output using a Go template", + }, + { + name: "--topic", + description: "Filter by topic", + args: { + name: "topic", + }, + }, + { + name: "--private", + description: "Show only private repositories", + deprecated: true, + }, + { + name: "--public", + description: "Show only public repositories", + deprecated: true, + }, + ], + }, + { + name: "rename", + description: `Rename a GitHub repository. +By default, this renames the current repository; otherwise renames the specified repository`, + args: { + name: "new-name", + isOptional: true, + }, + options: [ + ghOptions.confirm, + ghOptions.all, + { + name: ["-R", "--repo"], + description: + "Select another repository using the `[HOST/]OWNER/REPO` format", + args: { + name: "[HOST/]OWNER/REPO", + }, + }, + ], + }, + { + name: "set-default", + description: + "Sets the default remote repository to use when querying the GitHub API for the locally cloned repository", + args: { + name: "repository", + isOptional: true, + generators: [ + ghGenerators.listRepositories, + ghGenerators.listCustomRepositories, + ], + }, + options: [ + { + name: ["-u", "--unset"], + description: "Unset the current default repository", + }, + { + name: ["-v", "--view"], + description: "View the current default repository", + }, + ], + }, + { + name: "sync", + description: `Sync destination repository from source repository. Syncing uses the main branch +of the source repository to update the matching branch on the destination +repository so they are equal. A fast forward update will be used execept when the +'--force' flag is specified, then the two branches will +by synced using a hard reset. +Without an argument, the local repository is selected as the destination repository. +The source repository is the parent of the destination repository by default. +This can be overridden with the '--source' flag`, + args: { + name: "destination-repository", + isOptional: true, + }, + options: [ + { + name: ["-b", "--branch"], + description: "Branch to sync", + args: { + name: "branch name", + default: "main", + }, + }, + { + name: "--force", + description: + "Hard reset the branch of the destination repository to match the source repository", + }, + { + name: ["-s", "--source"], + description: "Source repository", + args: { + name: "source repository", + }, + }, + ], + }, + { + name: "view", + description: `Display the description and the README of a GitHub repository. +With no argument, the repository for the current directory is displayed. +With '--web', open the repository in a web browser instead. +With '--branch', view a specific branch of the repository. +For more information about output formatting flags, see 'gh help formatting'`, + args: { + name: "repository", + isOptional: true, + generators: [ + ghGenerators.listRepositories, + ghGenerators.listCustomRepositories, + ], + }, + options: [ + { + name: ["-b", "--branch"], + description: "View a specific branch of the repository", + args: { + name: "string", + }, + }, + { + name: ["-w", "--web"], + description: "Open a repository in the browser", + }, + { + name: ["-q", "--jq"], + description: "Filter JSON output using a jq expression", + args: { + name: "expression", + }, + }, + { + name: "--json", + description: "Output JSON with the specified fields", + args: { + name: "fields", + }, + }, + { + name: ["-t", "--template"], + description: "Format JSON output using a Go template", + args: { + name: "string", + }, + }, + ], + }, + ], + }, + { + name: "run", + description: "View details about workflow runs", + options: [ghOptions.all], + subcommands: [ + { + name: "download", + description: "Download artifacts generated by a workflow run", + args: { + name: "run-id", + }, + }, + { + name: "list", + description: "List recent workflow runs", + options: [ + ghOptions.all, + { + name: ["-L", "--limit"], + description: "Maximum number of runs to fetch (default 20)", + args: { + name: "int", + }, + }, + { + name: ["-w", "--workflow"], + description: "Filter runs by workflow", + args: { + name: "string", + }, + }, + ], + }, + { + name: "rerun", + description: "Rerun a failed run", + options: [ghOptions.all], + args: { + name: "run-id", + }, + }, + { + name: "view", + description: "View a summary of a workflow run", + options: [ + ghOptions.all, + { + name: "--exit-status", + description: "Exit with non-zero status if run failed", + }, + { + name: ["-j", "--job"], + description: "View a specific job ID from a run", + args: { + name: "string", + }, + }, + { + name: "--log", + description: "View full log for either a run or specific job", + }, + { + name: "--log-failed", + description: + "View the log for any failed steps in a run or specific job", + }, + { + name: ["-v", "--verbose"], + description: "Show job steps", + }, + { + name: ["-w", "--web"], + description: "Open run in the browser", + }, + ], + args: { + name: "run-id", + }, + }, + { + name: "watch", + description: "Watch a run until it completes, showing its progress", + options: [ + ghOptions.all, + { + name: "--exit-status", + description: "Exit with non-zero status if run fails", + }, + { + name: ["-i", "--interval"], + description: "Refresh interval in seconds (default 3)", + args: { + name: "int", + }, + }, + ], + }, + ], + }, + { + name: "secret", + description: "Manage GitHub secrets", + options: [ghOptions.all], + subcommands: [ + { + name: "list", + description: + "List secrets for a repository, environment, or organization", + options: [ + ghOptions.all, + { + name: ["-e", "--env"], + description: "List secrets for an environment", + args: { + name: "string", + }, + }, + { + name: ["-o", "--org"], + description: "List secrets for an environment", + args: { + name: "string", + }, + }, + ], + }, + { + name: "remove", + description: "Remove secrets", + options: [ghOptions.all, ghOptions.env, ghOptions.org], + }, + { + name: "set", + description: "Create or update secrets", + options: [ + ghOptions.all, + ghOptions.env, + ghOptions.org, + { + name: ["-b", "--body"], + description: + "A value for the secret. Reads from STDIN if not specified", + args: { + name: "string", + }, + }, + { + name: ["-v", "--visibility"], + description: + "Set visibility for an organization secret: all, `private`, or `selected` (default 'private')", + args: { + name: "string", + suggestions: [ + { name: "private" }, + { + name: "selected", + }, + { + name: "all", + }, + ], + }, + }, + ], + }, + ], + }, + { + name: "ssh-key", + description: "Manage SSH keys", + + subcommands: [ + { + name: "add", + description: "Add an SSH key to your GitHub account", + options: [ + ghOptions.all, + { + name: ["-t", "--title"], + description: "Title for the new key", + }, + ], + args: { + name: "", + template: "filepaths", + }, + }, + { + name: "list", + description: "Lists SSH keys in your GitHub account", + options: [ghOptions.all], + }, + ], + }, + { + name: "workflow", + description: "View details about GitHub Actions workflows", + options: [ghOptions.all], + subcommands: [ + { + name: "disable", + description: "Disable a workflow", + options: [ghOptions.all], + args: { + name: "[ | ]", + }, + }, + { + name: "enable", + description: "Enable a workflow", + options: [ghOptions.all], + args: { + name: "[ | ]", + }, + }, + { + name: "list", + description: "List workflows", + options: [ + ghOptions.all, + { + name: ["-a", "--all"], + description: "Show all workflows, including disabled workflows", + }, + { + name: ["-L", "--limit"], + description: "Show all workflows, including disabled workflows", + args: { + name: "int", + description: + "Maximum number of workflows to fetch (default 50)", + }, + }, + ], + args: { + name: "[ | ]", + }, + }, + { + name: "run", + description: "Run a workflow by creating a workflow_dispatch event", + options: [ + ghOptions.all, + { + name: ["-F", "--field"], + description: + "Add a string parameter in key=value format, respecting @ syntax", + args: { + name: "key=value", + }, + }, + { + name: "--json", + description: "Read workflow inputs as JSON via STDIN", + }, + { + name: ["-f", "--raw-field"], + description: "Add a string parameter in key=value format", + args: { + name: "key=value", + }, + }, + { + name: ["-r", "--ref"], + description: + "The branch or tag name which contains the version of the workflow file you'd like to run", + args: { + name: "string", + }, + }, + ], + args: { + name: "[ | ]", + }, + }, + { + name: "view", + description: "View the summary of a workflow", + options: [ + ghOptions.all, + { + name: ["-r", "--ref"], + description: + "The branch or tag name which contains the version of the workflow file you'd like to view", + args: { + name: "string", + }, + }, + { + name: ["-w", "--web"], + description: "Open workflow in the browser", + }, + { + name: ["-y", "--yaml"], + description: "View the workflow yaml file", + }, + ], + args: [ + { + name: "workflow-id", + }, + { + name: "workflow-name", + }, + { + name: "filename", + template: "filepaths", + }, + ], + }, + ], + }, + { + name: ["codespace", "cs"], + description: "Connect to and manage codespaces", + subcommands: [ + { + name: "code", + description: "Open a codespace in Visual Studio Code", + options: [ + codespaceOption, + { + name: "--insiders", + description: "Use the insiders version of Visual Studio Code", + }, + { + name: ["-w", "--web"], + description: "Use the web version of Visual Studio Code", + }, + ], + }, + { + name: "cp", + description: + "The cp command copies files between the local and remote file systems", + options: [ + codespaceOption, + { + name: ["-e", "--expand"], + description: "Expand remote file names on remote shell", + }, + { + name: ["-p", "--profile"], + description: "Name of the SSH profile to use", + args: { + name: "string", + }, + }, + { + name: ["-r", "--recursive"], + description: "Recursively copy directories", + }, + ], + args: [ + { + name: "sources", + isVariadic: true, + }, + { + name: "dest", + }, + ], + }, + { + name: "create", + description: "Create a codespace", + options: [ + { + name: ["-b", "--branch"], + description: "Repository branch", + }, + { + name: "--default-permissions", + description: + "Do not prompt to accept additional permissions requested by the codespace", + }, + { + name: "--devcontainer-path", + description: + "Path to the devcontainer.json file to use when creating codespace", + args: { + generators: filepaths({ extensions: ["json"] }), + }, + }, + { + name: ["-d", "--display-name"], + description: "Display name for the codespace", + args: { + name: "string", + }, + }, + { + name: "--idle-timeout", + description: "Allowed inactivity before codespace is stopped", + args: { + name: "duration", + description: "Example: '10m', '1h'", + }, + }, + { + name: ["-l", "--location"], + description: "Determined automatically if not provided", + args: { + name: "location", + suggestions: [ + "EastUs", + "SouthEastAsia", + "WestEurope", + "WestUs2", + ], + }, + }, + { + name: ["-m", "--machine"], + description: "Hardware specifications for the VM", + args: { + name: "string", + }, + }, + { + name: ["-R", "--repo"], + description: "Repository name with owner: user/repo", + args: { + name: "string", + }, + }, + { + name: "--retention-period", + description: + "Allowed time after shutting down before the codespace is automatically deleted (maximum 30 days)", + args: { + name: "duration", + description: "Example: '10m', '1h'", + }, + }, + { + name: ["-s", "--status"], + description: "Show status of post-create command and dotfiles", + }, + ], + }, + { + name: "delete", + description: + "Delete codespaces based on selection criteria. All codespaces for the authenticated user can be deleted, as well as codespaces for a specific repository. Alternatively, only codespaces older than N days can be deleted. Organization administrators may delete any codespace billed to the organization", + options: [ + codespaceOption, + { + name: "--all", + description: "Delete all codespaces", + }, + { + name: "--days", + description: "Delete codespaces older than N days", + args: { + name: "N days", + }, + }, + { + name: ["-f", "--force"], + description: + "Skip confirmation for codespaces that contain unsaved changes", + isDangerous: true, + }, + { + name: ["-o", "--org"], + description: "The login handle of the organization (admin-only)", + args: { + name: "login", + }, + }, + { + name: ["-r", "--repo"], + description: "Delete codespaces for a repository", + args: { + name: "repository", + }, + }, + { + name: ["-u", "--user"], + description: "The username to delete codespaces for", + args: { + name: "username", + }, + }, + ], + }, + { + name: "edit", + description: "Edit a codespace", + options: [ + codespaceOption, + { + name: ["-d", "--display-name"], + description: "Set the display name", + args: { + name: "string", + }, + }, + { + name: ["-m", "--machine"], + description: "Set hardware specifications for the VM", + args: { + name: "string", + }, + }, + ], + }, + { + name: "jupyter", + description: "Open a codespace in JupyterLab", + options: [codespaceOption], + }, + { + name: "list", + description: + "List codespaces of the authenticated user. Alternatively, organization administrators may list all codespaces billed to the organization", + options: [ + { + name: ["-q", "--jq"], + description: "Filter JSON output using a jq expression", + args: { + name: "expression", + }, + }, + { + name: "--json", + description: "Output JSON with the specified fields", + args: { + name: "fields", + }, + }, + { + name: ["-L", "--limit"], + description: "Maximum number of codespaces to list", + args: { + name: "int", + default: "30", + }, + }, + { + name: ["-o", "--org"], + description: + "The login handle of the organization to list codespaces for (admin-only)", + args: { + name: "login", + }, + }, + { + name: ["-R", "--repo"], + description: "Repository name with owner: user/repo", + args: { + name: "string", + }, + }, + { + name: ["-t", "--template"], + description: + "Format JSON output using a Go template; see 'gh help formatting'", + args: { + name: "string", + }, + }, + { + name: ["-u", "--user"], + description: "The username to list codespaces for", + args: { + name: "string", + }, + }, + ], + }, + { + name: "logs", + description: "Access codespace logs", + options: [ + codespaceOption, + { + name: ["-f", "--follow"], + description: "Tail and follow the logs", + }, + ], + }, + { + name: "ports", + description: "List ports in a codespace", + subcommands: [ + { + name: "forward", + description: "Forward ports", + options: [codespaceOption], + args: { + generators: keyValue({ separator: ":", cache: true }), + isVariadic: true, + }, + }, + { + name: "visibility", + description: "Change the visibility of the forwarded port", + options: [codespaceOption], + args: { + generators: keyValue({ + separator: ":", + values: ["public", "private", "org"], + cache: true, + }), + isVariadic: true, + }, + }, + ], + options: [ + codespaceOption, + { + name: ["-q", "--jq"], + description: "Filter JSON output using a jq expression", + args: { + name: "expression", + }, + }, + { + name: "--json", + description: "Output JSON with the specified fields", + args: { + name: "fields", + }, + }, + { + name: ["-t", "--template"], + description: + "Format JSON output using a Go template; see 'gh help formatting'", + args: { + name: "string", + }, + }, + ], + }, + { + name: "rebuild", + description: "Rebuild a codespace", + options: [codespaceOption], + }, + { + name: "ssh", + description: "SSH into a codespace", + options: [ + codespaceOption, + { + name: "--config", + description: "Write OpenSSH configuration to stdout", + }, + { + name: ["-d", "--debug"], + description: "Log debug data to a file", + }, + { + name: "--debug-file", + description: "Path of the file log to", + args: { + name: "file", + template: "filepaths", + suggestCurrentToken: true, + }, + }, + { + name: "--profile", + description: "Name of the SSH profile to use", + args: { + name: "string", + }, + }, + { + name: "--server-port", + description: "SSH server port number (0 => pick unused)", + args: { + name: "int", + }, + }, + ], + args: { + name: "command", + isCommand: true, + isOptional: true, + }, + }, + { + name: "stop", + description: "Stop running a codespace", + options: [ + codespaceOption, + { + name: ["-o", "--org"], + description: "The login handle of the organization (admin-only)", + args: { + name: "login", + }, + }, + { + name: ["-u", "--user"], + description: "The username to delete codespaces for", + args: { + name: "username", + }, + }, + ], + }, + ], + }, + { + name: "project", + description: "Manage projects", + subcommands: [ + { + name: "create", + description: "Create a project", + options: [ + { + name: "--title", + description: "Title for the project", + args: { name: "title" }, + }, + { + name: "--owner", + description: 'Login of the owner. Use "@me" for the current user', + args: { name: "owner" }, + }, + { + name: "--format", + description: "Output format: {json}", + args: { name: "body" }, + }, + ], + }, + { + name: "edit", + description: "Edit a project", + options: [ + { + name: "--title", + description: "New title of the project", + args: { name: "title" }, + }, + { + name: ["-d", "--description"], + description: "New description of the project", + args: { name: "description" }, + }, + { + name: "--owner", + description: 'Login of the owner. Use "@me" for the current user', + args: { name: "owner" }, + }, + { + name: "--readme", + description: "New readme for the project", + args: { name: "readme" }, + }, + { + name: "--visibility", + description: "Change project visibility", + args: { name: "visibility", suggestions: ["PUBLIC", "PRIVATE"] }, + }, + ], + }, + { + name: "list", + description: "List projects", + options: [ + { + name: "--closed", + description: "Include closed projects", + args: { name: "closed" }, + }, + { + name: "--owner", + description: "Login of the owner", + args: { name: "owner" }, + }, + { + name: ["-L", "--limit"], + description: "Maximum number of projects to fetch", + args: { name: "int", default: "30" }, + }, + { + name: "--format", + description: "Output format: {json}", + args: { name: "format" }, + }, + { + name: "--web", + description: "Open projects list in the browser", + }, + ], + }, + { + name: "delete", + description: "Delete a project", + options: [ + { + name: "--owner", + description: "Login of the owner. Use @me for the current user", + args: { name: "owner" }, + }, + { + name: "--format", + description: "Output format: {json}", + args: { name: "format" }, + }, + ], + }, + { + name: "close", + description: "Close a project", + options: [ + { + name: "--owner", + description: "Login of the owner. Use @me for the current user", + args: { name: "owner" }, + }, + { + name: "--format", + description: "Output format: {json}", + args: { name: "format" }, + }, + { + name: "--undo", + description: "Reopen a closed project", + }, + ], + }, + { + name: "view", + description: "View a project", + options: [ + { + name: "--owner", + description: "Login of the owner. Use @me for the current user", + args: { name: "owner" }, + }, + { + name: "--format", + description: "Output format: {json}", + args: { name: "format" }, + }, + { + name: ["-w", "--web"], + description: "Open project in the browser", + }, + ], + }, + { + name: "copy", + description: "Copy a project", + options: [ + { + name: "--title", + description: "Title for the new project", + args: { name: "title" }, + }, + { + name: "--target-owner", + description: + "Login of the target owner. Use @me for the current user", + args: { name: "target-owner" }, + }, + { + name: "--source-owner", + description: + "Login of the source owner. Use @me for the current user", + args: { name: "source-owner" }, + }, + { + name: "--format", + description: "Output format: {json}", + args: { name: "format" }, + }, + { + name: "--drafts", + description: "Include draft issues when copying", + }, + ], + }, + { + name: "field-create", + description: "Create a project field", + options: [ + { + name: "--name", + description: "Name of the field", + args: { name: "name" }, + }, + { + name: "--data-type", + description: "DataType of the new field", + args: { + name: "data-type", + suggestions: ["TEXT", "SINGLE_SELECT", "DATE", "NUMBER"], + }, + }, + { + name: "--owner", + description: "Login of the owner. Use @me for the current user", + args: { name: "owner" }, + }, + { + name: "--format", + description: "Output format: {json}", + args: { name: "format" }, + }, + { + name: "--single-select-options", + description: "Options for SINGLE_SELECT data type", + args: { name: "single-select-options" }, + }, + ], + }, + { + name: "field-delete", + description: "Delete a project field", + options: [ + { + name: "--id", + description: "ID of the field to delete", + args: { name: "id" }, + }, + { + name: "--format", + description: "Output format: {json}", + }, + ], + }, + { + name: "field-list", + description: "List project fields", + options: [ + { + name: "--owner", + description: "Login of the owner. Use @me for the current user", + args: { name: "owner" }, + }, + { + name: "--format", + description: "Output format: {json}", + }, + { + name: ["-L", "--limit"], + description: "Maximum number of fields to fetch", + args: { name: "int", default: "30" }, + }, + ], + }, + { + name: "item-create", + description: "Create a draft issue item in a project", + options: [ + { + name: "--title", + description: "Title for the draft issue", + args: { name: "title" }, + }, + { + name: "--body", + description: "Body for the draft issue", + args: { name: "body" }, + }, + { + name: "--format", + description: "Output format: {json}", + args: { name: "format" }, + }, + { + name: "--owner", + description: "Login of the owner. Use @me for the current user", + args: { name: "owner" }, + }, + ], + }, + { + name: "item-edit", + description: "Edit a project item", + options: [ + { + name: "--id", + description: "ID of the item to edit", + args: { name: "id" }, + }, + { + name: "--project-id", + description: "ID of the project to which the field belongs to", + args: { name: "project-id" }, + }, + { + name: "--title", + description: "Title of the draft issue item", + args: { name: "title" }, + }, + { + name: "--body", + description: "Body of the draft issue item", + args: { name: "body" }, + }, + { + name: "--format", + description: "Output format: {json}", + args: { name: "format" }, + }, + { + name: "--field-id", + description: "ID of the field to update", + args: { name: "field-id" }, + }, + { + name: "--iteration-id", + description: "ID of the iteration value to set on the field", + args: { name: "iteration-id" }, + }, + { + name: "--text", + description: "Text value for the field", + args: { name: "text" }, + }, + { + name: "--number", + description: "Number value for the field", + args: { name: "number" }, + }, + { + name: "--single-select-option-id", + description: + "ID of the single select option value to set on the field", + args: { name: "single-select-option-id" }, + }, + { + name: "--date", + description: "Date value for the field (YYYY-MM-DD)", + args: { name: "date" }, + }, + ], + }, + { + name: "item-delete", + description: "Delete a project item", + options: [ + { + name: "--id", + description: "ID of the item to delete", + args: { name: "id" }, + }, + { + name: "--format", + description: "Output format: {json}", + args: { name: "format" }, + }, + { + name: "--owner", + description: "Login of the owner. Use @me for the current user", + args: { name: "owner" }, + }, + ], + }, + { + name: "item-list", + description: "List project items", + options: [ + { + name: "--format", + description: "Output format: {json}", + args: { name: "format" }, + }, + { + name: ["-L", "--limit"], + description: "Maximum number of items to fetch", + args: { name: "int", default: "30" }, + }, + { + name: "--owner", + description: "Login of the owner. Use @me for the current user", + args: { name: "owner" }, + }, + ], + }, + { + name: "item-add", + description: "Add a pull request or an issue to a project", + options: [ + { + name: "--url", + description: + "URL of the issue or pull request to add to the project", + args: { name: "url" }, + }, + { + name: "--owner", + description: "Login of the owner. Use @me for the current user", + args: { name: "owner" }, + }, + { + name: "--format", + description: "Output format: {json}", + args: { name: "format" }, + }, + ], + }, + { + name: "item-archive", + description: "Archive a project item", + options: [ + { + name: "--id", + description: "ID of the item to archive", + args: { name: "id" }, + }, + { + name: "--format", + description: "Output format: {json}", + args: { name: "format" }, + }, + { + name: "--owner", + description: "Login of the owner. Use @me for the current user", + args: { name: "owner" }, + }, + { + name: "--undo", + description: "Unarchive a project item", + }, + ], + }, + ], + }, + ], + options: [ + { + name: "--help", + description: "Show help for command", + isPersistent: true, + }, + ], +}; + +export default completionSpec; diff --git a/extensions/terminal-suggest/src/completions/upstream/git.ts b/extensions/terminal-suggest/src/completions/git.ts similarity index 93% rename from extensions/terminal-suggest/src/completions/upstream/git.ts rename to extensions/terminal-suggest/src/completions/git.ts index 8f84acf54f488..68cc6fda2d7b7 100644 --- a/extensions/terminal-suggest/src/completions/upstream/git.ts +++ b/extensions/terminal-suggest/src/completions/git.ts @@ -1,3 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* eslint-disable local/code-no-unexternalized-strings */ + +import * as vscode from 'vscode'; + function ai(...args: any[]): undefined { return undefined; } const filterMessages = (out: string): string => { @@ -29,7 +38,7 @@ const postProcessTrackedFiles: Fig.Generator["postProcess"] = ( try { ext = file.split(".").slice(-1)[0]; - } catch (e) {} + } catch (e) { } if (file.endsWith("/")) { ext = "folder"; @@ -53,72 +62,107 @@ interface PostProcessBranchesOptions { const postProcessBranches = (options: PostProcessBranchesOptions = {}): Fig.Generator["postProcess"] => - (out) => { - const { insertWithoutRemotes = false } = options; + (out) => { + const { insertWithoutRemotes = false } = options; - const output = filterMessages(out); + const output = filterMessages(out); - if (output.startsWith("fatal:")) { - return []; - } + if (output.startsWith("fatal:")) { + return []; + } + + const seen = new Set(); + return output + .split("\n") + .filter((line) => line.trim() && !line.trim().startsWith("HEAD")) + .map((branch) => { + // Parse the format: branchName|author|hash|subject|timeAgo + const parts = branch.split("|"); + if (parts.length < 5) { + // Fallback to old parsing if format doesn't match + let name = branch.trim(); + const oldParts = branch.match(/\S+/g); + if (oldParts && oldParts.length > 1) { + if (oldParts[0] === "*") { + if (branch.includes("HEAD detached")) { + return null; + } + return { + name: branch.replaceAll("*", "").trim(), + description: "Current branch", + priority: 100, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}` + }; + } else if (oldParts[0] === "+") { + name = branch.replaceAll("+", "").trim(); + } + } + + let description = "Branch"; - const seen = new Set(); - return output - .split("\n") - .filter((line) => !line.trim().startsWith("HEAD")) - .map((branch) => { - let name = branch.trim(); - const parts = branch.match(/\S+/g); - if (parts && parts.length > 1) { - if (parts[0] === "*") { - // We are in a detached HEAD state - if (branch.includes("HEAD detached")) { - return null; + if (insertWithoutRemotes && name.startsWith("remotes/")) { + name = name.slice(name.indexOf("/", 8) + 1); + description = "Remote branch"; } - // Current branch + + const space = name.indexOf(" "); + if (space !== -1) { + name = name.slice(0, space); + } + return { - name: branch.replace("*", "").trim(), - description: "Current branch", - priority: 100, - icon: "⭐️", + name, + description, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`, + priority: 75, }; - } else if (parts[0] === "+") { - // Branch checked out in another worktree. - name = branch.replace("+", "").trim(); } - } - let description = "Branch"; + let name = parts[0].trim(); + const author = parts[1].trim(); + const hash = parts[2].trim(); + const subject = parts[3].trim(); + const timeAgo = parts[4].trim(); - if (insertWithoutRemotes && name.startsWith("remotes/")) { - name = name.slice(name.indexOf("/", 8) + 1); - description = "Remote branch"; - } + const description = `${timeAgo} • ${author} • ${hash} • ${subject}`; + const priority = 75; - const space = name.indexOf(" "); - if (space !== -1) { - name = name.slice(0, space); - } + if (insertWithoutRemotes && name.startsWith("remotes/")) { + name = name.slice(name.indexOf("/", 8) + 1); + } - return { - name, - description, - icon: "fig://icon?type=git", - priority: 75, - }; - }) - .filter((suggestion) => { - if (!suggestion) return false; - if (seen.has(suggestion.name)) return false; - seen.add(suggestion.name); - return true; - }); - }; + return { + name, + description, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmBranch}`, + priority, + }; + }) + .filter((suggestion) => { + if (!suggestion) { + return false; + } + if (seen.has(suggestion.name)) { + return false; + } + seen.add(suggestion.name); + return true; + }); + }; + +// Common git for-each-ref arguments for branch queries with commit details +const gitBranchForEachRefArgs = [ + "git", + "--no-optional-locks", + "for-each-ref", + "--sort=-committerdate", + "--format=%(refname:short)|%(authorname)|%(objectname:short)|%(subject)|%(committerdate:relative)", +] as const; -export const gitGenerators: Record = { +export const gitGenerators = { // Commit history commits: { - script: ["git", "--no-optional-locks", "log", "--oneline"], + script: ["git", "--no-optional-locks", "log", "--oneline", "-n", "1000"], postProcess: function (out) { const output = filterMessages(out); @@ -126,15 +170,21 @@ export const gitGenerators: Record = { return []; } - return output.split("\n").map((line) => { + const lines = output.split("\n"); + const firstLine = lines.length > 0 ? lines[0] : undefined; + const hashLength = + firstLine && firstLine.length > 0 ? firstLine.indexOf(" ") : 7; + const descriptionStart = hashLength + 1; + + return lines.map((line) => { return { - name: line.substring(0, 7), - icon: "fig://icon?type=node", - description: line.substring(7), + name: line.substring(0, hashLength), + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmCommit}`, + description: line.substring(descriptionStart), }; }); }, - }, + } satisfies Fig.Generator, // user aliases aliases: { @@ -161,7 +211,7 @@ export const gitGenerators: Record = { return true; }); }, - }, + } satisfies Fig.Generator, revs: { script: ["git", "rev-list", "--all", "--oneline"], @@ -175,12 +225,12 @@ export const gitGenerators: Record = { return output.split("\n").map((line) => { return { name: line.substring(0, 7), - icon: "fig://icon?type=node", + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmCommit}`, description: line.substring(7), }; }); }, - }, + } satisfies Fig.Generator, // Saved stashes // TODO: maybe only print names of stashes @@ -198,11 +248,11 @@ export const gitGenerators: Record = { // account for conventional commit messages name: file.split(":").slice(2).join(":"), insertValue: file.split(":")[0], - icon: `fig://icon?type=node`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmStash}`, }; }); }, - }, + } satisfies Fig.Generator, // Tree-ish // This needs to be fleshed out properly.... @@ -228,28 +278,22 @@ export const gitGenerators: Record = { }; }); }, - }, + } satisfies Fig.Generator, // All branches remoteLocalBranches: { script: [ - "git", - "--no-optional-locks", - "branch", - "-a", - "--no-color", - "--sort=-committerdate", + ...gitBranchForEachRefArgs, + "refs/heads/", + "refs/remotes/", ], postProcess: postProcessBranches({ insertWithoutRemotes: true }), }, localBranches: { script: [ - "git", - "--no-optional-locks", - "branch", - "--no-color", - "--sort=-committerdate", + ...gitBranchForEachRefArgs, + "refs/heads/", ], postProcess: postProcessBranches({ insertWithoutRemotes: true }), }, @@ -291,7 +335,7 @@ export const gitGenerators: Record = { ); } }, - }, + } satisfies Fig.Generator, remotes: { script: ["git", "--no-optional-locks", "remote", "-v"], @@ -308,27 +352,14 @@ export const gitGenerators: Record = { }, {}); return Object.keys(remoteURLs).map((remote) => { - const url = remoteURLs[remote]; - let icon = "box"; - if (url.includes("github.com")) { - icon = "github"; - } - - if (url.includes("gitlab.com")) { - icon = "gitlab"; - } - - if (url.includes("heroku.com")) { - icon = "heroku"; - } return { name: remote, - icon: `fig://icon?type=${icon}`, + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmRemote}`, description: "Remote", }; }); }, - }, + } satisfies Fig.Generator, tags: { script: [ @@ -341,10 +372,10 @@ export const gitGenerators: Record = { postProcess: function (output) { return output.split("\n").map((tag) => ({ name: tag, - icon: "🏷️", + icon: `vscode://icon?type=${vscode.TerminalCompletionItemKind.ScmTag}` })); }, - }, + } satisfies Fig.Generator, // Files for staging files_for_staging: { @@ -426,7 +457,7 @@ export const gitGenerators: Record = { let ext = ""; try { ext = file.split(".").slice(-1)[0]; - } catch (e) {} + } catch (e) { } if (file.endsWith("/")) { ext = "folder"; @@ -444,40 +475,53 @@ export const gitGenerators: Record = { }), ]; }, - }, + } satisfies Fig.Generator, getStagedFiles: { - script: [ - "bash", - "-c", - "git --no-optional-locks status --short | sed -ne '/^M /p' -e '/A /p'", - ], - postProcess: postProcessTrackedFiles, - }, + script: ["git", "--no-optional-locks", "status", "--short"], + postProcess: (out, context) => { + const output = filterMessages(out); + + if (output.startsWith("fatal:")) { + return []; + } + + const filteredLines = output.split("\n").filter(line => { + return line.match(/^M /) || line.match(/A /); + }); + + return postProcessTrackedFiles(filteredLines.join("\n"), context); + }, + } satisfies Fig.Generator, getUnstagedFiles: { script: ["git", "--no-optional-locks", "diff", "--name-only"], splitOn: "\n", - }, + } satisfies Fig.Generator, getChangedTrackedFiles: { - script: function (context) { + script: ["git", "--no-optional-locks", "status", "--short"], + postProcess: (out, context) => { + const output = filterMessages(out); + + if (output.startsWith("fatal:")) { + return []; + } + + let filteredLines; if (context.includes("--staged") || context.includes("--cached")) { - return [ - "bash", - "-c", - `git --no-optional-locks status --short | sed -ne '/^M /p' -e '/A /p'`, - ]; + filteredLines = output.split("\n").filter(line => { + return line.match(/^M /) || line.match(/A /); + }); } else { - return [ - "bash", - "-c", - `git --no-optional-locks status --short | sed -ne '/M /p' -e '/A /p'`, - ]; + filteredLines = output.split("\n").filter(line => { + return line.match(/M /) || line.match(/A /); + }); } + + return postProcessTrackedFiles(filteredLines.join("\n"), context); }, - postProcess: postProcessTrackedFiles, - }, + } satisfies Fig.Generator, }; const configSuggestions: Fig.Suggestion[] = [ @@ -3851,7 +3895,7 @@ const addOptions: Fig.Option[] = [ { name: ["-n", "--dry-run"], description: - "Don’t actually add the file(s), just show if they exist and/or will be ignored", + "Don't actually add the file(s), just show if they exist and/or will be ignored", }, { name: ["-v", "--verbose"], description: "Be verbose" }, { @@ -3861,7 +3905,7 @@ const addOptions: Fig.Option[] = [ { name: ["-i", "--interactive"], description: - "Add modified contents in the working tree interactively to the index. Optional path arguments may be supplied to limit operation to a subset of the working tree. See “Interactive mode” for details", + "Add modified contents in the working tree interactively to the index. Optional path arguments may be supplied to limit operation to a subset of the working tree. See \"Interactive mode\" for details", }, { name: ["-p", "--patch"], @@ -3896,7 +3940,7 @@ const addOptions: Fig.Option[] = [ { name: "--refresh", description: - "Don’t add the file(s), but only refresh their stat() information in the index", + "Don't add the file(s), but only refresh their stat() information in the index", }, { name: "--ignore-errors", @@ -3952,7 +3996,7 @@ const addOptions: Fig.Option[] = [ const headSuggestions = [ { name: "HEAD", - icon: "🔻", + icon: "🔻", // allow-any-unicode-next-line description: "The most recent commit", }, { @@ -4105,7 +4149,7 @@ const completionSpec: Fig.Spec = { }, { name: "--html-path", - description: "Print Git’s HTML documentation path", + description: "Print Git's HTML documentation path", }, { name: "--man-path", @@ -4918,7 +4962,7 @@ const completionSpec: Fig.Spec = { suggestCurrentToken: true, suggestions: configSuggestions.map((suggestion) => ({ ...suggestion, - icon: "⚙️", + icon: "⚙️", // allow-any-unicode-next-line })), generators: { script: ["git", "config", "--get-regexp", ".*"], @@ -4937,7 +4981,7 @@ const completionSpec: Fig.Spec = { line.startsWith("remote.") || !configSuggestions.find(({ name }) => line === name) ) - .map((name) => ({ name, icon: "⚙️" })), + .map((name) => ({ name, icon: "⚙️" })), // allow-any-unicode-next-line }, }, { @@ -4961,7 +5005,7 @@ const completionSpec: Fig.Spec = { { name: "--keep-base", description: - "Set the starting point at which to create the new commits to the merge base of . Running git rebase --keep-base is equivalent to running git rebase --onto …​ . This option is useful in the case where one is developing a feature on top of an upstream branch. While the feature is being worked on, the upstream branch may advance and it may not be the best idea to keep rebasing on top of the upstream but to keep the base commit as-is. Although both this option and --fork-point find the merge base between and , this option uses the merge base as the starting point on which new commits will be created, whereas --fork-point uses the merge base to determine the set of commits which will be rebased", + "Set the starting point at which to create the new commits to the merge base of . Running git rebase --keep-base is equivalent to running git rebase --onto ... . This option is useful in the case where one is developing a feature on top of an upstream branch. While the feature is being worked on, the upstream branch may advance and it may not be the best idea to keep rebasing on top of the upstream but to keep the base commit as-is. Although both this option and --fork-point find the merge base between and , this option uses the merge base as the starting point on which new commits will be created, whereas --fork-point uses the merge base to determine the set of commits which will be rebased", }, { name: "--continue", @@ -4995,12 +5039,12 @@ const completionSpec: Fig.Spec = { { name: "--no-keep-empty", description: - "Do not keep commits that start empty before the rebase (i.e. that do not change anything from its parent) in the result. The default is to keep commits which start empty, since creating such commits requires passing the --allow-empty override flag to git commit, signifying that a user is very intentionally creating such a commit and thus wants to keep it. Usage of this flag will probably be rare, since you can get rid of commits that start empty by just firing up an interactive rebase and removing the lines corresponding to the commits you don’t want. This flag exists as a convenient shortcut, such as for cases where external tools generate many empty commits and you want them all removed. For commits which do not start empty but become empty after rebasing, see the --empty flag", + "Do not keep commits that start empty before the rebase (i.e. that do not change anything from its parent) in the result. The default is to keep commits which start empty, since creating such commits requires passing the --allow-empty override flag to git commit, signifying that a user is very intentionally creating such a commit and thus wants to keep it. Usage of this flag will probably be rare, since you can get rid of commits that start empty by just firing up an interactive rebase and removing the lines corresponding to the commits you don't want. This flag exists as a convenient shortcut, such as for cases where external tools generate many empty commits and you want them all removed. For commits which do not start empty but become empty after rebasing, see the --empty flag", }, { name: "--keep-empty", description: - "Keep commits that start empty before the rebase (i.e. that do not change anything from its parent) in the result. The default is to keep commits which start empty, since creating such commits requires passing the --allow-empty override flag to git commit, signifying that a user is very intentionally creating such a commit and thus wants to keep it. Usage of this flag will probably be rare, since you can get rid of commits that start empty by just firing up an interactive rebase and removing the lines corresponding to the commits you don’t want. This flag exists as a convenient shortcut, such as for cases where external tools generate many empty commits and you want them all removed. For commits which do not start empty but become empty after rebasing, see the --empty flag", + "Keep commits that start empty before the rebase (i.e. that do not change anything from its parent) in the result. The default is to keep commits which start empty, since creating such commits requires passing the --allow-empty override flag to git commit, signifying that a user is very intentionally creating such a commit and thus wants to keep it. Usage of this flag will probably be rare, since you can get rid of commits that start empty by just firing up an interactive rebase and removing the lines corresponding to the commits you don't want. This flag exists as a convenient shortcut, such as for cases where external tools generate many empty commits and you want them all removed. For commits which do not start empty but become empty after rebasing, see the --empty flag", }, { name: "--reapply-cherry-picks", @@ -5208,12 +5252,12 @@ const completionSpec: Fig.Spec = { { name: "--autosquash", description: - "When the commit log message begins with 'squash! …​' (or 'fixup! …​'), and there is already a commit in the todo list that matches the same ..., automatically modify the todo list of rebase -i so that the commit marked for squashing comes right after the commit to be modified, and change the action of the moved commit from pick to squash (or fixup). A commit matches the ... if the commit subject matches, or if the ... refers to the commit’s hash. As a fall-back, partial matches of the commit subject work, too. The recommended way to create fixup/squash commits is by using the --fixup/--squash options of git-commit[1]", + "When the commit log message begins with 'squash!' (or 'fixup!'), and there is already a commit in the todo list that matches the same ..., automatically modify the todo list of rebase -i so that the commit marked for squashing comes right after the commit to be modified, and change the action of the moved commit from pick to squash (or fixup). A commit matches the ... if the commit subject matches, or if the ... refers to the commit's hash. As a fall-back, partial matches of the commit subject work, too. The recommended way to create fixup/squash commits is by using the --fixup/--squash options of git-commit[1]", }, { name: "--no-autosquash", description: - "When the commit log message begins with 'squash! …' (or 'fixup! …'), and there is already a commit in the todo list that matches the same ..., automatically modify the todo list of rebase -i so that the commit marked for squashing comes right after the commit to be modified, and change the action of the moved commit from pick to squash (or fixup). A commit matches the ... if the commit subject matches, or if the ... refers to the commit’s hash. As a fall-back, partial matches of the commit subject work, too. The recommended way to create fixup/squash commits is by using the --fixup/--squash options of git-commit[1]", + "When the commit log message begins with 'squash!' (or 'fixup!'), and there is already a commit in the todo list that matches the same ..., automatically modify the todo list of rebase -i so that the commit marked for squashing comes right after the commit to be modified, and change the action of the moved commit from pick to squash (or fixup). A commit matches the ... if the commit subject matches, or if the ... refers to the commit's hash. As a fall-back, partial matches of the commit subject work, too. The recommended way to create fixup/squash commits is by using the --fixup/--squash options of git-commit[1]", }, { name: "--autostash", @@ -5482,7 +5526,7 @@ const completionSpec: Fig.Spec = { { name: ["-n", "--dry-run"], description: - "Don’t actually remove anything, just show what would be done", + "Don't actually remove anything, just show what would be done", }, { name: ["-q", "--quiet"], @@ -5500,7 +5544,7 @@ const completionSpec: Fig.Spec = { { name: "-x", description: - "Don’t use the standard ignore rules (see gitignore(5)), but still use the ignore rules given with -e options from the command line. This allows removing all untracked files, including build products. This can be used (possibly in conjunction with git restore or git reset) to create a pristine working directory to test a clean build", + "Don't use the standard ignore rules (see gitignore(5)), but still use the ignore rules given with -e options from the command line. This allows removing all untracked files, including build products. This can be used (possibly in conjunction with git restore or git reset) to create a pristine working directory to test a clean build", }, { name: "-X", @@ -5721,7 +5765,7 @@ const completionSpec: Fig.Spec = { name: ["--rebase", "-r"], isDangerous: true, description: - "Fetch the remote’s copy of current branch and rebases it into the local copy", + "Fetch the remote's copy of current branch and rebases it into the local copy", args: { isOptional: true, name: "remote", @@ -5813,7 +5857,7 @@ const completionSpec: Fig.Spec = { { name: "--signoff", description: - "Add a Signed-off-by trailer by the committer at the end of the commit log message. The meaning of a signoff depends on the project to which you’re committing. For example, it may certify that the committer has the rights to submit the work under the project’s license or agrees to some contributor representation, such as a Developer Certificate of Origin. (See http://developercertificate.org for the one used by the Linux kernel and Git projects.) Consult the documentation or leadership of the project to which you’re contributing to understand how the signoffs are used in that project", + "Add a Signed-off-by trailer by the committer at the end of the commit log message. The meaning of a signoff depends on the project to which you're committing. For example, it may certify that the committer has the rights to submit the work under the project's license or agrees to some contributor representation, such as a Developer Certificate of Origin. (See http://developercertificate.org for the one used by the Linux kernel and Git projects.) Consult the documentation or leadership of the project to which you're contributing to understand how the signoffs are used in that project", }, { name: "--no-signoff", @@ -6312,6 +6356,504 @@ const completionSpec: Fig.Spec = { name: "pattern", }, }, + { + name: "--committer", + description: "Search for commits by a particular committer", + requiresSeparator: true, + args: { + name: "pattern", + }, + }, + { + name: "--graph", + description: "Draw a text-based graphical representation of the commit history", + }, + { + name: "--all", + description: "Show all branches", + }, + { + name: "--decorate", + description: "Show ref names of commits", + }, + { + name: "--no-decorate", + description: "Do not show ref names of commits", + }, + { + name: "--abbrev-commit", + description: "Show only the first few characters of the SHA-1 checksum", + }, + { + name: ["-n", "--max-count"], + description: "Limit the number of commits to output", + requiresSeparator: true, + args: { + name: "number", + }, + }, + { + name: "--since", + description: "Show commits more recent than a specific date", + requiresSeparator: true, + args: { + name: "date", + }, + }, + { + name: "--after", + description: "Show commits more recent than a specific date", + requiresSeparator: true, + args: { + name: "date", + }, + }, + { + name: "--until", + description: "Show commits older than a specific date", + requiresSeparator: true, + args: { + name: "date", + }, + }, + { + name: "--before", + description: "Show commits older than a specific date", + requiresSeparator: true, + args: { + name: "date", + }, + }, + { + name: "--merges", + description: "Show only merge commits", + }, + { + name: "--no-merges", + description: "Do not show merge commits", + }, + { + name: "--first-parent", + description: "Follow only the first parent commit upon seeing a merge commit", + }, + { + name: "--reverse", + description: "Output the commits in reverse order", + }, + { + name: "--relative-date", + description: "Show dates relative to the current time", + }, + { + name: "--date", + description: "Format dates (iso, rfc, short, relative, local, etc.)", + requiresSeparator: true, + args: { + name: "format", + suggestions: [ + { name: "relative", description: "Relative to current time" }, + { name: "local", description: "Local timezone" }, + { name: "iso", description: "ISO 8601 format" }, + { name: "iso8601", description: "ISO 8601 format" }, + { name: "iso-strict", description: "Strict ISO 8601 format" }, + { name: "rfc", description: "RFC 2822 format" }, + { name: "rfc2822", description: "RFC 2822 format" }, + { name: "short", description: "YYYY-MM-DD format" }, + { name: "raw", description: "Seconds since epoch + timezone" }, + { name: "human", description: "Human-readable format" }, + { name: "unix", description: "Unix timestamp (seconds since epoch)" }, + { name: "default", description: "Default ctime-like format" }, + { name: "format:", description: "Custom strftime format" }, + ], + }, + }, + { + name: "--pretty", + description: "Pretty-print the contents of the commit logs", + requiresSeparator: true, + args: { + name: "format", + suggestions: [ + { name: "oneline", description: "Show each commit as a single line" }, + { name: "short", description: "Show commit and author" }, + { name: "medium", description: "Show commit, author, and date (default)" }, + { name: "full", description: "Show commit, author, and committer" }, + { name: "fuller", description: "Show commit, author, committer, and dates" }, + { name: "reference", description: "Abbreviated hash with title and date" }, + { name: "email", description: "Format as email with headers" }, + { name: "mboxrd", description: "Email format with quoted From lines" }, + { name: "raw", description: "Show raw commit object" }, + { name: "format:", description: "Custom format string with placeholders" }, + { name: "tformat:", description: "Custom format with terminator semantics" }, + ], + }, + }, + { + name: "--format", + description: "Pretty-print the contents of the commit logs in a given format", + requiresSeparator: true, + args: { + name: "format", + }, + }, + { + name: "--name-only", + description: "Show only names of changed files", + }, + { + name: "--name-status", + description: "Show names and status of changed files", + }, + { + name: "--shortstat", + description: "Output only the last line of the --stat format", + }, + { + name: "-S", + description: "Look for differences that change the number of occurrences of the specified string", + requiresSeparator: true, + args: { + name: "string", + }, + }, + { + name: "-G", + description: "Look for differences whose patch text contains added/removed lines that match ", + requiresSeparator: true, + args: { + name: "regex", + }, + }, + { + name: "--no-walk", + description: "Only display the given commits, but do not traverse their ancestors", + }, + { + name: "--cherry-pick", + description: "Omit any commit that introduces the same change as another commit", + }, + { + name: ["-i", "--regexp-ignore-case"], + description: "Match patterns case-insensitively", + }, + { + name: ["-E", "--extended-regexp"], + description: "Use extended regular expressions for patterns", + }, + { + name: ["-F", "--fixed-strings"], + description: "Use fixed string matching instead of patterns", + }, + { + name: ["-P", "--perl-regexp"], + description: "Use Perl-compatible regular expressions", + }, + { + name: "--all-match", + description: "Match all --grep patterns instead of any", + }, + { + name: "--invert-grep", + description: "Show commits that don't match the --grep pattern", + }, + { + name: "--skip", + description: "Skip a number of commits before starting to show output", + requiresSeparator: true, + args: { + name: "number", + }, + }, + { + name: "--min-parents", + description: "Show only commits with at least this many parents", + requiresSeparator: true, + args: { + name: "number", + }, + }, + { + name: "--max-parents", + description: "Show only commits with at most this many parents", + requiresSeparator: true, + args: { + name: "number", + }, + }, + { + name: "--branches", + description: "Show commits from all branches", + args: { + name: "pattern", + isOptional: true, + }, + }, + { + name: "--tags", + description: "Show commits from all tags", + args: { + name: "pattern", + isOptional: true, + }, + }, + { + name: "--remotes", + description: "Show commits from all remote-tracking branches", + args: { + name: "pattern", + isOptional: true, + }, + }, + { + name: "--glob", + description: "Show commits from refs matching the given shell glob pattern", + requiresSeparator: true, + args: { + name: "pattern", + }, + }, + { + name: "--exclude", + description: "Exclude refs matching the given shell glob pattern", + requiresSeparator: true, + args: { + name: "pattern", + }, + }, + { + name: ["-g", "--walk-reflogs"], + description: "Walk reflog entries from most recent to oldest", + }, + { + name: "--boundary", + description: "Output excluded boundary commits", + }, + { + name: "--date-order", + description: "Show commits in date order", + }, + { + name: "--author-date-order", + description: "Show commits in author date order", + }, + { + name: "--topo-order", + description: "Show commits in topological order", + }, + { + name: "--parents", + description: "Print parent commit hashes", + }, + { + name: "--children", + description: "Print child commit hashes", + }, + { + name: "--left-right", + description: "Mark commits with < or > for left or right side of symmetric difference", + }, + { + name: "--cherry-mark", + description: "Mark equivalent commits with = and others with +", + }, + { + name: "--left-only", + description: "Show only commits on the left side of a symmetric difference", + }, + { + name: "--right-only", + description: "Show only commits on the right side of a symmetric difference", + }, + { + name: "--cherry", + description: "Synonym for --right-only --cherry-mark --no-merges", + }, + { + name: "--full-history", + description: "Show full commit history without simplification", + }, + { + name: "--simplify-merges", + description: "Remove unnecessary merges from history", + }, + { + name: "--ancestry-path", + description: "Only display commits between the specified range that are ancestors of the end commit", + }, + { + name: "--numstat", + description: "Show number of added and deleted lines in decimal notation", + }, + { + name: "--no-patch", + description: "Suppress diff output", + }, + { + name: "--raw", + description: "Show output in raw format", + }, + { + name: "-m", + description: "Show diffs for merge commits", + }, + { + name: "-c", + description: "Show combined diff format for merge commits", + }, + { + name: "--cc", + description: "Show condensed combined diff format for merge commits", + }, + { + name: "--notes", + description: "Show notes attached to commits", + args: { + name: "ref", + isOptional: true, + }, + }, + { + name: "--no-notes", + description: "Do not show notes", + }, + { + name: "--show-notes", + description: "Show notes (default when showing commit messages)", + }, + { + name: "-L", + description: "Trace the evolution of a line range or function", + requiresSeparator: true, + args: { + name: "range:file", + }, + }, + { + name: "--no-abbrev-commit", + description: "Show full 40-byte hexadecimal commit object names", + }, + { + name: "--encoding", + description: "Re-encode commit messages in the specified character encoding", + requiresSeparator: true, + args: { + name: "encoding", + }, + }, + { + name: "--no-commit-id", + description: "Suppress commit IDs in output", + }, + { + name: "--diff-filter", + description: "Select only files that are Added (A), Copied (C), Deleted (D), Modified (M), Renamed (R), etc.", + requiresSeparator: true, + args: { + name: "filter", + suggestions: [ + { name: "A", description: "Added files" }, + { name: "C", description: "Copied files" }, + { name: "D", description: "Deleted files" }, + { name: "M", description: "Modified files" }, + { name: "R", description: "Renamed files" }, + { name: "T", description: "Type changed files" }, + { name: "U", description: "Unmerged files" }, + { name: "X", description: "Unknown files" }, + { name: "B", description: "Broken files" }, + ], + }, + }, + { + name: "--full-diff", + description: "Show full diff, not just for specified paths", + }, + { + name: "--log-size", + description: "Include log size information", + }, + { + name: ["-U", "--unified"], + description: "Generate diffs with lines of context", + requiresSeparator: true, + args: { + name: "lines", + }, + }, + { + name: "--summary", + description: "Show a diffstat summary of created, renamed, and mode changes", + }, + { + name: "--patch-with-stat", + description: "Synonym for -p --stat", + }, + { + name: "--ignore-space-change", + description: "Ignore changes in whitespace", + }, + { + name: "--ignore-all-space", + description: "Ignore all whitespace when comparing lines", + }, + { + name: "--ignore-blank-lines", + description: "Ignore changes whose lines are all blank", + }, + { + name: "--function-context", + description: "Show whole function as context", + }, + { + name: "--ext-diff", + description: "Allow external diff helper to be executed", + }, + { + name: "--no-ext-diff", + description: "Disallow external diff helper", + }, + { + name: "--textconv", + description: "Allow external text conversion filters for binary files", + }, + { + name: "--no-textconv", + description: "Disallow external text conversion filters", + }, + { + name: "--color", + description: "Show colored diff", + args: { + name: "when", + isOptional: true, + suggestions: [ + { name: "always", description: "Always use colors" }, + { name: "never", description: "Never use colors" }, + { name: "auto", description: "Use colors when output is to a terminal" }, + ], + }, + }, + { + name: "--no-color", + description: "Turn off colored diff", + }, + { + name: "--word-diff", + description: "Show word diff", + args: { + name: "mode", + isOptional: true, + suggestions: [ + { name: "color", description: "Highlight changed words using colors" }, + { name: "plain", description: "Show words with [-removed-] and {+added+}" }, + { name: "porcelain", description: "Use special line-based format" }, + { name: "none", description: "Disable word diff" }, + ], + }, + }, + { + name: "--color-words", + description: "Equivalent to --word-diff=color", + }, ], args: [ { @@ -6349,7 +6891,7 @@ const completionSpec: Fig.Spec = { { name: "-m", description: - "A symbolic-ref refs/remotes//HEAD is set up to point at remote’s branch", + "A symbolic-ref refs/remotes//HEAD is set up to point at remote's branch", args: { name: "master", }, @@ -6431,7 +6973,7 @@ const completionSpec: Fig.Spec = { }, { name: ["rm", "remove"], - description: "Removes given remote [name]", + description: "Removes the given remote", args: { name: "remote", generators: gitGenerators.remotes, @@ -6440,7 +6982,7 @@ const completionSpec: Fig.Spec = { }, { name: "rename", - description: "Removes given remote [name]", + description: "Renames the given remote", args: [ { name: "old remote", @@ -6778,7 +7320,7 @@ const completionSpec: Fig.Spec = { name: "path", }, description: - 'Prepend to paths printed in informative messages such as ”Fetching submodule foo". This option is used internally when recursing over submodules', + 'Prepend to paths printed in informative messages such as "Fetching submodule foo". This option is used internally when recursing over submodules', }, { name: "--recurse-submodules-default", @@ -7155,7 +7697,7 @@ const completionSpec: Fig.Spec = { { name: "--server-option", description: - "Transmit the given string to the server when communicating using protocol version 2. The given string must not contain a NUL or LF character. The server’s handling of server options, including unknown ones, is server-specific. When multiple --server-option=